From 0ecfbac908010c5152554653454a5b82ee057761 Mon Sep 17 00:00:00 2001 From: mkt Date: Fri, 14 Aug 2026 10:13:02 +0800 Subject: [PATCH 1/2] fix: sync common package version during builds Signed-off-by: mkt --- .github/workflows/build.yml | 18 +++-- Makefile | 8 +- scripts/sync_common_version.py | 59 ++++++++++++++ src/common/CHANGELOG.md | 7 ++ src/common/oracle_mcp_common/__init__.py | 2 +- .../tests/test_version_sync.py | 78 +++++++++++++++++++ 6 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 scripts/sync_common_version.py create mode 100644 src/common/oracle_mcp_common/tests/test_version_sync.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bbf0c207..c2963fe0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,15 +28,17 @@ jobs: run: pip install -r requirements-dev.txt - name: Update __init__.py - working-directory: src/${{ matrix.directory }} run: | - name=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['name'])") - version=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") - if [ -d oracle/*_mcp_server ]; then - init_py_file=oracle/*_mcp_server/__init__.py - echo "\"\"\"\nCopyright (c) 2025, Oracle and/or its affiliates.\nLicensed under the Universal Permissive License v1.0 as shown at\nhttps://oss.oracle.com/licenses/upl.\n\"\"\"\n" > $$init_py_file; \ - echo "__project__ = \"$name\"" >> $init_py_file - echo "__version__ = \"$version\"" >> $init_py_file + package_dir="src/${{ matrix.directory }}" + name=$(python -c "import tomllib; print(tomllib.load(open('$package_dir/pyproject.toml', 'rb'))['project']['name'])") + version=$(python -c "import tomllib; print(tomllib.load(open('$package_dir/pyproject.toml', 'rb'))['project']['version'])") + if [ "${{ matrix.directory }}" = "common" ]; then + python scripts/sync_common_version.py "$package_dir" + elif [ -d "$package_dir"/oracle/*_mcp_server ]; then + init_py_file=$(echo "$package_dir"/oracle/*_mcp_server/__init__.py) + printf '\"\"\"\nCopyright (c) 2025, 2026 Oracle and/or its affiliates.\nLicensed under the Universal Permissive License v1.0 as shown at\nhttps://oss.oracle.com/licenses/upl.\n\"\"\"\n\n' > "$init_py_file" + echo "__project__ = \"$name\"" >> "$init_py_file" + echo "__version__ = \"$version\"" >> "$init_py_file" fi - name: Sync diff --git a/Makefile b/Makefile index a95ca399..542e4230 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ PUBLISH_URL ?= $(PYPI_PUBLISH_URL) PUBLISH_CHECK_URL ?= $(PYPI_CHECK_URL) VERIFY_INDEX ?= $(PYPI_CHECK_URL) -.PHONY: build build-common build-servers publish publish-common publish-servers \ +.PHONY: build build-common build-servers verify-common-version publish publish-common publish-servers \ test-publish test-publish-common test-publish-servers verify-published \ release test-release wait-for-common _build _publish test format @@ -35,6 +35,10 @@ build: build-common: @$(MAKE) _build BUILD_DIRS="$(COMMON_PROJECT_PATH)" + @$(MAKE) verify-common-version + +verify-common-version: + @python scripts/sync_common_version.py --check $(COMMON_PROJECT_PATH) build-servers: @$(MAKE) _build BUILD_DIRS="$(SERVER_DIRS)" @@ -51,6 +55,8 @@ _build: printf '"""\nCopyright (c) 2025, 2026 Oracle and/or its affiliates.\nLicensed under the Universal Permissive License v1.0 as shown at\nhttps://oss.oracle.com/licenses/upl.\n"""\n\n' > $$init_py_file; \ echo "__project__ = \"$$name\"" >> $$init_py_file; \ echo "__version__ = \"$$version\"" >> $$init_py_file; \ + elif [ "$$dir" = "$(COMMON_PROJECT_PATH)" ]; then \ + python scripts/sync_common_version.py $$dir; \ fi; \ cd $$dir && uv build --clear && cd ../..; \ fi \ diff --git a/scripts/sync_common_version.py b/scripts/sync_common_version.py new file mode 100644 index 00000000..4866f9ee --- /dev/null +++ b/scripts/sync_common_version.py @@ -0,0 +1,59 @@ +"""Keep oracle-mcp-common runtime version metadata aligned with pyproject.toml. + +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from __future__ import annotations + +import argparse +import re +import tomllib +from pathlib import Path + + +VERSION_PATTERN = re.compile(r'^__version__ = "[^"]*"$', re.MULTILINE) + + +def project_version(package_dir: Path) -> str: + with (package_dir / "pyproject.toml").open("rb") as pyproject_file: + pyproject = tomllib.load(pyproject_file) + try: + version = pyproject["project"]["version"] + except KeyError as error: + raise ValueError("pyproject.toml must define project.version") from error + if not isinstance(version, str) or not version: + raise ValueError("pyproject.toml must define a non-empty project.version") + return version + + +def synchronize(package_dir: Path, *, check: bool = False) -> None: + init_file = package_dir / "oracle_mcp_common" / "__init__.py" + content = init_file.read_text(encoding="utf-8") + version = project_version(package_dir) + updated, replacements = VERSION_PATTERN.subn(f'__version__ = "{version}"', content) + if replacements != 1: + raise ValueError( + f"{init_file} must contain exactly one __version__ declaration; found {replacements}" + ) + if check and updated != content: + raise ValueError(f"{init_file} does not match pyproject.toml project.version {version}") + if not check: + with init_file.open("w", encoding="utf-8", newline="") as init_file_handle: + init_file_handle.write(updated) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("package_dir", type=Path) + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + try: + synchronize(args.package_dir, check=args.check) + except ValueError as error: + parser.error(str(error)) + + +if __name__ == "__main__": + main() diff --git a/src/common/CHANGELOG.md b/src/common/CHANGELOG.md index 2987544a..d837bb3d 100644 --- a/src/common/CHANGELOG.md +++ b/src/common/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to `oracle-mcp-common` are documented in this file. +## Unreleased + +### Fixed + +- Build-time version metadata now synchronizes `oracle_mcp_common.__version__` + with the package version declared in `pyproject.toml`. + ## 0.1.2 ### Fixed diff --git a/src/common/oracle_mcp_common/__init__.py b/src/common/oracle_mcp_common/__init__.py index baeb9b34..546bea7d 100644 --- a/src/common/oracle_mcp_common/__init__.py +++ b/src/common/oracle_mcp_common/__init__.py @@ -35,4 +35,4 @@ ] __project__ = "oracle_mcp_common" -__version__ = "0.1.1" +__version__ = "0.1.2" diff --git a/src/common/oracle_mcp_common/tests/test_version_sync.py b/src/common/oracle_mcp_common/tests/test_version_sync.py new file mode 100644 index 00000000..c141ec41 --- /dev/null +++ b/src/common/oracle_mcp_common/tests/test_version_sync.py @@ -0,0 +1,78 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import pytest + + +SCRIPT_PATH = Path(__file__).resolve().parents[4] / "scripts" / "sync_common_version.py" +SPEC = importlib.util.spec_from_file_location("sync_common_version", SCRIPT_PATH) +assert SPEC and SPEC.loader +sync_common_version = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(sync_common_version) + + +def write_common_package(tmp_path: Path, init_content: str) -> Path: + package_dir = tmp_path / "common" + package_dir.mkdir() + (package_dir / "pyproject.toml").write_text( + '[project]\nname = "oracle-mcp-common"\nversion = "0.1.2"\n', + encoding="utf-8", + ) + init_file = package_dir / "oracle_mcp_common" / "__init__.py" + init_file.parent.mkdir() + init_file.write_text(init_content, encoding="utf-8") + return package_dir + + +def test_synchronize_updates_only_the_version_declaration(tmp_path: Path) -> None: + package_dir = write_common_package( + tmp_path, + 'from .auth import AuthContext\n\n__all__ = ["AuthContext"]\n' + '__project__ = "oracle_mcp_common"\n__version__ = "0.1.1"\n', + ) + + sync_common_version.synchronize(package_dir) + + assert (package_dir / "oracle_mcp_common" / "__init__.py").read_text(encoding="utf-8") == ( + 'from .auth import AuthContext\n\n__all__ = ["AuthContext"]\n' + '__project__ = "oracle_mcp_common"\n__version__ = "0.1.2"\n' + ) + + +@pytest.mark.parametrize( + "init_content, expected_error", + [ + ('__project__ = "oracle_mcp_common"\n', "found 0"), + ('__version__ = "0.1.1"\n__version__ = "0.1.0"\n', "found 2"), + ], +) +def test_synchronize_rejects_missing_or_ambiguous_versions( + tmp_path: Path, init_content: str, expected_error: str +) -> None: + package_dir = write_common_package(tmp_path, init_content) + + with pytest.raises(ValueError, match=expected_error): + sync_common_version.synchronize(package_dir) + + +def test_check_rejects_a_drifted_version(tmp_path: Path) -> None: + package_dir = write_common_package(tmp_path, '__version__ = "0.1.1"\n') + + with pytest.raises(ValueError, match="does not match"): + sync_common_version.synchronize(package_dir, check=True) + + +def test_synchronize_rejects_a_missing_project_version(tmp_path: Path) -> None: + package_dir = write_common_package(tmp_path, '__version__ = "0.1.1"\n') + (package_dir / "pyproject.toml").write_text("[project]\n", encoding="utf-8") + + with pytest.raises(ValueError, match="must define project.version"): + sync_common_version.synchronize(package_dir) From 4656cf0c0063c319ba44e8c1ac10a35875b7bcb3 Mon Sep 17 00:00:00 2001 From: mkt Date: Sun, 16 Aug 2026 14:34:47 +0800 Subject: [PATCH 2/2] fix: sync common package version in Makefile Signed-off-by: mkt --- .github/workflows/build.yml | 21 ++--- Makefile | 13 ++-- scripts/sync_common_version.py | 59 -------------- .../tests/test_version_sync.py | 78 ------------------- 4 files changed, 16 insertions(+), 155 deletions(-) delete mode 100644 scripts/sync_common_version.py delete mode 100644 src/common/oracle_mcp_common/tests/test_version_sync.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2963fe0..55e83aab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,17 +28,18 @@ jobs: run: pip install -r requirements-dev.txt - name: Update __init__.py + working-directory: src/${{ matrix.directory }} run: | - package_dir="src/${{ matrix.directory }}" - name=$(python -c "import tomllib; print(tomllib.load(open('$package_dir/pyproject.toml', 'rb'))['project']['name'])") - version=$(python -c "import tomllib; print(tomllib.load(open('$package_dir/pyproject.toml', 'rb'))['project']['version'])") - if [ "${{ matrix.directory }}" = "common" ]; then - python scripts/sync_common_version.py "$package_dir" - elif [ -d "$package_dir"/oracle/*_mcp_server ]; then - init_py_file=$(echo "$package_dir"/oracle/*_mcp_server/__init__.py) - printf '\"\"\"\nCopyright (c) 2025, 2026 Oracle and/or its affiliates.\nLicensed under the Universal Permissive License v1.0 as shown at\nhttps://oss.oracle.com/licenses/upl.\n\"\"\"\n\n' > "$init_py_file" - echo "__project__ = \"$name\"" >> "$init_py_file" - echo "__version__ = \"$version\"" >> "$init_py_file" + name=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['name'])") + version=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") + if [ -f oracle_mcp_common/__init__.py ]; then + init_py_file=oracle_mcp_common/__init__.py + python -c 'from pathlib import Path; import re, sys; p = Path(sys.argv[1]); text = p.read_text(); text, count = re.subn(r"^__version__ = \".*\"$", f"__version__ = \"{sys.argv[2]}\"", text, flags=re.MULTILINE); assert count == 1, f"Expected exactly one __version__ in {p}, found {count}"; p.write_text(text)' "$init_py_file" "$version" + elif [ -d oracle/*_mcp_server ]; then + init_py_file=oracle/*_mcp_server/__init__.py + echo "\"\"\"\nCopyright (c) 2025, Oracle and/or its affiliates.\nLicensed under the Universal Permissive License v1.0 as shown at\nhttps://oss.oracle.com/licenses/upl.\n\"\"\"\n" > $$init_py_file; \ + echo "__project__ = \"$name\"" >> $init_py_file + echo "__version__ = \"$version\"" >> $init_py_file fi - name: Sync diff --git a/Makefile b/Makefile index 542e4230..56b57260 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ PUBLISH_URL ?= $(PYPI_PUBLISH_URL) PUBLISH_CHECK_URL ?= $(PYPI_CHECK_URL) VERIFY_INDEX ?= $(PYPI_CHECK_URL) -.PHONY: build build-common build-servers verify-common-version publish publish-common publish-servers \ +.PHONY: build build-common build-servers publish publish-common publish-servers \ test-publish test-publish-common test-publish-servers verify-published \ release test-release wait-for-common _build _publish test format @@ -35,10 +35,6 @@ build: build-common: @$(MAKE) _build BUILD_DIRS="$(COMMON_PROJECT_PATH)" - @$(MAKE) verify-common-version - -verify-common-version: - @python scripts/sync_common_version.py --check $(COMMON_PROJECT_PATH) build-servers: @$(MAKE) _build BUILD_DIRS="$(SERVER_DIRS)" @@ -50,13 +46,14 @@ _build: name=$$(python -c "import tomllib; print(tomllib.load(open('$$dir/pyproject.toml', 'rb'))['project']['name'])"); \ version=$$(python -c "import tomllib; print(tomllib.load(open('$$dir/pyproject.toml', 'rb'))['project']['version'])"); \ echo "Building $$dir: $$name==$$version"; \ - if [ -d $$dir/oracle/*_mcp_server ]; then \ + if [ -f "$$dir/oracle_mcp_common/__init__.py" ]; then \ + init_py_file="$$dir/oracle_mcp_common/__init__.py"; \ + python -c 'from pathlib import Path; import re, sys; p = Path(sys.argv[1]); text = p.read_text(); text, count = re.subn(r"^__version__ = \".*\"$$", f"__version__ = \"{sys.argv[2]}\"", text, flags=re.MULTILINE); assert count == 1, f"Expected exactly one __version__ in {p}, found {count}"; p.write_text(text)' "$$init_py_file" "$$version"; \ + elif [ -d $$dir/oracle/*_mcp_server ]; then \ init_py_file=$$(echo $$dir/oracle/*_mcp_server/__init__.py); \ printf '"""\nCopyright (c) 2025, 2026 Oracle and/or its affiliates.\nLicensed under the Universal Permissive License v1.0 as shown at\nhttps://oss.oracle.com/licenses/upl.\n"""\n\n' > $$init_py_file; \ echo "__project__ = \"$$name\"" >> $$init_py_file; \ echo "__version__ = \"$$version\"" >> $$init_py_file; \ - elif [ "$$dir" = "$(COMMON_PROJECT_PATH)" ]; then \ - python scripts/sync_common_version.py $$dir; \ fi; \ cd $$dir && uv build --clear && cd ../..; \ fi \ diff --git a/scripts/sync_common_version.py b/scripts/sync_common_version.py deleted file mode 100644 index 4866f9ee..00000000 --- a/scripts/sync_common_version.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Keep oracle-mcp-common runtime version metadata aligned with pyproject.toml. - -Copyright (c) 2026, Oracle and/or its affiliates. -Licensed under the Universal Permissive License v1.0 as shown at -https://oss.oracle.com/licenses/upl. -""" - -from __future__ import annotations - -import argparse -import re -import tomllib -from pathlib import Path - - -VERSION_PATTERN = re.compile(r'^__version__ = "[^"]*"$', re.MULTILINE) - - -def project_version(package_dir: Path) -> str: - with (package_dir / "pyproject.toml").open("rb") as pyproject_file: - pyproject = tomllib.load(pyproject_file) - try: - version = pyproject["project"]["version"] - except KeyError as error: - raise ValueError("pyproject.toml must define project.version") from error - if not isinstance(version, str) or not version: - raise ValueError("pyproject.toml must define a non-empty project.version") - return version - - -def synchronize(package_dir: Path, *, check: bool = False) -> None: - init_file = package_dir / "oracle_mcp_common" / "__init__.py" - content = init_file.read_text(encoding="utf-8") - version = project_version(package_dir) - updated, replacements = VERSION_PATTERN.subn(f'__version__ = "{version}"', content) - if replacements != 1: - raise ValueError( - f"{init_file} must contain exactly one __version__ declaration; found {replacements}" - ) - if check and updated != content: - raise ValueError(f"{init_file} does not match pyproject.toml project.version {version}") - if not check: - with init_file.open("w", encoding="utf-8", newline="") as init_file_handle: - init_file_handle.write(updated) - - -def main() -> None: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("package_dir", type=Path) - parser.add_argument("--check", action="store_true") - args = parser.parse_args() - try: - synchronize(args.package_dir, check=args.check) - except ValueError as error: - parser.error(str(error)) - - -if __name__ == "__main__": - main() diff --git a/src/common/oracle_mcp_common/tests/test_version_sync.py b/src/common/oracle_mcp_common/tests/test_version_sync.py deleted file mode 100644 index c141ec41..00000000 --- a/src/common/oracle_mcp_common/tests/test_version_sync.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Copyright (c) 2026, Oracle and/or its affiliates. -Licensed under the Universal Permissive License v1.0 as shown at -https://oss.oracle.com/licenses/upl. -""" - -from __future__ import annotations - -import importlib.util -from pathlib import Path - -import pytest - - -SCRIPT_PATH = Path(__file__).resolve().parents[4] / "scripts" / "sync_common_version.py" -SPEC = importlib.util.spec_from_file_location("sync_common_version", SCRIPT_PATH) -assert SPEC and SPEC.loader -sync_common_version = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(sync_common_version) - - -def write_common_package(tmp_path: Path, init_content: str) -> Path: - package_dir = tmp_path / "common" - package_dir.mkdir() - (package_dir / "pyproject.toml").write_text( - '[project]\nname = "oracle-mcp-common"\nversion = "0.1.2"\n', - encoding="utf-8", - ) - init_file = package_dir / "oracle_mcp_common" / "__init__.py" - init_file.parent.mkdir() - init_file.write_text(init_content, encoding="utf-8") - return package_dir - - -def test_synchronize_updates_only_the_version_declaration(tmp_path: Path) -> None: - package_dir = write_common_package( - tmp_path, - 'from .auth import AuthContext\n\n__all__ = ["AuthContext"]\n' - '__project__ = "oracle_mcp_common"\n__version__ = "0.1.1"\n', - ) - - sync_common_version.synchronize(package_dir) - - assert (package_dir / "oracle_mcp_common" / "__init__.py").read_text(encoding="utf-8") == ( - 'from .auth import AuthContext\n\n__all__ = ["AuthContext"]\n' - '__project__ = "oracle_mcp_common"\n__version__ = "0.1.2"\n' - ) - - -@pytest.mark.parametrize( - "init_content, expected_error", - [ - ('__project__ = "oracle_mcp_common"\n', "found 0"), - ('__version__ = "0.1.1"\n__version__ = "0.1.0"\n', "found 2"), - ], -) -def test_synchronize_rejects_missing_or_ambiguous_versions( - tmp_path: Path, init_content: str, expected_error: str -) -> None: - package_dir = write_common_package(tmp_path, init_content) - - with pytest.raises(ValueError, match=expected_error): - sync_common_version.synchronize(package_dir) - - -def test_check_rejects_a_drifted_version(tmp_path: Path) -> None: - package_dir = write_common_package(tmp_path, '__version__ = "0.1.1"\n') - - with pytest.raises(ValueError, match="does not match"): - sync_common_version.synchronize(package_dir, check=True) - - -def test_synchronize_rejects_a_missing_project_version(tmp_path: Path) -> None: - package_dir = write_common_package(tmp_path, '__version__ = "0.1.1"\n') - (package_dir / "pyproject.toml").write_text("[project]\n", encoding="utf-8") - - with pytest.raises(ValueError, match="must define project.version"): - sync_common_version.synchronize(package_dir)