diff --git a/contrib/teamcity/build-configurations.json b/contrib/teamcity/build-configurations.json new file mode 100644 --- /dev/null +++ b/contrib/teamcity/build-configurations.json @@ -0,0 +1,71 @@ +{ + "build-asan": { + "script": "builds/build-asan.sh" + }, + "build-autotools": { + "script": "builds/build-autotools.sh" + }, + "build-bench": { + "script": "builds/build-bench.sh" + }, + "build-clang-10": { + "script": "builds/build-clang-10.sh" + }, + "build-clang-tidy": { + "script": "builds/build-clang-tidy.sh" + }, + "build-coverage": { + "script": "builds/build-coverage.sh" + }, + "build-diff": { + "script": "builds/build-diff.sh" + }, + "build-ibd": { + "script": "builds/build-ibd.sh" + }, + "build-ibd-no-assumevalid-checkpoint": { + "script": "builds/build-ibd-no-assumevalid-checkpoint.sh" + }, + "build-linux64": { + "script": "builds/build-linux64.sh" + }, + "build-linux-aarch64": { + "script": "builds/build-linux-aarch64.sh" + }, + "build-linux-arm": { + "script": "builds/build-linux-arm.sh" + }, + "build-make-generator": { + "script": "builds/build-make-generator.sh" + }, + "build-master": { + "script": "builds/build-master.sh" + }, + "build-osx": { + "script": "builds/build-osx.sh" + }, + "build-secp256k1": { + "script": "builds/build-secp256k1.sh" + }, + "build-tsan": { + "script": "builds/build-tsan.sh" + }, + "build-ubsan": { + "script": "builds/build-ubsan.sh" + }, + "build-win64": { + "script": "builds/build-win64.sh" + }, + "build-without-cli": { + "script": "builds/build-without-cli.sh" + }, + "build-without-wallet": { + "script": "builds/build-without-wallet.sh" + }, + "build-without-zmq": { + "script": "builds/build-without-zmq.sh" + }, + "check-seeds": { + "script": "builds/check-seeds.sh" + } +} diff --git a/contrib/teamcity/build-configurations.py b/contrib/teamcity/build-configurations.py new file mode 100755 --- /dev/null +++ b/contrib/teamcity/build-configurations.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import argparse +import json +import os +from pathlib import Path, PurePath +import subprocess +import sys + +if sys.version_info < (3, 6): + raise SystemError("This script requires python >= 3.6") + + +def main(): + script_dir = PurePath(os.path.realpath(__file__)).parent + + # By default search for a configuration file in the same directory as this + # script. + default_config_path = Path( + script_dir.joinpath("build-configurations.json") + ) + + parser = argparse.ArgumentParser(description="Run a CI build") + parser.add_argument( + "build", + help="The name of the build to run" + ) + parser.add_argument( + "--config", + "-c", + help="Path to the builds configuration file (default to {})".format( + str(default_config_path) + ) + ) + + args, unknown_args = parser.parse_known_args() + + # Check the configuration file exists + config_path = Path(args.config) if args.config else default_config_path + if not config_path.is_file(): + raise FileNotFoundError( + "The configuration file does not exist {}".format( + str(config_path) + ) + ) + + # Read the configuration + with open(config_path, encoding="utf-8") as f: + config = json.load(f) + + # Check the target build has an entry in the configuration file + build = config.get(args.build, None) + if not build: + raise AssertionError( + "{} is not a valid build identifier. Valid identifiers are {}".format( + args.build, list(config.keys()) + ) + ) + + # Make sure there is a script file associated with the build... + script = build.get("script", None) + if script is None: + raise AssertionError( + "No script provided for the build {}".format( + args.build + ) + ) + + # ... and that the script file can be executed + script_path = Path(script_dir.joinpath(script)) + if not script_path.is_file() or not os.access(script_path, os.X_OK): + raise FileNotFoundError( + "The script file {} does not exist or does not have execution permission".format( + str(script_path) + ) + ) + + # Find the git root directory + git_root = PurePath( + subprocess.run( + ['git', 'rev-parse', '--show-toplevel'], + capture_output=True, + check=True, + encoding='utf-8', + text=True, + ).stdout.strip() + ) + + # Create the build directory as needed + build_directory = Path(git_root.joinpath(args.build)) + build_directory.mkdir(exist_ok=True) + + # We will provide the required environment variables + environment_variables = { + "BUILD_DIR": str(build_directory), + "CMAKE_PLATFORMS_DIR": git_root.joinpath("cmake", "platforms"), + "THREADS": str(os.cpu_count() or 1), + "TOPLEVEL": str(git_root), + } + + try: + subprocess.run( + [str(script_path)] + unknown_args, + check=True, + cwd=build_directory, + env={**os.environ, **environment_variables}, + ) + except subprocess.CalledProcessError as e: + print("Build failed with exit code {}".format(e.returncode)) + sys.exit(e.returncode) + + +if __name__ == '__main__': + main() diff --git a/contrib/teamcity/build-configurations.sh b/contrib/teamcity/build-configurations.sh --- a/contrib/teamcity/build-configurations.sh +++ b/contrib/teamcity/build-configurations.sh @@ -16,30 +16,5 @@ exit 1 fi -echo "Running build configuration '${ABC_BUILD_NAME}'..." - TOPLEVEL=$(git rev-parse --show-toplevel) -export TOPLEVEL - -# Use separate build dirs to get the most out of ccache and prevent crosstalk -: "${BUILD_DIR:=${TOPLEVEL}/${ABC_BUILD_NAME}}" -mkdir -p "${BUILD_DIR}" -BUILD_DIR=$(cd "${BUILD_DIR}"; pwd) -export BUILD_DIR -cd "${BUILD_DIR}" - -# Determine the number of build threads -THREADS=$(nproc || sysctl -n hw.ncpu) -export THREADS - -export CMAKE_PLATFORMS_DIR="${TOPLEVEL}/cmake/platforms" - -# Check the script exists -SCRIPT_PATH="${TOPLEVEL}/contrib/teamcity/builds/${ABC_BUILD_NAME}.sh" -if [ ! -x "${SCRIPT_PATH}" ]; then - echo "${SCRIPT_PATH} does not exist or is not executable" - exit 1 -fi - -# Run the script -"${SCRIPT_PATH}" +python3 "${TOPLEVEL}/contrib/teamcity/build-configurations.py" "${ABC_BUILD_NAME}"