From f4a66cf365880b37bf0c976952828b5965285b94 Mon Sep 17 00:00:00 2001 From: shiny-code-bot Date: Sat, 18 Jul 2026 20:05:38 -0400 Subject: [PATCH] fix: synchronize artifact build backend --- docker/runtime-python/pyproject.toml | 2 +- docker/runtime-python/uv.lock | 8 ++-- docs/tooling/artifact-inputs.md | 6 ++- odoo_devkit/dependency_workspace.py | 22 +++++++++++ tests/test_dependency_workspace.py | 59 +++++++++++++++++++++++++++- 5 files changed, 89 insertions(+), 8 deletions(-) diff --git a/docker/runtime-python/pyproject.toml b/docker/runtime-python/pyproject.toml index 66a6ce0..e9f08ec 100644 --- a/docker/runtime-python/pyproject.toml +++ b/docker/runtime-python/pyproject.toml @@ -3,7 +3,7 @@ name = "odoo-runtime-support" version = "0.0.0" requires-python = ">=3.13" dependencies = [ - "hatchling==1.27.0", + "hatchling==1.31.0", "passlib>=1.7.4", "pydantic>=2.13.4", "pydantic-settings>=2.14.2", diff --git a/docker/runtime-python/uv.lock b/docker/runtime-python/uv.lock index e5edeac..5e2d4e8 100644 --- a/docker/runtime-python/uv.lock +++ b/docker/runtime-python/uv.lock @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "hatchling" -version = "1.27.0" +version = "1.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, @@ -21,9 +21,9 @@ dependencies = [ { name = "pluggy" }, { name = "trove-classifiers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/8a/cc1debe3514da292094f1c3a700e4ca25442489731ef7c0814358816bb03/hatchling-1.27.0.tar.gz", hash = "sha256:971c296d9819abb3811112fc52c7a9751c8d381898f36533bb16f9791e941fd6", size = 54983, upload-time = "2024-12-15T17:08:11.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/e2/dfa73fe78f773018dcaebc6d09b819bc10d328ff5a6b4a66efa1e3d71f52/hatchling-1.31.0.tar.gz", hash = "sha256:6b48ad4068a482ed7239b3a8215bc55b47aad3345d58dfc94e553c5d2d46211b", size = 57208, upload-time = "2026-07-08T01:48:32.237Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/e7/ae38d7a6dfba0533684e0b2136817d667588ae3ec984c1a4e5df5eb88482/hatchling-1.27.0-py3-none-any.whl", hash = "sha256:d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b", size = 75794, upload-time = "2024-12-15T17:08:10.364Z" }, + { url = "https://files.pythonhosted.org/packages/64/e2/2c0af0a52d16be74a4f194564fcdc417521ed863e9b65e4bc9052dacba6f/hatchling-1.31.0-py3-none-any.whl", hash = "sha256:aac80bec8b6fe35e8480f1c335be8910fa210a0e6f735a139be205dadcacb544", size = 77747, upload-time = "2026-07-08T01:48:31.024Z" }, ] [[package]] @@ -39,7 +39,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "hatchling", specifier = "==1.27.0" }, + { name = "hatchling", specifier = "==1.31.0" }, { name = "passlib", specifier = ">=1.7.4" }, { name = "pydantic", specifier = ">=2.13.4" }, { name = "pydantic-settings", specifier = ">=2.14.2" }, diff --git a/docs/tooling/artifact-inputs.md b/docs/tooling/artifact-inputs.md index 5eb239c..a2c50bf 100644 --- a/docs/tooling/artifact-inputs.md +++ b/docs/tooling/artifact-inputs.md @@ -33,7 +33,11 @@ Before `platform runtime publish`, run `platform dependencies check` and commit the exact tenant, devkit, and shared-addon inputs. Publish fails closed for dirty source repos, nonordinary index flags or Git replacement refs, untracked or symlinked staged files, stale/missing tenant lock pairs, mutable VCS refs, -source-supplied `.odoo-python-source.json` markers, or staged-byte changes. +source-supplied `.odoo-python-source.json` markers, staged-byte changes, or an +addon build backend that is absent at the exact version from both the devkit +support lock and tenant lock catalog. When the manifest includes the devkit +repo, `platform dependencies check` reports that build-tool mismatch before the +publish workflow reaches Buildx. Devkit alone writes those markers from the verified Git snapshots used for the build. Each recorded source commit must also be advertised by a ref in its normalized GitHub origin; changing only `.git/config` cannot reattribute a diff --git a/odoo_devkit/dependency_workspace.py b/odoo_devkit/dependency_workspace.py index 2562500..0ec9b4d 100644 --- a/odoo_devkit/dependency_workspace.py +++ b/odoo_devkit/dependency_workspace.py @@ -120,9 +120,15 @@ def inspect_dependency_workspace(*, manifest: WorkspaceManifest) -> DependencyWo raise DependencyWorkspaceError( "Shared addons repo must be materialized before dependency inspection. Run `platform workspace sync` first." ) + devkit_repo_path = _resolve_devkit_repo_path(manifest) + if manifest.devkit_repo is not None and (devkit_repo_path is None or not devkit_repo_path.is_dir()): + raise DependencyWorkspaceError( + "Devkit repo must be materialized before dependency inspection. Run `platform workspace sync` first." + ) tenant_repo_path = tenant_repo_path.resolve() shared_addons_repo_path = shared_addons_repo_path.resolve() if shared_addons_repo_path is not None else None + devkit_repo_path = devkit_repo_path.resolve() if devkit_repo_path is not None else None project_inputs = _discover_project_inputs( tenant_repo_path=tenant_repo_path, shared_addons_repo_path=shared_addons_repo_path, @@ -198,6 +204,11 @@ def inspect_dependency_workspace(*, manifest: WorkspaceManifest) -> DependencyWo root_payload = _load_pyproject(staged_root / "pyproject.toml") root_runtime_dependencies = _validate_root_pyproject(payload=root_payload) requires_tenant_lock = requires_tenant_lock or bool(root_runtime_dependencies) + if devkit_repo_path is not None: + require_staged_build_requirements_supplied( + support_root=devkit_repo_path / "docker" / "runtime-python", + tenant_root=staged_root, + ) workspace_member_set = _workspace_members(root=staged_root, payload=root_payload) workspace_members = tuple(sorted(path.as_posix() for path in workspace_member_set)) expected_members = { @@ -342,6 +353,17 @@ def _resolve_shared_addons_repo_path(manifest: WorkspaceManifest) -> Path | None ) +def _resolve_devkit_repo_path(manifest: WorkspaceManifest) -> Path | None: + from .workspace import resolve_optional_repo_path_with_managed_checkout, resolve_workspace_path + + workspace_path = resolve_workspace_path(manifest) + return resolve_optional_repo_path_with_managed_checkout( + manifest.devkit_repo, + manifest=manifest, + managed_checkout_path=workspace_path / "sources" / "devkit", + ) + + def _discover_project_inputs(*, tenant_repo_path: Path, shared_addons_repo_path: Path | None) -> tuple[_ProjectInput, ...]: projects: list[_ProjectInput] = [] tenant_addons_root = tenant_repo_path / "addons" diff --git a/tests/test_dependency_workspace.py b/tests/test_dependency_workspace.py index 29b27b2..17c5af1 100644 --- a/tests/test_dependency_workspace.py +++ b/tests/test_dependency_workspace.py @@ -428,6 +428,55 @@ def test_addon_build_requirements_must_be_supplied_by_a_lock_catalog(self) -> No tenant_root=tenant_root, ) + def test_dependency_inspection_checks_devkit_build_requirement_catalog(self) -> None: + with tempfile.TemporaryDirectory() as temporary_directory_name: + temp_root = Path(temporary_directory_name) + tenant_repo_path = temp_root / "tenant" + devkit_repo_path = temp_root / "devkit" + self._write_member_pyproject( + tenant_repo_path / "addons" / "tenant_addon", + build_requirement="hatchling==1.31.0", + ) + self._write_root_workspace( + tenant_repo_path=tenant_repo_path, + members=("addons/*",), + ) + support_root = devkit_repo_path / "docker" / "runtime-python" + support_root.mkdir(parents=True) + support_pyproject = support_root / "pyproject.toml" + support_pyproject.write_text( + '[project]\nname = "runtime-support"\nversion = "0.0.0"\n' + 'dependencies = ["hatchling==1.27.0"]\n\n[tool.uv]\npackage = false\n', + encoding="utf-8", + ) + manifest = self._write_manifest( + temp_root=temp_root, + tenant_repo_path=tenant_repo_path, + devkit_repo_path=devkit_repo_path, + ) + self._commit_repo(tenant_repo_path) + + with mock.patch("odoo_devkit.dependency_workspace._uv_lock_is_current", return_value=True): + inspection = inspect_dependency_workspace(manifest=manifest) + + self.assertFalse(inspection.current) + self.assertIn( + "Addon build requirements must be supplied by the support/runtime or tenant lock catalog: hatchling==1.31.0", + inspection.findings, + ) + + support_pyproject.write_text( + support_pyproject.read_text(encoding="utf-8").replace( + "hatchling==1.27.0", + "hatchling==1.31.0", + ), + encoding="utf-8", + ) + with mock.patch("odoo_devkit.dependency_workspace._uv_lock_is_current", return_value=True): + inspection = inspect_dependency_workspace(manifest=manifest) + + self.assertTrue(inspection.current, inspection.findings) + def test_owned_requirements_file_fails(self) -> None: with tempfile.TemporaryDirectory() as temporary_directory_name: temp_root = Path(temporary_directory_name) @@ -470,6 +519,7 @@ def _write_member_pyproject( *, project_name: str = "tenant_addon", dependencies: tuple[str, ...] = (), + build_requirement: str = "hatchling==1.27.0", ) -> None: project_root.mkdir(parents=True, exist_ok=True) dependency_lines = ",\n".join(f" {json.dumps(dependency)}" for dependency in dependencies) @@ -477,7 +527,7 @@ def _write_member_pyproject( dependency_lines += ",\n" (project_root / "pyproject.toml").write_text( "[build-system]\n" - 'requires = ["hatchling==1.27.0"]\n' + f"requires = [{json.dumps(build_requirement)}]\n" 'build-backend = "hatchling.build"\n\n' "[project]\n" f'name = "{project_name}"\n' @@ -526,12 +576,17 @@ def _write_manifest( temp_root: Path, tenant_repo_path: Path, shared_repo_path: Path | None = None, + devkit_repo_path: Path | None = None, ) -> WorkspaceManifest: tenant_repo_path.mkdir(parents=True, exist_ok=True) shared_repo_table = "" if shared_repo_path is not None: shared_repo_path.mkdir(parents=True, exist_ok=True) shared_repo_table = f'\n[repos.shared_addons]\nname = "shared-addons"\npath = "{shared_repo_path}"\nref = "main"\n' + devkit_repo_table = "" + if devkit_repo_path is not None: + devkit_repo_path.mkdir(parents=True, exist_ok=True) + devkit_repo_table = f'\n[repos.devkit]\nname = "odoo-devkit"\npath = "{devkit_repo_path}"\nref = "main"\n' manifest_path = tenant_repo_path / "workspace.toml" manifest_path.write_text( "schema_version = 1\n" @@ -544,7 +599,7 @@ def _write_manifest( 'name = "tenant"\n' 'path = "."\n' 'ref = "main"\n' - f"{shared_repo_table}\n" + f"{shared_repo_table}{devkit_repo_table}\n" "[runtime]\n" 'context = "test"\n' 'instance = "local"\n'