Skip to content

Commit c3af616

Browse files
msyycCopilot
andauthored
Skip dev_requirements/sdist install for changelog generation when using apistub (#48090)
* Skip dev_requirements/sdist install for changelog when using apistub The apistub path in the breaking-change check builds the code report via static analysis and never imports the target package, so installing the package's dev_requirements.txt and building/installing the sdist is unnecessary. This also avoids a local wheel-build failure (WinError 183 from stale in-source build/ dirs) during changelog generation, which always uses --use-apistub. Guard both steps behind 'not use_apistub' and add tests. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix indentation syntax error in test_breaking.py --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 6216970 commit c3af616

2 files changed

Lines changed: 121 additions & 13 deletions

File tree

eng/tools/azure-sdk-tools/azpysdk/breaking.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,15 @@ def run(self, args: argparse.Namespace) -> int:
128128
)
129129
logger.info(f"Processing {package_name} for breaking check...")
130130

131-
# install dependencies
132-
self.install_dev_reqs(executable, args, package_dir)
131+
use_apistub = getattr(args, "use_apistub", False)
132+
133+
# The apistub path builds the code report via static analysis (apistub generates
134+
# api.md and it is converted to a report); the target package is never imported.
135+
# So installing dev requirements and building/installing the package sdist is only
136+
# needed for the default (import-based) path. Skip both in apistub mode.
137+
if not use_apistub:
138+
# install dependencies
139+
self.install_dev_reqs(executable, args, package_dir)
133140

134141
try:
135142
install_into_venv(
@@ -144,17 +151,18 @@ def run(self, args: argparse.Namespace) -> int:
144151
results.append(1)
145152
continue
146153

147-
create_package_and_install(
148-
distribution_directory=staging_directory,
149-
target_setup=package_dir,
150-
skip_install=False,
151-
cache_dir=None,
152-
work_dir=staging_directory,
153-
force_create=False,
154-
package_type="sdist",
155-
pre_download_disabled=False,
156-
python_executable=executable,
157-
)
154+
if not use_apistub:
155+
create_package_and_install(
156+
distribution_directory=staging_directory,
157+
target_setup=package_dir,
158+
skip_install=False,
159+
cache_dir=None,
160+
work_dir=staging_directory,
161+
force_create=False,
162+
package_type="sdist",
163+
pre_download_disabled=False,
164+
python_executable=executable,
165+
)
158166

159167
try:
160168
cmd = [
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import argparse
2+
import os
3+
import sys
4+
5+
from unittest.mock import MagicMock, patch
6+
7+
from azpysdk.breaking import breaking
8+
9+
10+
def _make_args(use_apistub=False, changelog=True, isolate=False):
11+
"""Build an argparse.Namespace with every attribute breaking.run() reads."""
12+
return argparse.Namespace(
13+
target=".",
14+
isolate=isolate,
15+
command="breaking",
16+
service=None,
17+
target_module=None,
18+
in_venv=False,
19+
stable_version=None,
20+
changelog=changelog,
21+
code_report=False,
22+
source_report=None,
23+
target_report=None,
24+
latest_pypi_version=False,
25+
use_apistub=use_apistub,
26+
debug=False,
27+
)
28+
29+
30+
def _run_breaking(args, tmp_path):
31+
"""Invoke breaking.run() with all external side effects mocked out.
32+
33+
Returns a dict of the mocks that assertions can inspect.
34+
"""
35+
chk = breaking()
36+
staging = str(tmp_path / "staging")
37+
os.makedirs(staging, exist_ok=True)
38+
fake_parsed = MagicMock()
39+
fake_parsed.folder = str(tmp_path)
40+
fake_parsed.name = "azure-core"
41+
42+
original_cwd = os.getcwd()
43+
with patch("azpysdk.breaking.set_envvar_defaults"), patch(
44+
"azpysdk.breaking.install_into_venv"
45+
) as install_into_venv, patch("azpysdk.breaking.create_package_and_install") as create_package_and_install, patch(
46+
"azpysdk.breaking.check_call"
47+
) as check_call, patch.object(
48+
chk, "get_targeted_directories", return_value=[fake_parsed]
49+
), patch.object(
50+
chk, "get_executable", return_value=(sys.executable, staging)
51+
), patch.object(
52+
chk, "install_dev_reqs"
53+
) as install_dev_reqs:
54+
try:
55+
result = chk.run(args)
56+
finally:
57+
os.chdir(original_cwd)
58+
59+
return {
60+
"result": result,
61+
"install_dev_reqs": install_dev_reqs,
62+
"install_into_venv": install_into_venv,
63+
"create_package_and_install": create_package_and_install,
64+
"check_call": check_call,
65+
}
66+
67+
68+
class TestBreakingUseApistubGuard:
69+
"""The apistub path builds the report via static analysis, so it must not install the
70+
package's dev requirements nor build/install the target package sdist."""
71+
72+
def test_use_apistub_skips_dev_reqs_and_sdist_install(self, tmp_path):
73+
mocks = _run_breaking(_make_args(use_apistub=True), tmp_path)
74+
75+
# The failing/expensive steps are skipped in apistub mode.
76+
mocks["install_dev_reqs"].assert_not_called()
77+
mocks["create_package_and_install"].assert_not_called()
78+
79+
# jsondiff + breaking-change checker are still required by the detector.
80+
mocks["install_into_venv"].assert_called_once()
81+
82+
# The detector still runs, and it receives --use-apistub.
83+
mocks["check_call"].assert_called_once()
84+
detector_cmd = mocks["check_call"].call_args.args[0]
85+
assert "--use-apistub" in detector_cmd
86+
assert mocks["result"] == 0
87+
88+
def test_default_path_installs_dev_reqs_and_sdist(self, tmp_path):
89+
mocks = _run_breaking(_make_args(use_apistub=False), tmp_path)
90+
91+
# The import-based path needs the package (and its deps) installed.
92+
mocks["install_dev_reqs"].assert_called_once()
93+
mocks["create_package_and_install"].assert_called_once()
94+
95+
mocks["install_into_venv"].assert_called_once()
96+
97+
mocks["check_call"].assert_called_once()
98+
detector_cmd = mocks["check_call"].call_args.args[0]
99+
assert "--use-apistub" not in detector_cmd
100+
assert mocks["result"] == 0

0 commit comments

Comments
 (0)