diff options
author | Jaime Resano <gemailpersonal02@gmail.com> | 2025-04-27 23:41:40 +0200 |
---|---|---|
committer | Jaime Resano <gemailpersonal02@gmail.com> | 2025-05-09 19:38:50 +0200 |
commit | 9ad97270b5ce1b0ef8103041e7309744825f1810 (patch) | |
tree | 8a92aec4685a97f171600e01fe638e8aaf3189f8 | |
parent | f81fb9ee887d088e8988d743bb7cac4f781fff82 (diff) |
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 <cristian.maureira-fredes@qt.io>
5 files changed, 72 insertions, 29 deletions
diff --git a/sources/pyside-tools/project_lib/pyproject_toml.py b/sources/pyside-tools/project_lib/pyproject_toml.py index fafe0d67d..6da7b455e 100644 --- a/sources/pyside-tools/project_lib/pyproject_toml.py +++ b/sources/pyside-tools/project_lib/pyproject_toml.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only from __future__ import annotations +import os import sys # TODO: Remove this import when Python 3.11 is the minimum supported version if sys.version_info >= (3, 11): @@ -48,19 +49,19 @@ def _parse_toml_content(content: str) -> dict: return result -def _write_toml_content(data: dict) -> str: +def _write_base_toml_content(data: dict) -> str: """ Write minimal TOML content with project and tool.pyside6-project sections. """ lines = [] - if 'project' in data and data['project']: + if data.get('project'): lines.append('[project]') for key, value in sorted(data['project'].items()): if isinstance(value, str): lines.append(f'{key} = "{value}"') - if 'tool' in data and 'pyside6-project' in data['tool']: + if data.get("tool") and data['tool'].get('pyside6-project'): lines.append('\n[tool.pyside6-project]') for key, value in sorted(data['tool']['pyside6-project'].items()): if isinstance(value, list): @@ -115,7 +116,7 @@ def parse_pyproject_toml(pyproject_toml_file: Path) -> PyProjectParseResult: def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: list[str]): """ - Create or update a pyproject.toml file with the specified content. + Create or overwrite a pyproject.toml file with the specified content. """ data = { "project": {"name": project_name}, @@ -124,13 +125,33 @@ def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: } } + content = _write_base_toml_content(data) try: - content = _write_toml_content(data) pyproject_file.write_text(content, encoding='utf-8') except Exception as e: raise ValueError(f"Error writing TOML file: {str(e)}") +def robust_relative_to_posix(target_path: Path, base_path: Path) -> str: + """ + Calculates the relative path from base_path to target_path. + Uses Path.relative_to first, falls back to os.path.relpath if it fails. + Returns the result as a POSIX path string. + """ + # Ensure both paths are absolute for reliable calculation, although in this specific code, + # project_folder and paths in output_files are expected to be resolved/absolute already. + abs_target = target_path.resolve() if not target_path.is_absolute() else target_path + abs_base = base_path.resolve() if not base_path.is_absolute() else base_path + + try: + return abs_target.relative_to(abs_base).as_posix() + except ValueError: + # Fallback to os.path.relpath which is more robust for paths that are not direct subpaths. + relative_str = os.path.relpath(str(abs_target), str(abs_base)) + # Convert back to Path temporarily to get POSIX format + return Path(relative_str).as_posix() + + def migrate_pyproject(pyproject_file: Path | str = None) -> int: """ 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: project_name = project_files[0].stem # The project files that will be written to the pyproject.toml file - output_files = set() + output_files: set[Path] = set() for project_file in project_files: project_data = parse_pyproject_json(project_file) if project_data.errors: @@ -185,39 +206,58 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int: project_name = project_folder.name pyproject_toml_file = project_folder / "pyproject.toml" - if pyproject_toml_file.exists(): - already_existing_file = True + + relative_files = sorted( + robust_relative_to_posix(p, project_folder) for p in output_files + ) + + if not (already_existing_file := pyproject_toml_file.exists()): + # Create new pyproject.toml file + data = { + "project": {"name": project_name}, + "tool": { + "pyside6-project": {"files": relative_files} + } + } + updated_content = _write_base_toml_content(data) + else: + # For an already existing file, append our tool.pyside6-project section + # If the project section is missing, add it try: content = pyproject_toml_file.read_text(encoding='utf-8') - data = _parse_toml_content(content) except Exception as e: - raise ValueError(f"Error parsing TOML: {str(e)}") - else: - already_existing_file = False - data = {"project": {}, "tool": {"pyside6-project": {}}} + print(f"Error processing existing TOML file: {str(e)}", file=sys.stderr) + return 1 - # Update project name if not present - if "name" not in data["project"]: - data["project"]["name"] = project_name + append_content = [] - # Update files list - data["tool"]["pyside6-project"]["files"] = sorted( - p.relative_to(project_folder).as_posix() for p in output_files - ) + if '[project]' not in content: + # Add project section if needed + append_content.append('\n[project]') + append_content.append(f'name = "{project_name}"') + + if '[tool.pyside6-project]' not in content: + # Add tool.pyside6-project section + append_content.append('\n[tool.pyside6-project]') + items = [f'"{item}"' for item in relative_files] + append_content.append(f'files = [{", ".join(items)}]') - # Generate TOML content - toml_content = _write_toml_content(data) + if append_content: + updated_content = content.rstrip() + '\n' + '\n'.join(append_content) + else: + # No changes needed + print("pyproject.toml already contains [project] and [tool.pyside6-project] sections") + return 0 - if already_existing_file: print(f"WARNING: A pyproject.toml file already exists at \"{pyproject_toml_file}\"") print("The file will be updated with the following content:") - print(toml_content) + print(updated_content) response = input("Proceed? [Y/n] ") if response.lower().strip() not in {"yes", "y"}: return 0 try: - pyproject_toml_file.write_text(toml_content) + pyproject_toml_file.write_text(updated_content, encoding='utf-8') except Exception as e: print(f"Error writing to \"{pyproject_toml_file}\": {str(e)}", file=sys.stderr) return 1 diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml index 1ceb0ac0b..b6f16e698 100644 --- a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml +++ b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml @@ -2,4 +2,4 @@ name = "subproject" [tool.pyside6-project] -files = ["subproject_button.py"] +files = ["subproject_button.py", "../main.py"] diff --git a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject index abfa1f461..688f07c33 100644 --- a/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject +++ b/sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject @@ -1,3 +1,3 @@ { - "files": ["subproject_button.py"] + "files": ["subproject_button.py", "../main.py"] } diff --git a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml index be8aa949f..c0adb0c76 100644 --- a/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml +++ b/sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml @@ -15,8 +15,9 @@ target-version = ["py38"] # Another comment -[tool.pyside6-project] -files = ["main.py", "zzz.py"] [build-system] requires = ["setuptools >=42"] build-backend = "setuptools.build_meta" + +[tool.pyside6-project] +files = ["main.py", "zzz.py"] diff --git a/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py b/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py index d66395251..cbacc4e48 100644 --- a/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py +++ b/sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py @@ -1,5 +1,7 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations + import contextlib import difflib import importlib |