From c17a2adb8dc0e9706319ddecc42737be4268ac6a Mon Sep 17 00:00:00 2001 From: will Date: Fri, 23 Jan 2026 12:09:22 +0000 Subject: [PATCH] lint: upgrade lint scripts for worktrees Add a ci/lint.py script to run the linter both locally or inside the CI (replacing .github/ci-lint-exec.py) which supports running from a worktree. Determines whether we are in a worktree, and mounts the real `.git` directory as a read-only volume if we are. --- .github/ci-lint-exec.py | 55 --------------------------- .github/workflows/ci.yml | 4 +- ci/lint.py | 81 ++++++++++++++++++++++++++++++++++++++++ test/lint/README.md | 11 ++---- 4 files changed, 88 insertions(+), 63 deletions(-) delete mode 100755 .github/ci-lint-exec.py create mode 100755 ci/lint.py diff --git a/.github/ci-lint-exec.py b/.github/ci-lint-exec.py deleted file mode 100755 index 73749accd6f..00000000000 --- a/.github/ci-lint-exec.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or https://opensource.org/license/mit. - -import os -import shlex -import subprocess -import sys -import time - - -def run(cmd, **kwargs): - print("+ " + shlex.join(cmd), flush=True) - kwargs.setdefault("check", True) - try: - return subprocess.run(cmd, **kwargs) - except Exception as e: - sys.exit(e) - - -def main(): - CONTAINER_NAME = os.environ["CONTAINER_NAME"] - - build_cmd = [ - "docker", "buildx", "build", - f"--tag={CONTAINER_NAME}", - *shlex.split(os.getenv("DOCKER_BUILD_CACHE_ARG", "")), - "--file=./ci/lint_imagefile", - "." - ] - - if run(build_cmd, check=False).returncode != 0: - print("Retry building image tag after failure") - time.sleep(3) - run(build_cmd) - - extra_env = [] - if os.environ["GITHUB_EVENT_NAME"] == "pull_request": - extra_env = ["--env", "LINT_CI_IS_PR=1"] - if os.environ["GITHUB_EVENT_NAME"] != "pull_request" and os.environ["GITHUB_REPOSITORY"] == "bitcoin/bitcoin": - extra_env = ["--env", "LINT_CI_SANITY_CHECK_COMMIT_SIG=1"] - - run([ - "docker", - "run", - "--rm", - *extra_env, - f"--volume={os.getcwd()}:/bitcoin", - CONTAINER_NAME, - ]) - - -if __name__ == "__main__": - main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f54e06616f7..bbac5e023c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -681,4 +681,6 @@ jobs: cache-provider: ${{ needs.runners.outputs.provider }} - name: CI script - run: python .github/ci-lint-exec.py + run: | + git worktree add ../lint-worktree HEAD + ../lint-worktree/ci/lint.py diff --git a/ci/lint.py b/ci/lint.py new file mode 100755 index 00000000000..8e2a1e55ed9 --- /dev/null +++ b/ci/lint.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# Copyright (c) The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit/. + +import os +import shlex +import subprocess +import sys +import time +from pathlib import Path + + +def run(cmd, **kwargs): + print("+ " + shlex.join(cmd), flush=True) + kwargs.setdefault("check", True) + try: + return subprocess.run(cmd, **kwargs) + except Exception as e: + sys.exit(str(e)) + + +def get_worktree_mounts(repo_root): + git_path = repo_root / ".git" + if not git_path.is_file(): + return [] + content = git_path.read_text().strip() + if not content.startswith("gitdir: "): + return [] + gitdir = (repo_root / content.removeprefix("gitdir: ")).resolve() + main_gitdir = gitdir.parent.parent + return [ + f"--volume={gitdir}:{gitdir}", + f"--volume={main_gitdir}:{main_gitdir}:ro", + ] + + +def main(): + repo_root = Path(__file__).resolve().parent.parent + is_ci = os.environ.get("GITHUB_ACTIONS") == "true" + container = "bitcoin-linter" + + build_cmd = [ + "docker", + "buildx", + "build", + f"--tag={container}", + *shlex.split(os.environ.get("DOCKER_BUILD_CACHE_ARG", "")), + f"--file={repo_root}/ci/lint_imagefile", + str(repo_root), + ] + if run(build_cmd, check=False).returncode != 0: + if is_ci: + print("Retry building image after failure") + time.sleep(3) + run(build_cmd) + + extra_env = [] + if is_ci: + if os.environ.get("GITHUB_EVENT_NAME") == "pull_request": + extra_env = ["--env", "LINT_CI_IS_PR=1"] + elif os.environ.get("GITHUB_REPOSITORY") == "bitcoin/bitcoin": + extra_env = ["--env", "LINT_CI_SANITY_CHECK_COMMIT_SIG=1"] + + run( + [ + "docker", + "run", + "--rm", + *extra_env, + f"--volume={repo_root}:/bitcoin", + *get_worktree_mounts(repo_root), + *([] if is_ci else ["-it"]), + container, + *sys.argv[1:], + ] + ) + + +if __name__ == "__main__": + main() diff --git a/test/lint/README.md b/test/lint/README.md index e28eda1fdf2..462e4c80dcb 100644 --- a/test/lint/README.md +++ b/test/lint/README.md @@ -3,20 +3,17 @@ This folder contains lint scripts. Running locally =============== -To run linters locally with the same versions as the CI environment, use the included -Dockerfile: +To run linters locally with the same versions as the CI environment use +the _lint.py_ helper script which runs checks inside the CI container: ```sh -DOCKER_BUILDKIT=1 docker build --platform=linux --tag=bitcoin-linter --file="./ci/lint_imagefile" ./ && docker run --rm -v $(pwd):/bitcoin -it bitcoin-linter +./ci/lint.py ``` -Building the container can be done every time, because it is fast when the -result is cached and it prevents issues when the image changes. - test runner =========== -To run all the lint checks in the test runner outside the docker you first need +To run all the lint checks in the test runner outside the container you first need to install the rust toolchain using your package manager of choice or [rustup](https://www.rust-lang.org/tools/install).