Skip to content

Commit 880663f

Browse files
authored
Fix whl azypysdk env (#44065)
* fix whl environment for running in CI
1 parent d0fc0a6 commit 880663f

File tree

5 files changed

+140
-83
lines changed

5 files changed

+140
-83
lines changed

argfile.json

Lines changed: 0 additions & 22 deletions
This file was deleted.

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ def get_executable(self, isolate: bool, check_name: str, executable: str, packag
110110
return executable, staging_directory
111111

112112
def run_venv_command(
113-
self, executable: str, command: Sequence[str], cwd: str, check: bool = False, append_executable: bool = True
113+
self,
114+
executable: str,
115+
command: Sequence[str],
116+
cwd: str,
117+
check: bool = False,
118+
append_executable: bool = True,
119+
immediately_dump: bool = False,
114120
) -> subprocess.CompletedProcess[str]:
115121
"""Run a command in the given virtual environment.
116122
- Prepends the virtual environment's bin directory to the PATH environment variable (if one exists)
@@ -149,9 +155,9 @@ def run_venv_command(
149155
logger.debug(f"VIRTUAL_ENV: {env['VIRTUAL_ENV']}.")
150156
logger.debug(f"PATH : {env['PATH']}.")
151157

152-
result = subprocess.run(
153-
cmd_to_run, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=check, env=env
154-
)
158+
s_out = None if immediately_dump else subprocess.PIPE
159+
s_err = None if immediately_dump else subprocess.PIPE
160+
result = subprocess.run(cmd_to_run, cwd=cwd, stdout=s_out, stderr=s_err, text=True, check=check, env=env)
155161

156162
return result
157163

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from .ruff import ruff
2626
from .verifytypes import verifytypes
2727
from .verify_sdist import verify_sdist
28+
from .whl import whl
2829
from .verify_whl import verify_whl
2930
from .bandit import bandit
3031
from .verify_keywords import verify_keywords
@@ -86,6 +87,7 @@ def build_parser() -> argparse.ArgumentParser:
8687
ruff().register(subparsers, [common])
8788
verifytypes().register(subparsers, [common])
8889
verify_sdist().register(subparsers, [common])
90+
whl().register(subparsers, [common])
8991
verify_whl().register(subparsers, [common])
9092
bandit().register(subparsers, [common])
9193
verify_keywords().register(subparsers, [common])
Lines changed: 123 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
11
import argparse
2-
import tempfile
32
import os
4-
from typing import Optional, List, Any
53
import sys
6-
from subprocess import run
4+
from subprocess import CalledProcessError
5+
from typing import List, Optional
76

87
from .Check import Check
98

109
from ci_tools.functions import is_error_code_5_allowed, install_into_venv
11-
from ci_tools.variables import set_envvar_defaults
12-
from ci_tools.parsing import ParsedSetup
1310
from ci_tools.scenario.generation import create_package_and_install
11+
from ci_tools.variables import discover_repo_root, set_envvar_defaults
1412
from ci_tools.logging import logger
1513

14+
REPO_ROOT = discover_repo_root()
15+
16+
PACKAGING_REQUIREMENTS = [
17+
"wheel==0.45.1",
18+
"packaging==24.2",
19+
"urllib3==2.2.3",
20+
"tomli==2.2.1",
21+
"build==1.2.2.post1",
22+
"pkginfo==1.12.1.2",
23+
]
24+
25+
TEST_TOOLS_REQUIREMENTS = os.path.join(REPO_ROOT, "eng/test_tools.txt")
26+
1627

1728
class whl(Check):
1829
def __init__(self) -> None:
@@ -27,64 +38,120 @@ def register(
2738
parents = parent_parsers or []
2839
p = subparsers.add_parser("whl", parents=parents, help="Run the whl check")
2940
p.set_defaults(func=self.run)
30-
# TODO add mark_args, and other parameters
41+
p.add_argument(
42+
"--pytest-args",
43+
nargs=argparse.REMAINDER,
44+
help="Additional arguments forwarded to pytest.",
45+
)
3146

3247
def run(self, args: argparse.Namespace) -> int:
3348
"""Run the whl check command."""
3449
logger.info("Running whl check...")
3550

36-
set_envvar_defaults()
51+
set_envvar_defaults({"PROXY_URL": "http://localhost:5001"})
3752

3853
targeted = self.get_targeted_directories(args)
54+
if not targeted:
55+
logger.warning("No target packages discovered for whl check.")
56+
return 0
3957

40-
results: List[int] = []
58+
overall_result = 0
4159

4260
for parsed in targeted:
43-
pkg = parsed.folder
44-
executable, staging_directory = self.get_executable(args.isolate, args.command, sys.executable, pkg)
45-
46-
logger.info(f"Invoking check with {executable}")
47-
48-
self.install_dev_reqs(executable, args, pkg)
49-
50-
create_package_and_install(
51-
distribution_directory=staging_directory,
52-
target_setup=pkg,
53-
skip_install=False,
54-
cache_dir=None,
55-
work_dir=staging_directory,
56-
force_create=False,
57-
package_type="wheel",
58-
pre_download_disabled=False,
59-
python_executable=executable,
60-
)
61-
62-
# TODO: split sys.argv[1:] on -- and pass in everything after the -- as additional arguments
63-
# TODO: handle mark_args
64-
logger.info(f"Invoke pytest for {pkg}")
65-
exit_code = run(
66-
[executable, "-m", "pytest", "."]
67-
+ [
68-
"-rsfE",
69-
f"--junitxml={pkg}/test-junit-{args.command}.xml",
70-
"--verbose",
71-
"--cov-branch",
72-
"--durations=10",
73-
"--ignore=azure",
74-
"--ignore-glob=.venv*",
75-
"--ignore=build",
76-
"--ignore=.eggs",
77-
"--ignore=samples",
78-
],
79-
cwd=pkg,
80-
).returncode
81-
82-
if exit_code != 0:
83-
if exit_code == 5 and is_error_code_5_allowed(parsed.folder, parsed.name):
84-
logger.info("Exit code 5 is allowed, continuing execution.")
85-
else:
86-
logger.info(f"pytest failed with exit code {exit_code}.")
87-
results.append(exit_code)
88-
89-
# final result is the worst case of all the results
90-
return max(results)
61+
package_dir = parsed.folder
62+
package_name = parsed.name
63+
64+
executable, staging_directory = self.get_executable(args.isolate, args.command, sys.executable, package_dir)
65+
logger.info(f"Processing {package_name} using interpreter {executable}")
66+
67+
try:
68+
self._install_common_requirements(executable, package_dir)
69+
self.install_dev_reqs(executable, args, package_dir)
70+
except CalledProcessError as exc:
71+
logger.error(f"Failed to install dependencies for {package_name}: {exc}")
72+
overall_result = max(overall_result, exc.returncode or 1)
73+
continue
74+
75+
try:
76+
create_package_and_install(
77+
distribution_directory=staging_directory,
78+
target_setup=package_dir,
79+
skip_install=False,
80+
cache_dir=None,
81+
work_dir=staging_directory,
82+
force_create=False,
83+
package_type="wheel",
84+
pre_download_disabled=False,
85+
python_executable=executable,
86+
)
87+
except CalledProcessError as exc:
88+
logger.error(f"Failed to build/install wheel for {package_name}: {exc}")
89+
overall_result = max(overall_result, exc.returncode or 1)
90+
continue
91+
92+
pytest_args = self._build_pytest_args(package_dir, args)
93+
pytest_command = ["-m", "pytest", *pytest_args]
94+
pytest_result = self.run_venv_command(executable, pytest_command, cwd=package_dir, immediately_dump=True)
95+
96+
if pytest_result.returncode != 0:
97+
if pytest_result.returncode == 5 and is_error_code_5_allowed(package_dir, package_name):
98+
logger.info(
99+
"pytest exited with code 5 for %s, which is allowed for management or opt-out packages.",
100+
package_name,
101+
)
102+
# Align with tox: skip coverage when tests are skipped entirely
103+
continue
104+
105+
logger.error(f"pytest failed for {package_name} with exit code {pytest_result.returncode}.")
106+
continue
107+
108+
coverage_command = [
109+
os.path.join(REPO_ROOT, "eng/tox/run_coverage.py"),
110+
"-t",
111+
package_dir,
112+
"-r",
113+
REPO_ROOT,
114+
]
115+
coverage_result = self.run_venv_command(executable, coverage_command, cwd=package_dir)
116+
if coverage_result.returncode != 0:
117+
logger.error(
118+
f"Coverage generation failed for {package_name} with exit code {coverage_result.returncode}."
119+
)
120+
if coverage_result.stdout:
121+
logger.error(coverage_result.stdout)
122+
if coverage_result.stderr:
123+
logger.error(coverage_result.stderr)
124+
overall_result = max(overall_result, coverage_result.returncode)
125+
126+
return overall_result
127+
128+
def _install_common_requirements(self, executable: str, package_dir: str) -> None:
129+
install_into_venv(executable, PACKAGING_REQUIREMENTS, package_dir)
130+
131+
if os.path.exists(TEST_TOOLS_REQUIREMENTS):
132+
install_into_venv(executable, ["-r", TEST_TOOLS_REQUIREMENTS], package_dir)
133+
else:
134+
logger.warning(f"Test tools requirements file not found at {TEST_TOOLS_REQUIREMENTS}.")
135+
136+
def _build_pytest_args(self, package_dir: str, args: argparse.Namespace) -> List[str]:
137+
log_level = os.getenv("PYTEST_LOG_LEVEL", "51")
138+
junit_path = os.path.join(package_dir, f"test-junit-{args.command}.xml")
139+
140+
default_args = [
141+
"-rsfE",
142+
f"--junitxml={junit_path}",
143+
"--verbose",
144+
"--cov-branch",
145+
"--durations=10",
146+
"--ignore=azure",
147+
"--ignore=.tox",
148+
"--ignore-glob=.venv*",
149+
"--ignore=build",
150+
"--ignore=.eggs",
151+
"--ignore=samples",
152+
f"--log-cli-level={log_level}",
153+
]
154+
155+
additional = args.pytest_args if args.pytest_args else []
156+
157+
return [*default_args, *additional, package_dir]

eng/tools/azure-sdk-tools/ci_tools/variables.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,14 @@ def set_environment_from_dictionary(settings: Dict[str, str]) -> None:
121121
os.environ.setdefault(key, value)
122122

123123

124-
def set_envvar_defaults() -> None:
124+
def set_envvar_defaults(settings: Optional[Dict[str, str]] = None) -> None:
125125
"""
126126
Sets default environment variables for any given process to our default dictionary.
127127
Args:
128128
settings (Dict[str, str]): A dictionary of environment variable names and their default values.
129129
"""
130130
set_environment_from_dictionary(DEFAULT_ENVIRONMENT_VARIABLES)
131+
132+
if settings:
133+
# this will override any defaults set prior in the case of override
134+
set_environment_from_dictionary(settings)

0 commit comments

Comments
 (0)