#!/usr/bin/env python3 # # Copyright (c) 2018-2020 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # # Download or build previous releases. # Needs curl and tar to download a release, or the build dependencies when # building a release. import argparse import contextlib from fnmatch import fnmatch import os from pathlib import Path import re import shutil import subprocess import sys import hashlib @contextlib.contextmanager def pushd(new_dir) -> None: previous_dir = os.getcwd() os.chdir(new_dir) try: yield finally: os.chdir(previous_dir) def download_binary(tag, args) -> int: if Path(tag).is_dir(): if not args.remove_dir: print('Using cached {}'.format(tag)) return 0 shutil.rmtree(tag) Path(tag).mkdir() bin_path = 'bin/bitcoin-core-{}'.format(tag[1:]) match = re.compile('v(.*)(rc[0-9]+)$').search(tag) if match: bin_path = 'bin/bitcoin-core-{}/test.{}'.format( match.group(1), match.group(2)) tarball = 'bitcoin-{tag}-{platform}.tar.gz'.format( tag=tag[1:], platform=args.platform) sha256Sums = "SHA256SUMS-{tag}.asc".format(tag=tag[1:]) tarballUrl = 'https://bitcoincore.org/{bin_path}/{tarball}'.format( bin_path=bin_path, tarball=tarball) sha256SumsUrl = 'https://bitcoincore.org/{bin_path}/SHA256SUMS.asc'.format( bin_path=bin_path) print('Fetching: {tarballUrl}'.format(tarballUrl=tarballUrl)) print('Fetching: {sha256SumsUrl}'.format(sha256SumsUrl=sha256SumsUrl)) header, status = subprocess.Popen( ['curl', '--head', tarballUrl], stdout=subprocess.PIPE).communicate() if re.search("404 Not Found", header.decode("utf-8")): print("Binary tag was not found") return 1 curlCmds = [ ['curl', '--remote-name', tarballUrl], ['curl', '--output', sha256Sums, sha256SumsUrl], ] for cmd in curlCmds: ret = subprocess.run(cmd).returncode if ret: return ret hasher = hashlib.sha256() with open(tarball, "rb") as afile: hasher.update(afile.read()) tarballHash = hasher.hexdigest() tarballHash = '{} {}\n'.format(tarballHash, tarball) with open(sha256Sums, 'r', encoding="utf-8") as afile: shasums = afile.readlines() if tarballHash not in shasums: print("Checksum did not match") Path(tarball).unlink() return 1 print("Checksum matched") # Bitcoin Core Release Signing Keys v0.11.0+ signingKey = "01EA5486DE18A882D4C2684590C8019E36C2E964" isKeyPresent = subprocess.run( ["gpg", "--list-keys", signingKey]).returncode if isKeyPresent: return isKeyPresent isVerified = subprocess.run( ["gpg", "--verify", sha256Sums]).returncode if isVerified: return isVerified # Extract tarball ret = subprocess.run(['tar', '-zxf', tarball, '-C', tag, '--strip-components=1', 'bitcoin-{tag}'.format(tag=tag[1:])]).returncode if ret: return ret Path(tarball).unlink() Path(sha256Sums).unlink() return 0 def build_release(tag, args) -> int: githubUrl = "https://github.com/bitcoin/bitcoin" if args.remove_dir: if Path(tag).is_dir(): shutil.rmtree(tag) if not Path(tag).is_dir(): # fetch new tags subprocess.run( ["git", "fetch", githubUrl, "--tags"]) output = subprocess.check_output(['git', 'tag', '-l', tag]) if not output: print('Tag {} not found'.format(tag)) return 1 ret = subprocess.run([ 'git', 'clone', githubUrl, tag ]).returncode if ret: return ret with pushd(tag): ret = subprocess.run(['git', 'checkout', tag]).returncode if ret: return ret host = args.host if args.depends: with pushd('depends'): ret = subprocess.run(['make', 'NO_QT=1']).returncode if ret: return ret host = os.environ.get( 'HOST', subprocess.check_output(['./config.guess'])) config_flags = '--prefix={pwd}/depends/{host} '.format( pwd=os.getcwd(), host=host) + args.config_flags cmds = [ './autogen.sh', './configure {}'.format(config_flags), 'make', ] for cmd in cmds: ret = subprocess.run(cmd.split()).returncode if ret: return ret # Move binaries, so they're in the same place as in the # release download Path('bin').mkdir(exist_ok=True) files = ['bitcoind', 'bitcoin-cli', 'bitcoin-tx'] for f in files: Path('src/'+f).rename('bin/'+f) return 0 def check_host(args) -> int: args.host = os.environ.get('HOST', subprocess.check_output( './depends/config.guess').decode()) if args.download_binary: platforms = { 'x86_64-*-linux*': 'x86_64-linux-gnu', 'x86_64-apple-darwin*': 'osx64', } args.platform = '' for pattern, target in platforms.items(): if fnmatch(args.host, pattern): args.platform = target if not args.platform: print('Not sure which binary to download for {}'.format(args.host)) return 1 return 0 def main(args) -> int: Path(args.target_dir).mkdir(exist_ok=True, parents=True) print("Releases directory: {}".format(args.target_dir)) ret = check_host(args) if ret: return ret if args.download_binary: with pushd(args.target_dir): for tag in args.tags: ret = download_binary(tag, args) if ret: return ret return 0 args.config_flags = os.environ.get('CONFIG_FLAGS', '') args.config_flags += ' --without-gui --disable-tests --disable-bench' with pushd(args.target_dir): for tag in args.tags: ret = build_release(tag, args) if ret: return ret return 0 if __name__ == '__main__': parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('-r', '--remove-dir', action='store_true', help='remove existing directory.') parser.add_argument('-d', '--depends', action='store_true', help='use depends.') parser.add_argument('-b', '--download-binary', action='store_true', help='download release binary.') parser.add_argument('-t', '--target-dir', action='store', help='target directory.', default='releases') parser.add_argument('tags', nargs='+', help="release tags. e.g.: v0.18.1 v0.20.0rc2") args = parser.parse_args() sys.exit(main(args))