Beispiel #1
0
def prepare_release(session: nox.Session) -> None:
    version = release.get_version_from_arguments(session)
    if not version:
        session.error("Usage: nox -s prepare-release -- <version>")

    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():
        release.commit_file(session,
                            AUTHORS_FILE,
                            message=f"Update {AUTHORS_FILE}")
    else:
        session.log(f"# No changes to {AUTHORS_FILE}")

    session.log("# Generating NEWS")
    release.generate_news(session, version)

    session.log(f"# Bumping for release {version}")
    release.update_version_file(version, VERSION_FILE)
    release.commit_file(session, VERSION_FILE, message="Bump for release")

    session.log("# Tagging release")
    release.create_git_tag(session, version, message=f"Release {version}")

    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")
Beispiel #2
0
def do_release(session: nox.Session) -> None:
    """Do a release to PyPI."""
    # TODO: maybe add version validation
    if modified_files_in_git() or modified_files_in_git("--staged"):
        session.error(
            "Repository not clean, please remove, unstage, or commit your changes"
        )

    if not len(session.posargs):
        session.error("Usage: nox -s publish -- <version> [publish-args]")
    else:
        version = session.posargs[0]

    update_version(session, version, MAIN_MODULE)
    update_changelog(session, version, CHANGELOG)
    commit_files(session, [MAIN_MODULE, CHANGELOG],
                 message=f"Prepare for release {version}")
    create_git_tag(session, version, message=f"Release {version}")

    with isolated_temporary_checkout(session) as workdir:
        session.chdir(workdir)
        session.install("flit")
        session.run("flit", "publish", *session.posargs[1:])

    session.chdir(THIS_DIR)
    update_version(session, next_development_version(version), MAIN_MODULE)
    update_changelog(session, "<unreleased>", CHANGELOG)
    commit_files(session, [MAIN_MODULE, CHANGELOG],
                 message="Let's get back to development")
    session.log(
        "Alright, just do a push to GitHub and everything should be done")
    session.log("If you're paranoid, go ahead, verify that things are fine")
    session.log("If not, sit back and relax, you just did a release 🎉")
Beispiel #3
0
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("setuptools", "wheel", "twine")

    with release.isolated_temporary_checkout(session, version) as build_dir:
        session.log(
            "# Start the build in an isolated, "
            f"temporary Git checkout at {build_dir!s}", )
        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}")
        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)
Beispiel #4
0
def update_changelog(session: nox.Session, version: str,
                     filepath: Path) -> None:
    """Update the changelog for both a new release and development."""
    with open(filepath, encoding="utf-8") as f:
        lines = f.read().splitlines()

    if version != "<unreleased>":
        for index, line in enumerate(lines):
            if line == "## Unreleased":
                # Let's replace that unreleased text with the actual version
                # and fix up that date of release.
                lines[index] = f"## {version}"
                lines[index + 2] = f"Date of release: {get_today_date()}"
                break
        else:
            session.error("Couldn't find changelog section for next release")
    else:
        # Grab the title and newline right after it
        updated_lines = [*lines[0:2]]
        # ... then add some boilerplace
        updated_lines.append("## Unreleased\n")
        updated_lines.append("Date of release: *n/a*\n")
        updated_lines.append("**Bugfixes & enhancements**:\n")
        updated_lines.append("- *so far it's looking like a desert*\n")
        # and finally add back the rest of the changelog.
        updated_lines.extend(lines[2:])
        lines = updated_lines

    with open(filepath, "w", encoding="utf-8") as f:
        f.write("\n".join(lines))
        f.write("\n")
Beispiel #5
0
def dev_test_sim(
    session: nox.Session,
    sim: Optional[str],
    toplevel_lang: Optional[str],
    gpi_interface: Optional[str],
) -> None:
    """Test a development version of cocotb against a simulator."""

    session.env["CFLAGS"] = "-Werror -Wno-deprecated-declarations -g --coverage"
    session.env["COCOTB_LIBRARY_COVERAGE"] = "1"
    session.env["CXXFLAGS"] = "-Werror"
    session.env["LDFLAGS"] = "--coverage"

    session.install(*test_deps, *coverage_deps)
    session.install("-e", ".")

    env = env_vars_for_test(sim, toplevel_lang, gpi_interface)
    config_str = stringify_dict(env)

    # Remove a potentially existing coverage file from a previous run for the
    # same test configuration.
    coverage_file = Path(f".coverage.test.sim-{sim}-{toplevel_lang}-{gpi_interface}")
    with suppress(FileNotFoundError):
        coverage_file.unlink()

    session.log(f"Running 'make test' against a simulator {config_str}")
    session.run("make", "test", external=True, env=env)

    session.log(f"Running simulator-specific tests against a simulator {config_str}")
    session.run(
        "pytest",
        "-v",
        "--cov=cocotb",
        "--cov-branch",
        # Don't display coverage report here
        "--cov-report=",
        "-k",
        "simulator_required",
    )
    Path(".coverage").rename(".coverage.pytest")

    session.log(f"All tests passed with configuration {config_str}!")

    # Combine coverage produced during the test runs, and place it in a file
    # with a name specific to this invocation of dev_test_sim().
    coverage_files = glob.glob("**/.coverage.cocotb", recursive=True)
    if not coverage_files:
        session.error(
            "No coverage files found. Something went wrong during the test execution."
        )
    coverage_files.append(".coverage.pytest")
    session.run("coverage", "combine", "--append", *coverage_files)
    Path(".coverage").rename(coverage_file)

    session.log(f"Stored Python coverage for this test run in {coverage_file}.")

    # Combine coverage from all nox sessions as last step after all sessions
    # have completed.
    session.notify("dev_coverage_combine")
Beispiel #6
0
def vendoring(session: nox.Session) -> None:
    session.install("vendoring>=0.3.0")

    if "--upgrade" not in session.posargs:
        session.run("vendoring", "sync", ".", "-v")
        return

    def pinned_requirements(path):
        # type: (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 == "setuptools":
            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:
                # this is a dedicated assignment, to make flake8 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)
Beispiel #7
0
def docs(session: nox.Session) -> None:
    """
    Build the docs.
    """
    session.install("-e", ".[docs]")

    if session.posargs:
        if "serve" in session.posargs:
            session.run("mkdocs", "serve")
        else:
            session.error("Unrecognized args, use 'serve'")
    else:
        session.run("mkdocs", "build")
Beispiel #8
0
def update_version(session: nox.Session, version: str, filepath: Path) -> None:
    with open(filepath, encoding="utf-8") as f:
        lines = f.read().splitlines()

    for index, line in enumerate(lines):
        if line.startswith("__version__"):
            lines[index] = f'__version__ = "{version}"'
            break
    else:
        session.error("Couldn't find __version__ in {filepath}")

    with open(filepath, "w", encoding="utf-8") as f:
        f.write("\n".join(lines))
        f.write("\n")
Beispiel #9
0
def enforce_branch_no_changes(session: nox.Session) -> None:
    """
    Errors out the current session if we're not on
    default branch or if there are uncommitted changes.
    """
    if has_changes():
        session.error("All changes must be committed or removed before release")

    branch = get_branch()

    if branch != DEFAULT_BRANCH:
        session.error(
            f"Must be on {DEFAULT_BRANCH!r} branch. Currently on {branch!r} branch"
        )
Beispiel #10
0
def dev(session: nox.Session) -> None:
    """
    Sets up a python dev environment for the project if one doesn't already exist.

    This session will:
    - Create a python virtualenv for the session
    - Install the `virtualenv` cli tool into this environment
    - Use `virtualenv` to create a global project virtual environment
    - Invoke the python interpreter from the global project environment to install
      the project and all it's development dependencies.
    """
    # Check if dev has been run before
    # this prevents manual running nox -s dev more than once
    # thus potentially corrupting an environment
    if VENV_DIR.exists():
        session.error(
            "There is already a virtual environment deactivate and remove it "
            "before running 'dev' again"
        )

    # Create the project virtual environment using virtualenv
    # installed into this sessions virtual environment
    # confusing but it works!
    session.install("virtualenv")
    session.run("virtualenv", os.fsdecode(VENV_DIR), silent=True)

    # Use the venv's interpreter to install the project along with
    # all it's dev dependencies, this ensure it's installed
    # in the right way
    session.run(
        PYTHON,
        "-m",
        "pip",
        "install",
        "--upgrade",
        "pip",
        "setuptools",
        "wheel",
        silent=True,
        external=True,
    )
    session.run(PYTHON, "-m", "pip", "install", "-e", ".[dev]", external=True)

    if bool(shutil.which("code")) or bool(shutil.which("code-insiders")):
        # Only do this is user has VSCode installed
        set_up_vscode(session)
Beispiel #11
0
def release(session: nox.Session) -> None:
    """
    Kicks off the automated release process by creating and pushing a new tag.

    Invokes bump2version with the posarg setting the version.

    Usage:

    $ nox -s release -- [major|minor|patch]
    """
    enforce_branch_no_changes(session)

    parser = argparse.ArgumentParser(
        description="Release a new semantic version.")
    parser.add_argument(
        "version",
        type=str,
        nargs=1,
        help="The type of semver release to make.",
        choices={"major", "minor", "patch"},
    )
    args: argparse.Namespace = parser.parse_args(args=session.posargs)
    version: str = args.version.pop()

    # If we get here, we should be good to go
    # Let's do a final check for safety
    confirm = input(
        f"You are about to bump the {version!r} version. Are you sure? [y/n]: "
    )

    # Abort on anything other than 'y'
    if confirm.lower().strip() != "y":
        session.error(
            f"You said no when prompted to bump the {version!r} version.")

    update_seeds(session)

    session.install("bump2version")

    session.log(f"Bumping the {version!r} version")
    session.run("bump2version", version)

    session.log("Pushing the new tag")
    session.run("git", "push", external=True)
    session.run("git", "push", "--tags", external=True)
Beispiel #12
0
def docs(session: nox.Session) -> None:
    """
    Build the docs. Pass "serve" to serve.
    """

    session.chdir("docs")
    session.install("-r", "requirements.txt")
    session.run("sphinx-build", "-M", "html", ".", "_build")

    if session.posargs:
        if "serve" in session.posargs:
            session.log(
                "Launching docs at http://localhost:8000/ - use Ctrl-C to quit"
            )
            session.run("python", "-m", "http.server", "8000", "-d",
                        "_build/html")
        else:
            session.error("Unsupported argument to docs")
Beispiel #13
0
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):
        session.error(
            f"Distribution files do not seem to be for {version} release.")

    session.log("# Upload distributions")
    session.run("twine", "upload", *distribution_files)
Beispiel #14
0
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
        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", "setup.py", "sdist", "bdist_wheel", 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
Beispiel #15
0
def release(session: nox.Session) -> None:
    """
    Kicks off the automated release process by creating and pushing a new tag.

    Invokes bump2version with the posarg setting the version.

    Usage:

    $ nox -s release -- [major|minor|patch]
    """
    # Little known Nox fact: Passing silent=True captures the output
    status = session.run(
        "git", "status", "--porcelain", silent=True, external=True
    ).strip()
    if len(status) > 1:
        session.error("All changes must be committed or removed before release")

    branch = session.run(
        "git", "rev-parse", "--abbrev-ref", "HEAD", silent=True, external=True
    ).strip()

    if branch != DEFAULT_BRANCH:
        session.error(
            f"Must be on {DEFAULT_BRANCH!r} branch. Currently on {branch!r} branch"
        )

    parser = argparse.ArgumentParser(description="Release a new semantic version.")
    parser.add_argument(
        "version",
        type=str,
        nargs=1,
        help="The type of semver release to make.",
        choices={"major", "minor", "patch"},
    )
    args: argparse.Namespace = parser.parse_args(args=session.posargs)
    version: str = args.version.pop()

    # If we get here, we should be good to go
    # Let's do a final check for safety
    confirm = input(
        f"You are about to bump the {version!r} version. Are you sure? [y/n]: "
    )

    # Abort on anything other than 'y'
    if confirm.lower().strip() != "y":
        session.error(f"You said no when prompted to bump the {version!r} version.")

    session.install("--upgrade", "pip", "setuptools", "wheel")

    session.install("bump2version")

    session.log(f"Bumping the {version!r} version")
    session.run("bump2version", version)

    session.log("Pushing the new tag")
    session.run("git", "push", external=True)
    session.run("git", "push", "--tags", external=True)
Beispiel #16
0
def release(session: nox.Session) -> None:
    """
    Kicks off the automated release process by creating and pushing a new tag.

    Invokes bump2version with the posarg setting the version.

    Usage:

    $ nox -s release -- [major|minor|patch]
    """

    enforce_branch_no_changes(session)

    allowed_args: Set[str] = {"major", "minor", "patch"}
    n_args: int = len(session.posargs)

    if n_args != 1:
        session.error(
            f"Only 1 session arg allowed, got {n_args}. Pass one of: {allowed_args}"
        )

    # If we get here, we know there's only 1 posarg
    version = session.posargs.pop()

    if version not in allowed_args:
        session.error(
            f"Invalid argument: got {version!r}, expected one of: {allowed_args}"
        )

    # If we get here, we should be good to go
    # Let's do a final check for safety
    confirm = input(
        f"You are about to bump the {version!r} version. Are you sure? [y/n]: "
    )

    # Abort on anything other than 'y'
    if confirm.lower().strip() != "y":
        session.error(
            f"You said no when prompted to bump the {version!r} version.")

    update_seeds(session)

    session.install("bump2version")

    session.log(f"Bumping the {version!r} version")
    session.run("bump2version", version)

    session.log("Pushing the new tag")
    session.run("git", "push", external=True)
    session.run("git", "push", "--tags", external=True)
Beispiel #17
0
def find_session_runner(session: nox.Session, name: str):
    """Helper function to find parameterized action by name"""
    for s, _ in session._runner.manifest.list_all_sessions():
        if name in s.signatures:
            return s
    session.error(f"Could not find a nox session by the name {name!r}")