From de394f9e4324258264721f193156714489216d72 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:52:05 -0800 Subject: [PATCH 1/7] migrate --- doc/tool_usage_guide.md | 1 + eng/tools/azure-sdk-tools/azpysdk/breaking.py | 86 +++++++++++++++++++ eng/tools/azure-sdk-tools/azpysdk/main.py | 2 + 3 files changed, 89 insertions(+) create mode 100644 eng/tools/azure-sdk-tools/azpysdk/breaking.py diff --git a/doc/tool_usage_guide.md b/doc/tool_usage_guide.md index 8a41fcee7135..7b84516cbebe 100644 --- a/doc/tool_usage_guide.md +++ b/doc/tool_usage_guide.md @@ -27,6 +27,7 @@ This repo is currently migrating all checks from a slower `tox`-based framework, |`verifysdist`| Verify directories included in sdist and contents in manifest file. Also ensures that py.typed configuration is correct within the setup.py. | `azpysdk verifysdist .` | |`verify_keywords`| Verify that the keyword 'azure sdk' is present in the targeted package's keywords. | `azpysdk verify_keywords .` | |`import_all`| Installs the package w/ default dependencies, then attempts to `import *` from the base namespace. Ensures that all imports will resolve after a base install and import. | `azpysdk import_all .` | +|`breaking`| Checks for breaking changes. | `azpysdk breaking .` | ## Common arguments diff --git a/eng/tools/azure-sdk-tools/azpysdk/breaking.py b/eng/tools/azure-sdk-tools/azpysdk/breaking.py new file mode 100644 index 000000000000..84f1b1370c0f --- /dev/null +++ b/eng/tools/azure-sdk-tools/azpysdk/breaking.py @@ -0,0 +1,86 @@ +import argparse +import os +import sys + +from typing import Optional, List +from subprocess import CalledProcessError, check_call + +from .Check import Check +from ci_tools.functions import install_into_venv, get_pip_command +from ci_tools.scenario.generation import create_package_and_install +from ci_tools.variables import discover_repo_root, in_ci, set_envvar_defaults +from ci_tools.environment_exclusions import is_check_enabled +from ci_tools.logging import logger + +JSONDIFF_VERSION = "1.2.0" +REPO_ROOT = discover_repo_root() + +class breaking(Check): + def __init__(self) -> None: + super().__init__() + + def register( + self, subparsers: "argparse._SubParsersAction", parent_parsers: Optional[List[argparse.ArgumentParser]] = None + ) -> None: + """Register the breaking change check. The breaking change check checks for breaking changes against the target package.""" + parents = parent_parsers or [] + p = subparsers.add_parser("breaking", parents=parents, help="Run the breaking change check") + p.set_defaults(func=self.run) + + def run(self, args: argparse.Namespace) -> int: + """Run the breaking change check command.""" + logger.info("Running breaking check...") + + set_envvar_defaults() + targeted = self.get_targeted_directories(args) + + results: List[int] = [] + + BREAKING_CHECKER_PATH = os.path.join(REPO_ROOT, "scripts", "breaking_changes_checker") + + for parsed in targeted: + package_dir = parsed.folder + package_name = parsed.name + executable, staging_directory = self.get_executable(args.isolate, args.command, sys.executable, package_dir) + logger.info(f"Processing {package_name} for breaking check...") + + # install dependencies + self.install_dev_reqs(executable, args, package_dir) + + try: + install_into_venv( + executable, + [f"jsondiff=={JSONDIFF_VERSION}", "-e", BREAKING_CHECKER_PATH], + package_dir, + ) + except CalledProcessError as e: + logger.error(f"Failed to install dependencies for {package_name}: {e}") + results.append(1) + continue + + create_package_and_install( + distribution_directory=staging_directory, + target_setup=package_dir, + skip_install=False, + cache_dir=None, + work_dir=staging_directory, + force_create=False, + package_type="sdist", + pre_download_disabled=False, + python_executable=executable, + ) + + try: + cmd = [ + os.path.join(BREAKING_CHECKER_PATH, "detect_breaking_changes.py"), + "--target", + package_dir, + ] + check_call([executable] + cmd) + logger.info(f"No breaking changes detected for {package_name}.") + except CalledProcessError as e: + logger.error(f"Breaking changes detected for {package_name}: {e}") + results.append(1) + continue + + return max(results) if results else 0 diff --git a/eng/tools/azure-sdk-tools/azpysdk/main.py b/eng/tools/azure-sdk-tools/azpysdk/main.py index 13643a24ccd2..dd8fac51bf79 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/main.py +++ b/eng/tools/azure-sdk-tools/azpysdk/main.py @@ -28,6 +28,7 @@ from .verify_whl import verify_whl from .bandit import bandit from .verify_keywords import verify_keywords +from .breaking import breaking from ci_tools.logging import configure_logging, logger @@ -87,6 +88,7 @@ def build_parser() -> argparse.ArgumentParser: verify_whl().register(subparsers, [common]) bandit().register(subparsers, [common]) verify_keywords().register(subparsers, [common]) + breaking().register(subparsers, [common]) return parser From 96c1722783c8f26a6deb8d57744d4800b709c080 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:54:36 -0800 Subject: [PATCH 2/7] format --- eng/tools/azure-sdk-tools/azpysdk/breaking.py | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/tools/azure-sdk-tools/azpysdk/breaking.py b/eng/tools/azure-sdk-tools/azpysdk/breaking.py index 84f1b1370c0f..1f814298f1c4 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/breaking.py +++ b/eng/tools/azure-sdk-tools/azpysdk/breaking.py @@ -15,6 +15,7 @@ JSONDIFF_VERSION = "1.2.0" REPO_ROOT = discover_repo_root() + class breaking(Check): def __init__(self) -> None: super().__init__() From b6028d82108242e1bac787bc91eaab8210cc4761 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:58:28 -0800 Subject: [PATCH 3/7] clean --- eng/tools/azure-sdk-tools/azpysdk/breaking.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/breaking.py b/eng/tools/azure-sdk-tools/azpysdk/breaking.py index 1f814298f1c4..84e8af9f7780 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/breaking.py +++ b/eng/tools/azure-sdk-tools/azpysdk/breaking.py @@ -6,10 +6,9 @@ from subprocess import CalledProcessError, check_call from .Check import Check -from ci_tools.functions import install_into_venv, get_pip_command +from ci_tools.functions import install_into_venv from ci_tools.scenario.generation import create_package_and_install -from ci_tools.variables import discover_repo_root, in_ci, set_envvar_defaults -from ci_tools.environment_exclusions import is_check_enabled +from ci_tools.variables import discover_repo_root, set_envvar_defaults from ci_tools.logging import logger JSONDIFF_VERSION = "1.2.0" @@ -78,9 +77,7 @@ def run(self, args: argparse.Namespace) -> int: package_dir, ] check_call([executable] + cmd) - logger.info(f"No breaking changes detected for {package_name}.") except CalledProcessError as e: - logger.error(f"Breaking changes detected for {package_name}: {e}") results.append(1) continue From e8925b242ab6a54fb7a9486f832a6efed640f4ce Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:07:42 -0800 Subject: [PATCH 4/7] minor --- eng/tools/azure-sdk-tools/azpysdk/breaking.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/breaking.py b/eng/tools/azure-sdk-tools/azpysdk/breaking.py index 84e8af9f7780..9e7738e82700 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/breaking.py +++ b/eng/tools/azure-sdk-tools/azpysdk/breaking.py @@ -72,12 +72,14 @@ def run(self, args: argparse.Namespace) -> int: try: cmd = [ + executable, os.path.join(BREAKING_CHECKER_PATH, "detect_breaking_changes.py"), "--target", package_dir, ] - check_call([executable] + cmd) + check_call(cmd) except CalledProcessError as e: + logger.error(f"Breaking check failed for {package_name}: {e}") results.append(1) continue From 8b306e8ca1792d60a7a9cb91e3e623002229197c Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:15:49 -0800 Subject: [PATCH 5/7] edit readme for breaking --- scripts/breaking_changes_checker/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/breaking_changes_checker/README.md b/scripts/breaking_changes_checker/README.md index 3d1b03743ed4..15aff938e77d 100644 --- a/scripts/breaking_changes_checker/README.md +++ b/scripts/breaking_changes_checker/README.md @@ -7,6 +7,21 @@ The breaking changes tool compares the last stable/GA version of the library (if Add your package name to the `RUN_BREAKING_CHANGES_PACKAGES` found [here](https://github.com/Azure/azure-sdk-for-python/tree/main/scripts/breaking_changes_checker/breaking_changes_allowlist.py). +## Run locally with `azpysdk` + +**1) Install azpysdk:** + +`pip install -e eng/tools/azure-sdk-tools[build]` + +**2) Run the `breaking` check.** + +Here we run the breaking changes tool against azure-storage-blob, for example: + +```bash +cd ./sdk/storage/azure-storage-blob +azpysdk breaking . +``` + ## Run locally with tox **1) Install tox:** From 2ff21710ec4bc0a39ec8ccb64bf20265fffd4477 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:49:13 -0800 Subject: [PATCH 6/7] address comments --- eng/tools/azure-sdk-tools/azpysdk/breaking.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/breaking.py b/eng/tools/azure-sdk-tools/azpysdk/breaking.py index 9e7738e82700..23ceb2197544 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/breaking.py +++ b/eng/tools/azure-sdk-tools/azpysdk/breaking.py @@ -13,7 +13,7 @@ JSONDIFF_VERSION = "1.2.0" REPO_ROOT = discover_repo_root() - +BREAKING_CHECKER_PATH = os.path.join(REPO_ROOT, "scripts", "breaking_changes_checker") class breaking(Check): def __init__(self) -> None: @@ -36,7 +36,6 @@ def run(self, args: argparse.Namespace) -> int: results: List[int] = [] - BREAKING_CHECKER_PATH = os.path.join(REPO_ROOT, "scripts", "breaking_changes_checker") for parsed in targeted: package_dir = parsed.folder @@ -54,7 +53,7 @@ def run(self, args: argparse.Namespace) -> int: package_dir, ) except CalledProcessError as e: - logger.error(f"Failed to install dependencies for {package_name}: {e}") + logger.error(f"Failed to install jsondiff or breaking change checker while processing {package_name}: {e}") results.append(1) continue From 3f5a743ea498cce39b1461c54b7606caa4c729c7 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:02:58 -0800 Subject: [PATCH 7/7] FORMAT --- eng/tools/azure-sdk-tools/azpysdk/breaking.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eng/tools/azure-sdk-tools/azpysdk/breaking.py b/eng/tools/azure-sdk-tools/azpysdk/breaking.py index 23ceb2197544..39c736318989 100644 --- a/eng/tools/azure-sdk-tools/azpysdk/breaking.py +++ b/eng/tools/azure-sdk-tools/azpysdk/breaking.py @@ -15,6 +15,7 @@ REPO_ROOT = discover_repo_root() BREAKING_CHECKER_PATH = os.path.join(REPO_ROOT, "scripts", "breaking_changes_checker") + class breaking(Check): def __init__(self) -> None: super().__init__() @@ -36,7 +37,6 @@ def run(self, args: argparse.Namespace) -> int: results: List[int] = [] - for parsed in targeted: package_dir = parsed.folder package_name = parsed.name @@ -53,7 +53,9 @@ def run(self, args: argparse.Namespace) -> int: package_dir, ) except CalledProcessError as e: - logger.error(f"Failed to install jsondiff or breaking change checker while processing {package_name}: {e}") + logger.error( + f"Failed to install jsondiff or breaking change checker while processing {package_name}: {e}" + ) results.append(1) continue