Skip to content

Commit 2f59429

Browse files
scbeddCopilotmccoyp
authored
Configure create_package_and_install to be more responsive to logging settings (#42862)
* update build_package function (heavily used by create_package_and_install) to be much less verbose by default. We do not see to see all build output for every package when it's just standard assembly. we have verify_sdist and verify_whl, and beyond that devs should validate the contents of their packages. * in error cases, we should still see error output, but otherwise, users will have to set LOG_LEVEL environment variable at runtime to see the raw build logs --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: McCoy Patiño <[email protected]>
1 parent a23cbb4 commit 2f59429

File tree

7 files changed

+84
-36
lines changed

7 files changed

+84
-36
lines changed

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ci_tools.variables import DEFAULT_BUILD_ID, str_to_bool, discover_repo_root, get_artifact_directory
99
from ci_tools.versioning.version_shared import set_version_py, set_dev_classifier
1010
from ci_tools.versioning.version_set_dev import get_dev_version, format_build_id
11-
11+
from ci_tools.logging import logger, configure_logging, run_logged
1212

1313
def build_package() -> None:
1414
parser = argparse.ArgumentParser(
@@ -39,6 +39,7 @@ def build_package() -> None:
3939
)
4040

4141
args = parser.parse_args()
42+
configure_logging(args)
4243

4344
target_package = ParsedSetup.from_path(args.package_folder)
4445
artifact_directory = get_artifact_directory(args.distribution_directory)
@@ -146,6 +147,8 @@ def build() -> None:
146147

147148
args = parser.parse_args()
148149

150+
configure_logging(args)
151+
149152
repo_root = discover_repo_root(args.repo)
150153

151154
if args.service and args.service != "auto":
@@ -154,7 +157,7 @@ def build() -> None:
154157
else:
155158
target_dir = repo_root
156159

157-
logging.debug(
160+
logger.debug(
158161
f"Searching for packages starting from {target_dir} with glob string {args.glob_string} and package filter {args.package_filter_string}"
159162
)
160163

@@ -202,7 +205,7 @@ def build_packages(
202205
enable_wheel: bool = True,
203206
enable_sdist: bool = True
204207
):
205-
logging.log(level=logging.INFO, msg=f"Generating {targeted_packages} using python{sys.version}")
208+
logger.info(f"Generating {targeted_packages} using python{sys.version}")
206209

207210
for package_root in targeted_packages:
208211
setup_parsed = ParsedSetup.from_path(package_root)
@@ -218,7 +221,7 @@ def build_packages(
218221

219222
new_version = get_dev_version(setup_parsed.version, build_id)
220223

221-
logging.log(level=logging.DEBUG, msg=f"{setup_parsed.name}: {setup_parsed.version} -> {new_version}")
224+
logger.debug(f"{setup_parsed.name}: {setup_parsed.version} -> {new_version}")
222225

223226
set_version_py(setup_parsed.setup_filename, new_version)
224227
set_dev_classifier(setup_parsed.setup_filename, new_version)
@@ -237,6 +240,8 @@ def create_package(
237240
dist = get_artifact_directory(dest_folder)
238241
setup_parsed = ParsedSetup.from_path(setup_directory_or_file)
239242

243+
should_log_build_output = logger.getEffectiveLevel() <= logging.DEBUG
244+
240245
if setup_parsed.is_pyproject:
241246
# when building with pyproject, we will use `python -m build` to build the package
242247
# -n argument will not use an isolated environment, which means the current environment must have all the dependencies of the package installed, to successfully
@@ -247,14 +252,14 @@ def create_package(
247252
# we assume the presence of `wheel`, `build`, `setuptools>=61.0.0`
248253
pip_output = get_pip_list_output(sys.executable)
249254
necessary_install_requirements = [req for req in setup_parsed.requires if parse_require(req).name not in pip_output.keys()]
250-
run([sys.executable, "-m", "pip", "install", *necessary_install_requirements], cwd=setup_parsed.folder)
251-
run([sys.executable, "-m", "build", f"-n{'s' if enable_sdist else ''}{'w' if enable_wheel else ''}", "-o", dist], cwd=setup_parsed.folder, check=True)
255+
run_logged([sys.executable, "-m", "pip", "install", *necessary_install_requirements], cwd=setup_parsed.folder, check=False, should_stream_to_console=should_log_build_output)
256+
run_logged([sys.executable, "-m", "build", f"-n{'s' if enable_sdist else ''}{'w' if enable_wheel else ''}", "-o", dist], cwd=setup_parsed.folder, check=True, should_stream_to_console=should_log_build_output)
252257
else:
253258
if enable_wheel:
254259
if setup_parsed.ext_modules:
255-
run([sys.executable, "-m", "cibuildwheel", "--output-dir", dist], cwd=setup_parsed.folder, check=True)
260+
run_logged([sys.executable, "-m", "cibuildwheel", "--output-dir", dist], cwd=setup_parsed.folder, check=True, should_stream_to_console=should_log_build_output)
256261
else:
257-
run([sys.executable, "setup.py", "bdist_wheel", "-d", dist], cwd=setup_parsed.folder, check=True)
262+
run_logged([sys.executable, "setup.py", "bdist_wheel", "-d", dist], cwd=setup_parsed.folder, check=True, should_stream_to_console=should_log_build_output)
258263

259264
if enable_sdist:
260-
run([sys.executable, "setup.py", "sdist", "-d", dist], cwd=setup_parsed.folder, check=True)
265+
run_logged([sys.executable, "setup.py", "sdist", "-d", dist], cwd=setup_parsed.folder, check=True, should_stream_to_console=should_log_build_output)

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from ci_tools.variables import discover_repo_root, DEV_BUILD_IDENTIFIER, str_to_bool
1414
from ci_tools.parsing import ParsedSetup, get_config_setting, get_pyproject
1515
from pypi_tools.pypi import PyPIClient
16+
from ci_tools.logging import logger
1617

1718
import os, sys, platform, glob, re, logging
1819
from typing import List, Any, Optional, Tuple
@@ -222,25 +223,25 @@ def discover_targeted_packages(
222223

223224
# glob the starting package set
224225
collected_packages = glob_packages(glob_string, target_root_dir)
225-
logging.info(
226+
logger.debug(
226227
f'Results for glob_string "{glob_string}" and root directory "{target_root_dir}" are: {collected_packages}'
227228
)
228229

229230
# apply the additional contains filter
230231
collected_packages = [pkg for pkg in collected_packages if additional_contains_filter in pkg]
231-
logging.info(f'Results after additional contains filter: "{additional_contains_filter}" {collected_packages}')
232+
logger.debug(f'Results after additional contains filter: "{additional_contains_filter}" {collected_packages}')
232233

233234
# filter for compatibility, this means excluding a package that doesn't support py36 when we are running a py36 executable
234235
if compatibility_filter:
235236
collected_packages = apply_compatibility_filter(collected_packages)
236-
logging.info(f"Results after compatibility filter: {collected_packages}")
237+
logger.debug(f"Results after compatibility filter: {collected_packages}")
237238

238239
if not include_inactive:
239240
collected_packages = apply_inactive_filter(collected_packages)
240241

241242
# Apply filter based on filter type. for e.g. Docs, Regression, Management
242243
collected_packages = apply_business_filter(collected_packages, filter_type)
243-
logging.info(f"Results after business filter: {collected_packages}")
244+
logger.debug(f"Results after business filter: {collected_packages}")
244245

245246
return sorted(collected_packages)
246247

@@ -1037,6 +1038,7 @@ def get_pip_command(python_exe: Optional[str] = None) -> List[str]:
10371038
return [python_exe if python_exe else sys.executable, "-m", "pip"]
10381039

10391040

1041+
10401042
def is_error_code_5_allowed(target_pkg: str, pkg_name: str):
10411043
"""
10421044
Determine if error code 5 (no pytests run) is allowed for the given package.

eng/tools/azure-sdk-tools/ci_tools/logging/__init__.py

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import logging
2-
from logging import Logger
3-
from ci_tools.variables import get_log_directory, in_ci
41
import os
52
import datetime
6-
from subprocess import run
3+
import subprocess
74
import argparse
5+
import logging
86

9-
LOGLEVEL = getattr(logging, os.environ.get("LOGLEVEL", "INFO").upper())
7+
from logging import Logger
108

9+
from ci_tools.variables import get_log_directory, in_ci
10+
11+
LOGLEVEL = getattr(logging, os.environ.get("LOGLEVEL", "INFO").upper())
1112
logger = logging.getLogger("azure-sdk-tools")
1213

1314
def configure_logging(
@@ -73,11 +74,43 @@ def get_log_file(prefix: str = "") -> str:
7374
return logfile
7475

7576

76-
def run_logged(*args, prefix="", **kwargs):
77+
def run_logged_to_file(*args, prefix="", **kwargs):
7778
logfile = get_log_file(prefix)
7879

7980
with open(logfile, "w") as log_output:
80-
run(*args, **kwargs, stdout=log_output, stderr=log_output)
81+
subprocess.run(*args, **kwargs, stdout=log_output, stderr=log_output)
8182

8283

83-
__all__ = ["initialize_logger", "run_logged"]
84+
def run_logged(cmd: list[str], cwd: str, check: bool, should_stream_to_console: bool):
85+
"""
86+
Runs a command, logging output to subprocess.PIPE or streaming live to console based on log level.
87+
88+
Regardless of `should_stream_to_console`, if the command fails, the captured output will be logged.
89+
"""
90+
try:
91+
if should_stream_to_console:
92+
# Stream live, no capturing
93+
return subprocess.run(
94+
cmd,
95+
cwd=cwd,
96+
check=check,
97+
text=True,
98+
stderr=subprocess.STDOUT
99+
)
100+
else:
101+
# Capture merged output but don't print unless there's a failure
102+
return subprocess.run(
103+
cmd,
104+
cwd=cwd,
105+
check=check,
106+
text=True,
107+
stdout=subprocess.PIPE,
108+
stderr=subprocess.STDOUT
109+
)
110+
except subprocess.CalledProcessError as e:
111+
logger.error(f"Command failed: {' '.join(cmd)}")
112+
if e.stdout:
113+
logger.error(f"\n{e.stdout.strip()}")
114+
raise
115+
116+
__all__ = ["initialize_logger", "run_logged_to_file", "run_logged"]

eng/tools/azure-sdk-tools/ci_tools/scenario/generation.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from ci_tools.functions import get_package_from_repo_or_folder, find_whl, get_pip_list_output, pytest
2121
from .managed_virtual_env import ManagedVirtualEnv
2222

23+
from ci_tools.logging import logger
2324

2425
def prepare_environment(package_folder: str, venv_directory: str, env_name: str) -> str:
2526
"""
@@ -84,7 +85,7 @@ def create_package_and_install(
8485
shutil.copy(built_pkg_path, distribution_directory)
8586

8687
if skip_install:
87-
logging.info("Flag to skip install whl is passed. Skipping package installation")
88+
logger.info("Flag to skip install whl is passed. Skipping package installation")
8889
else:
8990
for built_package in discovered_packages:
9091
if os.getenv("PREBUILT_WHEEL_DIR") is not None and not force_create:
@@ -93,21 +94,21 @@ def create_package_and_install(
9394
if os.path.isfile(package_path):
9495
built_pkg_path = package_path
9596

96-
logging.info("Installing {w} from directory".format(w=built_package))
97+
logger.info("Installing {w} from directory".format(w=built_package))
9798
# it does't exist, so we need to error out
9899
else:
99-
logging.error("{w} not present in the prebuilt package directory. Exiting.".format(w=built_package))
100+
logger.error("{w} not present in the prebuilt package directory. Exiting.".format(w=built_package))
100101
exit(1)
101102
else:
102103
built_pkg_path = os.path.abspath(os.path.join(distribution_directory, built_package))
103-
logging.info("Installing {w} from fresh built package.".format(w=built_package))
104+
logger.info("Installing {w} from fresh built package.".format(w=built_package))
104105

105106
if not pre_download_disabled:
106107
requirements = ParsedSetup.from_path(os.path.join(os.path.abspath(target_setup))).requires
107108
azure_requirements = [req.split(";")[0] for req in requirements if req.startswith("azure-")]
108109

109110
if azure_requirements:
110-
logging.info(
111+
logger.debug(
111112
"Found {} azure requirement(s): {}".format(len(azure_requirements), azure_requirements)
112113
)
113114

@@ -130,7 +131,7 @@ def create_package_and_install(
130131
addition_necessary = True
131132
# get all installed packages
132133
installed_pkgs = get_pip_list_output(python_exe)
133-
logging.info("Installed packages: {}".format(installed_pkgs))
134+
logger.debug("Installed packages: {}".format(installed_pkgs))
134135

135136
# parse the specifier
136137
requirement = parse_require(req)
@@ -180,11 +181,11 @@ def create_package_and_install(
180181
commands.extend(commands_options)
181182

182183
if work_dir and os.path.exists(work_dir):
183-
logging.info("Executing command from {0}:{1}".format(work_dir, commands))
184+
logger.info("Executing command from {0}:{1}".format(work_dir, commands))
184185
subprocess.check_call(commands, cwd=work_dir)
185186
else:
186187
subprocess.check_call(commands)
187-
logging.info("Installed {w}".format(w=built_package))
188+
logger.info("Installed {w}".format(w=built_package))
188189

189190

190191
def replace_dev_reqs(file: str, pkg_root: str, wheel_dir: Optional[str]) -> None:
@@ -238,7 +239,7 @@ def discover_packages(
238239
pkg = ParsedSetup.from_path(setup_path)
239240

240241
if not packages:
241-
logging.error(
242+
logger.error(
242243
"Package is missing in prebuilt directory {0} for package {1} and version {2}".format(
243244
os.getenv("PREBUILT_WHEEL_DIR"), pkg.name, pkg.version
244245
)
@@ -363,7 +364,7 @@ def build_and_discover_package(setup_path: str, dist_dir: str, target_setup: str
363364
]
364365

365366
if not in_ci():
366-
logging.info("Cleaning up build directories and files")
367+
logger.info("Cleaning up build directories and files")
367368
cleanup_build_artifacts(target_setup)
368369
return prebuilt_packages
369370

eng/tools/azure-sdk-tools/ci_tools/versioning/version_shared.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from ci_tools.functions import discover_targeted_packages
2222
from ci_tools.parsing import ParsedSetup, get_version_py, VERSION_REGEX
2323
from ci_tools.variables import discover_repo_root
24+
from ci_tools.logging import logger
2425

2526
from subprocess import run
2627

@@ -80,7 +81,7 @@ def set_version_py(setup_path, new_version):
8081
version_py_location = get_version_py(setup_path)
8182

8283
if not version_py_location:
83-
logging.error("No version.py file found in {}".format(setup_path))
84+
logger.error("No version.py file found in {}".format(setup_path))
8485
sys.exit(1)
8586

8687
version_contents = ""

eng/tox/create_package_and_install.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
# can be successfully tested from within a tox environment.
1111

1212
import argparse
13-
import logging
14-
15-
logging.getLogger().setLevel(logging.INFO)
1613

14+
from ci_tools.logging import configure_logging
1715
from ci_tools.scenario.generation import create_package_and_install
1816

1917
if __name__ == "__main__":
@@ -82,6 +80,10 @@
8280

8381
args = parser.parse_args()
8482

83+
# none of the args will apply to logging, but the LOGLEVEL env var will
84+
# so we can just pass the args object directly to configure_logging to meet the needs of the function
85+
configure_logging(args)
86+
8587
create_package_and_install(
8688
distribution_directory=args.distribution_directory,
8789
target_setup=args.target_setup,
@@ -94,4 +96,4 @@
9496
)
9597

9698

97-
99+

scripts/devops_tasks/dispatch_tox.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import logging
1616
from tox_harness import prep_and_run_tox
1717
from ci_tools.functions import discover_targeted_packages
18+
from ci_tools.logging import configure_logging
19+
1820

1921
logging.getLogger().setLevel(logging.INFO)
2022

@@ -114,6 +116,8 @@
114116

115117
args = parser.parse_args()
116118

119+
configure_logging(args)
120+
117121
# We need to support both CI builds of everything and individual service
118122
# folders. This logic allows us to do both.
119123
if args.service and args.service != "auto":

0 commit comments

Comments
 (0)