pip/noxfile.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

400 lines
13 KiB
Python
Raw Normal View History

2019-09-27 19:13:32 +02:00
"""Automation using nox.
2019-07-30 13:26:44 +02:00
"""
import argparse
import glob
import os
import shutil
2019-11-02 20:06:57 +01:00
import sys
from pathlib import Path
from typing import Iterator, List, Tuple
2019-09-05 09:15:28 +02:00
2019-07-30 13:26:44 +02:00
import nox
2021-02-21 14:03:38 +01:00
# fmt: off
2019-11-02 20:06:57 +01:00
sys.path.append(".")
from tools import release # isort:skip # noqa
2019-11-02 20:06:57 +01:00
sys.path.pop()
2021-02-21 14:03:38 +01:00
# fmt: on
2019-11-02 20:06:57 +01:00
nox.options.reuse_existing_virtualenvs = True
nox.options.sessions = ["lint"]
LOCATIONS = {
"common-wheels": "tests/data/common_wheels",
"protected-pip": "tools/protected_pip.py",
}
REQUIREMENTS = {
"docs": "docs/requirements.txt",
2021-07-29 00:24:49 +02:00
"tests": "tests/requirements.txt",
"common-wheels": "tests/requirements-common_wheels.txt",
}
2019-10-13 23:32:00 +02:00
AUTHORS_FILE = "AUTHORS.txt"
VERSION_FILE = "src/pip/__init__.py"
2019-07-30 13:26:44 +02:00
def run_with_protected_pip(session: nox.Session, *arguments: str) -> None:
"""Do a session.run("pip", *arguments), using a "protected" pip.
2019-09-28 07:43:37 +02:00
This invokes a wrapper script, that forwards calls to original virtualenv
(stable) version, and not the code being tested. This ensures pip being
used is not the code being tested.
"""
2021-08-08 21:42:44 +02:00
env = {"VIRTUAL_ENV": session.virtualenv.location}
command = ("python", LOCATIONS["protected-pip"]) + arguments
session.run(*command, env=env, silent=True)
def should_update_common_wheels() -> bool:
# If the cache hasn't been created, create it.
if not os.path.exists(LOCATIONS["common-wheels"]):
return True
# If the requirements was updated after cache, we'll repopulate it.
cache_last_populated_at = os.path.getmtime(LOCATIONS["common-wheels"])
requirements_updated_at = os.path.getmtime(REQUIREMENTS["common-wheels"])
need_to_repopulate = requirements_updated_at > cache_last_populated_at
# Clear the stale cache.
if need_to_repopulate:
shutil.rmtree(LOCATIONS["common-wheels"], ignore_errors=True)
return need_to_repopulate
# -----------------------------------------------------------------------------
# Development Commands
# -----------------------------------------------------------------------------
2023-07-04 09:35:47 +02:00
@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3"])
def test(session: nox.Session) -> None:
# Get the common wheels.
if should_update_common_wheels():
2021-02-21 14:03:38 +01:00
# fmt: off
run_with_protected_pip(
session,
"wheel",
"-w", LOCATIONS["common-wheels"],
"-r", REQUIREMENTS["common-wheels"],
)
2021-02-21 14:03:38 +01:00
# fmt: on
else:
2021-02-21 14:03:38 +01:00
msg = f"Re-using existing common-wheels at {LOCATIONS['common-wheels']}."
session.log(msg)
# Build source distribution
2021-08-08 21:42:44 +02:00
sdist_dir = os.path.join(session.virtualenv.location, "sdist")
if os.path.exists(sdist_dir):
shutil.rmtree(sdist_dir, ignore_errors=True)
2021-02-21 14:03:38 +01:00
# fmt: off
session.install("setuptools")
session.run(
2021-02-21 14:03:38 +01:00
"python", "setup.py", "sdist", "--formats=zip", "--dist-dir", sdist_dir,
silent=True,
)
2021-02-21 14:03:38 +01:00
# fmt: on
generated_files = os.listdir(sdist_dir)
assert len(generated_files) == 1
generated_sdist = os.path.join(sdist_dir, generated_files[0])
# Install source distribution
run_with_protected_pip(session, "install", generated_sdist)
# Install test dependencies
run_with_protected_pip(session, "install", "-r", REQUIREMENTS["tests"])
# Parallelize tests as much as possible, by default.
arguments = session.posargs or ["-n", "auto"]
# Run the tests
2019-09-28 07:43:37 +02:00
# LC_CTYPE is set to get UTF-8 output inside of the subprocesses that our
# tests use.
session.run(
"pytest",
*arguments,
env={
"LC_CTYPE": "en_US.UTF-8",
},
)
@nox.session
def docs(session: nox.Session) -> None:
2020-03-13 19:59:04 +01:00
session.install("-e", ".")
session.install("-r", REQUIREMENTS["docs"])
def get_sphinx_build_command(kind: str) -> List[str]:
2019-09-28 07:43:37 +02:00
# Having the conf.py in the docs/html is weird but needed because we
# can not use a different configuration directory vs source directory
# on RTD currently. So, we'll pass "-c docs/html" here.
# See https://github.com/rtfd/readthedocs.org/issues/1543.
2021-02-21 14:03:38 +01:00
# fmt: off
return [
"sphinx-build",
"--keep-going",
"-W",
2019-09-28 07:43:37 +02:00
"-c", "docs/html", # see note above
"-d", "docs/build/doctrees/" + kind,
"-b", kind,
"docs/" + kind,
"docs/build/" + kind,
]
2021-02-21 14:03:38 +01:00
# fmt: on
session.run(*get_sphinx_build_command("html"))
session.run(*get_sphinx_build_command("man"))
2021-02-04 10:10:43 +01:00
@nox.session(name="docs-live")
def docs_live(session: nox.Session) -> None:
2021-02-04 10:10:43 +01:00
session.install("-e", ".")
session.install("-r", REQUIREMENTS["docs"], "sphinx-autobuild")
session.run(
"sphinx-autobuild",
"-d=docs/build/doctrees/livehtml",
"-b=dirhtml",
"docs/html",
"docs/build/livehtml",
*session.posargs,
)
@nox.session
def lint(session: nox.Session) -> None:
session.install("pre-commit")
if session.posargs:
args = session.posargs + ["--all-files"]
else:
args = ["--all-files", "--show-diff-on-failure"]
session.run("pre-commit", "run", *args)
# NOTE: This session will COMMIT upgrades to vendored libraries.
# You should therefore not run it directly against `main`. If you
2022-10-08 18:47:22 +02:00
# do (assuming you started with a clean main), you can run:
#
# git checkout -b vendoring-updates
# git checkout main
# git reset --hard origin/main
2020-04-13 13:32:28 +02:00
@nox.session
def vendoring(session: nox.Session) -> None:
# Ensure that the session Python is running 3.10+
# so that truststore can be installed correctly.
session.run(
"python", "-c", "import sys; sys.exit(1 if sys.version_info < (3, 10) else 0)"
)
session.install("vendoring~=1.2.0")
parser = argparse.ArgumentParser(prog="nox -s vendoring")
parser.add_argument("--upgrade-all", action="store_true")
parser.add_argument("--upgrade", action="append", default=[])
parser.add_argument("--skip", action="append", default=[])
args = parser.parse_args(session.posargs)
if not (args.upgrade or args.upgrade_all):
2021-10-09 10:41:04 +02:00
session.run("vendoring", "sync", "-v")
return
def pinned_requirements(path: Path) -> Iterator[Tuple[str, str]]:
for line in path.read_text().splitlines(keepends=False):
one, sep, two = line.partition("==")
if not sep:
continue
name = one.strip()
version = two.split("#", 1)[0].strip()
if name and version:
yield name, version
vendor_txt = Path("src/pip/_vendor/vendor.txt")
for name, old_version in pinned_requirements(vendor_txt):
if name in args.skip:
continue
if args.upgrade and name not in args.upgrade:
continue
# update requirements.txt
session.run("vendoring", "update", ".", name)
# get the updated version
new_version = old_version
for inner_name, inner_version in pinned_requirements(vendor_txt):
if inner_name == name:
2023-06-05 15:26:05 +02:00
# this is a dedicated assignment, to make lint happy
new_version = inner_version
break
else:
session.error(f"Could not find {name} in {vendor_txt}")
# check if the version changed.
if new_version == old_version:
continue # no change, nothing more to do here.
# synchronize the contents
session.run("vendoring", "sync", ".")
# Determine the correct message
message = f"Upgrade {name} to {new_version}"
# Write our news fragment
news_file = Path("news") / (name + ".vendor.rst")
news_file.write_text(message + "\n") # "\n" appeases end-of-line-fixer
# Commit the changes
release.commit_file(session, ".", message=message)
2020-04-13 13:32:28 +02:00
@nox.session
def coverage(session: nox.Session) -> None:
# Install source distribution
run_with_protected_pip(session, "install", ".")
# Install test dependencies
run_with_protected_pip(session, "install", "-r", REQUIREMENTS["tests"])
if not os.path.exists(".coverage-output"):
os.mkdir(".coverage-output")
session.run(
"pytest",
"--cov=pip",
"--cov-config=./setup.cfg",
*session.posargs,
2021-11-29 17:15:07 +01:00
env={
"COVERAGE_OUTPUT_DIR": "./.coverage-output",
"COVERAGE_PROCESS_START": os.fsdecode(Path("setup.cfg").resolve()),
2021-11-29 17:15:07 +01:00
},
)
2019-07-30 13:26:44 +02:00
# -----------------------------------------------------------------------------
# Release Commands
2019-07-30 13:26:44 +02:00
# -----------------------------------------------------------------------------
2019-11-02 20:06:57 +01:00
@nox.session(name="prepare-release")
def prepare_release(session: nox.Session) -> None:
version = release.get_version_from_arguments(session)
2019-11-02 20:06:57 +01:00
if not version:
session.error("Usage: nox -s prepare-release -- <version>")
2019-11-02 20:06:57 +01:00
session.log("# Ensure nothing is staged")
if release.modified_files_in_git("--staged"):
session.error("There are files staged in git")
session.log(f"# Updating {AUTHORS_FILE}")
release.generate_authors(AUTHORS_FILE)
if release.modified_files_in_git():
2021-02-21 14:03:38 +01:00
release.commit_file(session, AUTHORS_FILE, message=f"Update {AUTHORS_FILE}")
2019-10-13 23:32:00 +02:00
else:
2019-11-02 20:06:57 +01:00
session.log(f"# No changes to {AUTHORS_FILE}")
2019-10-13 23:32:00 +02:00
2019-11-02 20:06:57 +01:00
session.log("# Generating NEWS")
release.generate_news(session, version)
2019-10-13 23:32:00 +02:00
2019-11-02 20:06:57 +01:00
session.log(f"# Bumping for release {version}")
release.update_version_file(version, VERSION_FILE)
release.commit_file(session, VERSION_FILE, message="Bump for release")
2019-10-13 23:32:00 +02:00
2019-11-02 20:06:57 +01:00
session.log("# Tagging release")
release.create_git_tag(session, version, message=f"Release {version}")
2019-10-13 23:32:00 +02:00
2019-11-02 20:06:57 +01:00
session.log("# Bumping for development")
next_dev_version = release.get_next_development_version(version)
release.update_version_file(next_dev_version, VERSION_FILE)
release.commit_file(session, VERSION_FILE, message="Bump for development")
@nox.session(name="build-release")
def build_release(session: nox.Session) -> None:
version = release.get_version_from_arguments(session)
if not version:
session.error("Usage: nox -s build-release -- YY.N[.P]")
session.log("# Ensure no files in dist/")
if release.have_files_in_folder("dist"):
session.error(
"There are files in dist/. Remove them and try again. "
"You can use `git clean -fxdi -- dist` command to do this"
)
session.log("# Install dependencies")
session.install("build", "twine")
with release.isolated_temporary_checkout(session, version) as build_dir:
2020-01-28 16:45:42 +01:00
session.log(
"# Start the build in an isolated, "
f"temporary Git checkout at {build_dir!s}",
2020-01-28 16:45:42 +01:00
)
with release.workdir(session, build_dir):
tmp_dists = build_dists(session)
tmp_dist_paths = (build_dir / p for p in tmp_dists)
session.log(f"# Copying dists from {build_dir}")
2021-02-21 14:03:38 +01:00
os.makedirs("dist", exist_ok=True)
for dist, final in zip(tmp_dist_paths, tmp_dists):
session.log(f"# Copying {dist} to {final}")
shutil.copy(dist, final)
def build_dists(session: nox.Session) -> List[str]:
"""Return dists with valid metadata."""
session.log(
"# Check if there's any Git-untracked files before building the wheel",
)
has_forbidden_git_untracked_files = any(
# Don't report the environment this session is running in
2021-02-21 14:03:38 +01:00
not untracked_file.startswith(".nox/build-release/")
for untracked_file in release.get_git_untracked_files()
)
if has_forbidden_git_untracked_files:
session.error(
"There are untracked files in the working directory. "
"Remove them and try again",
)
session.log("# Build distributions")
session.run("python", "-m", "build", silent=True)
produced_dists = glob.glob("dist/*")
session.log(f"# Verify distributions: {', '.join(produced_dists)}")
session.run("twine", "check", *produced_dists, silent=True)
return produced_dists
@nox.session(name="upload-release")
def upload_release(session: nox.Session) -> None:
version = release.get_version_from_arguments(session)
if not version:
session.error("Usage: nox -s upload-release -- YY.N[.P]")
session.log("# Install dependencies")
session.install("twine")
distribution_files = glob.glob("dist/*")
session.log(f"# Distribution files: {distribution_files}")
# Sanity check: Make sure there's 2 distribution files.
count = len(distribution_files)
if count != 2:
session.error(
f"Expected 2 distribution files for upload, got {count}. "
f"Remove dist/ and run 'nox -s build-release -- {version}'"
)
# Sanity check: Make sure the files are correctly named.
distfile_names = (os.path.basename(fn) for fn in distribution_files)
expected_distribution_files = [
f"pip-{version}-py3-none-any.whl",
f"pip-{version}.tar.gz",
]
if sorted(distfile_names) != sorted(expected_distribution_files):
2021-02-21 14:03:38 +01:00
session.error(f"Distribution files do not seem to be for {version} release.")
session.log("# Upload distributions")
session.run("twine", "upload", *distribution_files)