From 0feee4824c641d7023f4a0d0a45f7aa51de1280c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Mon, 29 Sep 2025 17:48:58 +0200 Subject: [PATCH] =?UTF-8?q?[stable-2.14]=20=F0=9F=A7=AA=20Pin=20codecov-cl?= =?UTF-8?q?i=20deptree=20used=20in=20AZP=20(#85903)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use codecov-cli for uploads (#85386) ci_coverage ci_complete (cherry picked from commit 4a03ccbd41e3ce506fca98488a9e2d25f2c9c859) * 🧪 Pin codecov-cli deptree used in AZP PR #85888 `codecov-cli == 11.0.3` allows `click == 8.3.0` in its deps but the latter causes commit auto-discovery breakage in the former. With https://github.com/getsentry/prevent-cli/pull/95, `codecov-cli == 11.2.3` excludes this version so this patch updates the requirement to that. To prevent this from happening again, the change also makes use of a pip constraint file that pins the entire dependency tree to concrete versions. The constraint file is managed by `pip-tools`. Refs: * https://github.com/getsentry/prevent-cli/pull/95 * https://github.com/pallets/click/issues/3066 ci_coverage ci_complete (cherry picked from commit 1e572ba5cc80753d543af338a16cd9fa8f747907) * Unignore `publish-codecov.py` sanity violations --------- Co-authored-by: Matt Clay --- .../scripts/dependencies/.pip-tools.toml | 6 ++ .../scripts/dependencies/codecov.in | 1 + .../scripts/dependencies/codecov.txt | 18 ++++ .azure-pipelines/scripts/publish-codecov.py | 85 +++++++++++++------ test/sanity/ignore.txt | 1 - 5 files changed, 83 insertions(+), 28 deletions(-) create mode 100644 .azure-pipelines/scripts/dependencies/.pip-tools.toml create mode 100644 .azure-pipelines/scripts/dependencies/codecov.in create mode 100644 .azure-pipelines/scripts/dependencies/codecov.txt diff --git a/.azure-pipelines/scripts/dependencies/.pip-tools.toml b/.azure-pipelines/scripts/dependencies/.pip-tools.toml new file mode 100644 index 00000000000..20558ff7478 --- /dev/null +++ b/.azure-pipelines/scripts/dependencies/.pip-tools.toml @@ -0,0 +1,6 @@ +[tool.pip-tools] +allow-unsafe = true # weird outdated default +annotation-style = "line" # put the source tracking comments inline +generate-hashes = false # pip bug https://github.com/pypa/pip/issues/9243 +resolver = "backtracking" # modern depresolver +strip-extras = true # so that output files are true pip constraints diff --git a/.azure-pipelines/scripts/dependencies/codecov.in b/.azure-pipelines/scripts/dependencies/codecov.in new file mode 100644 index 00000000000..11750996445 --- /dev/null +++ b/.azure-pipelines/scripts/dependencies/codecov.in @@ -0,0 +1 @@ +codecov-cli diff --git a/.azure-pipelines/scripts/dependencies/codecov.txt b/.azure-pipelines/scripts/dependencies/codecov.txt new file mode 100644 index 00000000000..9012189437f --- /dev/null +++ b/.azure-pipelines/scripts/dependencies/codecov.txt @@ -0,0 +1,18 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --annotation-style=line --output-file=codecov.txt --strip-extras codecov.in +# +certifi==2025.8.3 # via requests, sentry-sdk +charset-normalizer==3.4.3 # via requests +click==8.2.1 # via codecov-cli +codecov-cli==11.2.3 # via -r codecov.in +idna==3.10 # via requests +ijson==3.4.0 # via codecov-cli +pyyaml==6.0.2 # via codecov-cli +requests==2.32.5 # via responses +responses==0.21.0 # via codecov-cli +sentry-sdk==2.38.0 # via codecov-cli +test-results-parser==0.5.4 # via codecov-cli +urllib3==2.5.0 # via requests, responses, sentry-sdk diff --git a/.azure-pipelines/scripts/publish-codecov.py b/.azure-pipelines/scripts/publish-codecov.py index f2bc4b84b38..b36bef76467 100755 --- a/.azure-pipelines/scripts/publish-codecov.py +++ b/.azure-pipelines/scripts/publish-codecov.py @@ -8,11 +8,15 @@ Python coverage, as well as PowerShell and Python stubs can all be uploaded. import argparse import dataclasses import pathlib -import shutil +import shlex import subprocess import tempfile import typing as t -import urllib.request +import venv + + +SCRIPTS_DIR = pathlib.Path(__file__).parent.resolve() +DEPS_DIR = SCRIPTS_DIR / 'dependencies' @dataclasses.dataclass(frozen=True) @@ -42,6 +46,36 @@ def parse_args() -> Args: return Args(**kwargs) +def run(*args: str | pathlib.Path) -> None: + cmd = [str(arg) for arg in args] + print(f'==> {shlex.join(cmd)}', flush=True) + subprocess.run(cmd, check=True) + + +def install_codecov(dest: pathlib.Path) -> pathlib.Path: + """Populate a transitively pinned venv with ``codecov-cli``.""" + requirement_file = DEPS_DIR / 'codecov.in' + constraint_file = requirement_file.with_suffix('.txt') + + venv_dir = dest / 'venv' + python_bin = venv_dir / 'bin' / 'python' + codecov_bin = venv_dir / 'bin' / 'codecovcli' + + venv.create(venv_dir, with_pip=True) + + run( + python_bin, + '-m', + 'pip', + 'install', + f'--constraint={constraint_file!s}', + f'--requirement={requirement_file!s}', + '--disable-pip-version-check', + ) + + return codecov_bin + + def process_files(directory: pathlib.Path) -> t.Tuple[CoverageFile, ...]: processed = [] for file in directory.joinpath('reports').glob('coverage*.xml'): @@ -56,45 +90,42 @@ def process_files(directory: pathlib.Path) -> t.Tuple[CoverageFile, ...]: return tuple(processed) -def upload_files(codecov_bin: pathlib.Path, files: t.Tuple[CoverageFile, ...], dry_run: bool = False) -> None: +def upload_files(codecov_bin: pathlib.Path, config_file: pathlib.Path, files: t.Tuple[CoverageFile, ...], dry_run: bool = False) -> None: for file in files: cmd = [ str(codecov_bin), - '--name', file.name, - '--file', str(file.path), + '--disable-telem', + '--codecov-yml-path', + config_file, + 'upload-process', + '--disable-search', + '--disable-file-fixes', + '--plugin', + 'noop', + '--name', + file.name, + '--file', + file.path, ] for flag in file.flags: - cmd.extend(['--flags', flag]) + cmd.extend(['--flag', flag]) if dry_run: - print(f'DRY-RUN: Would run command: {cmd}') - continue - - subprocess.run(cmd, check=True) - + cmd.append('--dry-run') -def download_file(url: str, dest: pathlib.Path, flags: int, dry_run: bool = False) -> None: - if dry_run: - print(f'DRY-RUN: Would download {url} to {dest} and set mode to {flags:o}') - return + run(*cmd) - with urllib.request.urlopen(url) as resp: - with dest.open('w+b') as f: - # Read data in chunks rather than all at once - shutil.copyfileobj(resp, f, 64 * 1024) - dest.chmod(flags) - - -def main(): +def main() -> None: args = parse_args() - url = 'https://ci-files.testing.ansible.com/codecov/linux/codecov' + with tempfile.TemporaryDirectory(prefix='codecov-') as tmpdir: - codecov_bin = pathlib.Path(tmpdir) / 'codecov' - download_file(url, codecov_bin, 0o755, args.dry_run) + config_file = pathlib.Path(tmpdir) / 'config.yml' + config_file.write_text('') + codecov_bin = install_codecov(pathlib.Path(tmpdir)) files = process_files(args.path) - upload_files(codecov_bin, files, args.dry_run) + upload_files(codecov_bin, config_file, files, args.dry_run) if __name__ == '__main__': diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 26209de93f9..8298904121b 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -1,4 +1,3 @@ -.azure-pipelines/scripts/publish-codecov.py replace-urlopen lib/ansible/cli/scripts/ansible_connection_cli_stub.py shebang lib/ansible/config/base.yml no-unwanted-files lib/ansible/executor/playbook_executor.py pylint:disallowed-name