Skip to content

Commit 9ad9727

Browse files
author
Jaime Resano
committed
Fix pyside6-project relative paths handling and pyproject.toml migration
This patch fixes two existing bugs in the pyside6-project tool: 1. Relative file paths outside the project folder in the .pyproject file e.g. files = ["../main.py"] are not handled correctly. A ValueError is raised due to the usage of Path.relative_to Fixed using a new function: robust_relative_to_posix() Updated the example_project test to contain this edge case (Suggestion: use this as a replacement for the rel_path() function located in tools/example_gallery/main.py) 2. The migration of a .pyproject to a pyproject.toml file with existing content is not done properly due to removing the tomlkit implementation. Fixed by appending the [tool.pyside6-project] section (and the [project] section too) at the end of the file (I don't know how the tests were passing before ??) Change-Id: Ie061c9fa172c429c8b45381bbec35ad30d01f4ee Reviewed-by: Cristian Maureira-Fredes <[email protected]>
1 parent f81fb9e commit 9ad9727

File tree

5 files changed

+72
-29
lines changed

5 files changed

+72
-29
lines changed

sources/pyside-tools/project_lib/pyproject_toml.py

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
33
from __future__ import annotations
44

5+
import os
56
import sys
67
# TODO: Remove this import when Python 3.11 is the minimum supported version
78
if sys.version_info >= (3, 11):
@@ -48,19 +49,19 @@ def _parse_toml_content(content: str) -> dict:
4849
return result
4950

5051

51-
def _write_toml_content(data: dict) -> str:
52+
def _write_base_toml_content(data: dict) -> str:
5253
"""
5354
Write minimal TOML content with project and tool.pyside6-project sections.
5455
"""
5556
lines = []
5657

57-
if 'project' in data and data['project']:
58+
if data.get('project'):
5859
lines.append('[project]')
5960
for key, value in sorted(data['project'].items()):
6061
if isinstance(value, str):
6162
lines.append(f'{key} = "{value}"')
6263

63-
if 'tool' in data and 'pyside6-project' in data['tool']:
64+
if data.get("tool") and data['tool'].get('pyside6-project'):
6465
lines.append('\n[tool.pyside6-project]')
6566
for key, value in sorted(data['tool']['pyside6-project'].items()):
6667
if isinstance(value, list):
@@ -115,7 +116,7 @@ def parse_pyproject_toml(pyproject_toml_file: Path) -> PyProjectParseResult:
115116

116117
def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: list[str]):
117118
"""
118-
Create or update a pyproject.toml file with the specified content.
119+
Create or overwrite a pyproject.toml file with the specified content.
119120
"""
120121
data = {
121122
"project": {"name": project_name},
@@ -124,13 +125,33 @@ def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files:
124125
}
125126
}
126127

128+
content = _write_base_toml_content(data)
127129
try:
128-
content = _write_toml_content(data)
129130
pyproject_file.write_text(content, encoding='utf-8')
130131
except Exception as e:
131132
raise ValueError(f"Error writing TOML file: {str(e)}")
132133

133134

135+
def robust_relative_to_posix(target_path: Path, base_path: Path) -> str:
136+
"""
137+
Calculates the relative path from base_path to target_path.
138+
Uses Path.relative_to first, falls back to os.path.relpath if it fails.
139+
Returns the result as a POSIX path string.
140+
"""
141+
# Ensure both paths are absolute for reliable calculation, although in this specific code,
142+
# project_folder and paths in output_files are expected to be resolved/absolute already.
143+
abs_target = target_path.resolve() if not target_path.is_absolute() else target_path
144+
abs_base = base_path.resolve() if not base_path.is_absolute() else base_path
145+
146+
try:
147+
return abs_target.relative_to(abs_base).as_posix()
148+
except ValueError:
149+
# Fallback to os.path.relpath which is more robust for paths that are not direct subpaths.
150+
relative_str = os.path.relpath(str(abs_target), str(abs_base))
151+
# Convert back to Path temporarily to get POSIX format
152+
return Path(relative_str).as_posix()
153+
154+
134155
def migrate_pyproject(pyproject_file: Path | str = None) -> int:
135156
"""
136157
Migrate a project *.pyproject JSON file to the new pyproject.toml format.
@@ -170,7 +191,7 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int:
170191
project_name = project_files[0].stem
171192

172193
# The project files that will be written to the pyproject.toml file
173-
output_files = set()
194+
output_files: set[Path] = set()
174195
for project_file in project_files:
175196
project_data = parse_pyproject_json(project_file)
176197
if project_data.errors:
@@ -185,39 +206,58 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int:
185206
project_name = project_folder.name
186207

187208
pyproject_toml_file = project_folder / "pyproject.toml"
188-
if pyproject_toml_file.exists():
189-
already_existing_file = True
209+
210+
relative_files = sorted(
211+
robust_relative_to_posix(p, project_folder) for p in output_files
212+
)
213+
214+
if not (already_existing_file := pyproject_toml_file.exists()):
215+
# Create new pyproject.toml file
216+
data = {
217+
"project": {"name": project_name},
218+
"tool": {
219+
"pyside6-project": {"files": relative_files}
220+
}
221+
}
222+
updated_content = _write_base_toml_content(data)
223+
else:
224+
# For an already existing file, append our tool.pyside6-project section
225+
# If the project section is missing, add it
190226
try:
191227
content = pyproject_toml_file.read_text(encoding='utf-8')
192-
data = _parse_toml_content(content)
193228
except Exception as e:
194-
raise ValueError(f"Error parsing TOML: {str(e)}")
195-
else:
196-
already_existing_file = False
197-
data = {"project": {}, "tool": {"pyside6-project": {}}}
229+
print(f"Error processing existing TOML file: {str(e)}", file=sys.stderr)
230+
return 1
198231

199-
# Update project name if not present
200-
if "name" not in data["project"]:
201-
data["project"]["name"] = project_name
232+
append_content = []
202233

203-
# Update files list
204-
data["tool"]["pyside6-project"]["files"] = sorted(
205-
p.relative_to(project_folder).as_posix() for p in output_files
206-
)
234+
if '[project]' not in content:
235+
# Add project section if needed
236+
append_content.append('\n[project]')
237+
append_content.append(f'name = "{project_name}"')
238+
239+
if '[tool.pyside6-project]' not in content:
240+
# Add tool.pyside6-project section
241+
append_content.append('\n[tool.pyside6-project]')
242+
items = [f'"{item}"' for item in relative_files]
243+
append_content.append(f'files = [{", ".join(items)}]')
207244

208-
# Generate TOML content
209-
toml_content = _write_toml_content(data)
245+
if append_content:
246+
updated_content = content.rstrip() + '\n' + '\n'.join(append_content)
247+
else:
248+
# No changes needed
249+
print("pyproject.toml already contains [project] and [tool.pyside6-project] sections")
250+
return 0
210251

211-
if already_existing_file:
212252
print(f"WARNING: A pyproject.toml file already exists at \"{pyproject_toml_file}\"")
213253
print("The file will be updated with the following content:")
214-
print(toml_content)
254+
print(updated_content)
215255
response = input("Proceed? [Y/n] ")
216256
if response.lower().strip() not in {"yes", "y"}:
217257
return 0
218258

219259
try:
220-
pyproject_toml_file.write_text(toml_content)
260+
pyproject_toml_file.write_text(updated_content, encoding='utf-8')
221261
except Exception as e:
222262
print(f"Error writing to \"{pyproject_toml_file}\": {str(e)}", file=sys.stderr)
223263
return 1

sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
name = "subproject"
33

44
[tool.pyside6-project]
5-
files = ["subproject_button.py"]
5+
files = ["subproject_button.py", "../main.py"]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"files": ["subproject_button.py"]
2+
"files": ["subproject_button.py", "../main.py"]
33
}

sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ target-version = ["py38"]
1515

1616
# Another comment
1717

18-
[tool.pyside6-project]
19-
files = ["main.py", "zzz.py"]
2018
[build-system]
2119
requires = ["setuptools >=42"]
2220
build-backend = "setuptools.build_meta"
21+
22+
[tool.pyside6-project]
23+
files = ["main.py", "zzz.py"]

sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Copyright (C) 2024 The Qt Company Ltd.
22
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3+
from __future__ import annotations
4+
35
import contextlib
46
import difflib
57
import importlib

0 commit comments

Comments
 (0)