1#!/usr/bin/env python3 2# Copyright (c) 2018-2019 The Bitcoin Core developers 3# Distributed under the MIT software license, see the accompanying 4# file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 6import argparse 7import os 8import subprocess 9import sys 10 11def setup(): 12 global args, workdir 13 programs = ['ruby', 'git', 'make', 'wget', 'curl'] 14 if args.kvm: 15 programs += ['apt-cacher-ng', 'python-vm-builder', 'qemu-kvm', 'qemu-utils'] 16 elif args.docker and not os.path.isfile('/lib/systemd/system/docker.service'): 17 dockers = ['docker.io', 'docker-ce'] 18 for i in dockers: 19 return_code = subprocess.call(['sudo', 'apt-get', 'install', '-qq', i]) 20 if return_code == 0: 21 break 22 if return_code != 0: 23 print('Cannot find any way to install Docker.', file=sys.stderr) 24 sys.exit(1) 25 else: 26 programs += ['apt-cacher-ng', 'lxc', 'debootstrap'] 27 subprocess.check_call(['sudo', 'apt-get', 'install', '-qq'] + programs) 28 if not os.path.isdir('gitian.sigs'): 29 subprocess.check_call(['git', 'clone', 'https://github.com/qtumproject/gitian.sigs.git']) 30 if not os.path.isdir('qtum-detached-sigs'): 31 subprocess.check_call(['git', 'clone', 'https://github.com/qtumproject/qtum-detached-sigs.git']) 32 if not os.path.isdir('gitian-builder'): 33 subprocess.check_call(['git', 'clone', 'https://github.com/devrandom/gitian-builder.git']) 34 if not os.path.isdir('qtum'): 35 subprocess.check_call(['git', 'clone', 'https://github.com/qtumproject/qtum.git']) 36 os.chdir('gitian-builder') 37 make_image_prog = ['bin/make-base-vm', '--suite', 'bionic', '--arch', 'amd64'] 38 if args.docker: 39 make_image_prog += ['--docker'] 40 elif not args.kvm: 41 make_image_prog += ['--lxc'] 42 subprocess.check_call(make_image_prog) 43 os.chdir(workdir) 44 if args.is_bionic and not args.kvm and not args.docker: 45 subprocess.check_call(['sudo', 'sed', '-i', 's/lxcbr0/br0/', '/etc/default/lxc-net']) 46 print('Reboot is required') 47 sys.exit(0) 48 49def build(): 50 global args, workdir 51 52 os.makedirs('qtum-binaries/' + args.version, exist_ok=True) 53 print('\nBuilding Dependencies\n') 54 os.chdir('gitian-builder') 55 os.makedirs('inputs', exist_ok=True) 56 57 subprocess.check_call(['wget', '-O', 'inputs/osslsigncode-2.0.tar.gz', 'https://github.com/mtrojnar/osslsigncode/archive/2.0.tar.gz']) 58 subprocess.check_call(["echo '5a60e0a4b3e0b4d655317b2f12a810211c50242138322b16e7e01c6fbb89d92f inputs/osslsigncode-2.0.tar.gz' | sha256sum -c"], shell=True) 59 subprocess.check_call(['make', '-C', '../qtum/depends', 'download', 'SOURCES_PATH=' + os.getcwd() + '/cache/common']) 60 61 if args.linux: 62 print('\nCompiling ' + args.version + ' Linux') 63 subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'qtum='+args.commit+',cpp-eth-qtum=develop', '--url', 'qtum='+args.url, '../qtum/contrib/gitian-descriptors/gitian-linux.yml']) 64 subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-linux', '--destination', '../gitian.sigs/', '../qtum/contrib/gitian-descriptors/gitian-linux.yml']) 65 subprocess.check_call('mv build/out/qtum-*.tar.gz build/out/src/qtum-*.tar.gz ../qtum-binaries/'+args.version, shell=True) 66 67 if args.windows: 68 print('\nCompiling ' + args.version + ' Windows') 69 subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'qtum='+args.commit+',cpp-eth-qtum=develop', '--url', 'qtum='+args.url, '../qtum/contrib/gitian-descriptors/gitian-win.yml']) 70 subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-win-unsigned', '--destination', '../gitian.sigs/', '../qtum/contrib/gitian-descriptors/gitian-win.yml']) 71 subprocess.check_call('mv build/out/qtum-*-win-unsigned.tar.gz inputs/', shell=True) 72 subprocess.check_call('mv build/out/qtum-*.zip build/out/qtum-*.exe build/out/src/qtum-*.tar.gz ../qtum-binaries/'+args.version, shell=True) 73 74 if args.macos: 75 print('\nCompiling ' + args.version + ' MacOS') 76 subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'qtum='+args.commit+',cpp-eth-qtum=develop', '--url', 'qtum='+args.url, '../qtum/contrib/gitian-descriptors/gitian-osx.yml']) 77 subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-osx-unsigned', '--destination', '../gitian.sigs/', '../qtum/contrib/gitian-descriptors/gitian-osx.yml']) 78 subprocess.check_call('mv build/out/qtum-*-osx-unsigned.tar.gz inputs/', shell=True) 79 subprocess.check_call('mv build/out/qtum-*.tar.gz build/out/qtum-*.dmg build/out/src/qtum-*.tar.gz ../qtum-binaries/'+args.version, shell=True) 80 81 os.chdir(workdir) 82 83 if args.commit_files: 84 print('\nCommitting '+args.version+' Unsigned Sigs\n') 85 os.chdir('gitian.sigs') 86 subprocess.check_call(['git', 'add', args.version+'-linux/'+args.signer]) 87 subprocess.check_call(['git', 'add', args.version+'-win-unsigned/'+args.signer]) 88 subprocess.check_call(['git', 'add', args.version+'-osx-unsigned/'+args.signer]) 89 subprocess.check_call(['git', 'commit', '-m', 'Add '+args.version+' unsigned sigs for '+args.signer]) 90 os.chdir(workdir) 91 92def sign(): 93 global args, workdir 94 os.chdir('gitian-builder') 95 96 if args.windows: 97 print('\nSigning ' + args.version + ' Windows') 98 subprocess.check_call('cp inputs/qtum-' + args.version + '-win-unsigned.tar.gz inputs/qtum-win-unsigned.tar.gz', shell=True) 99 subprocess.check_call(['bin/gbuild', '--skip-image', '--upgrade', '--commit', 'signature='+args.commit, '../qtum/contrib/gitian-descriptors/gitian-win-signer.yml']) 100 subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-win-signed', '--destination', '../gitian.sigs/', '../qtum/contrib/gitian-descriptors/gitian-win-signer.yml']) 101 subprocess.check_call('mv build/out/qtum-*win64-setup.exe ../qtum-binaries/'+args.version, shell=True) 102 103 if args.macos: 104 print('\nSigning ' + args.version + ' MacOS') 105 subprocess.check_call('cp inputs/qtum-' + args.version + '-osx-unsigned.tar.gz inputs/qtum-osx-unsigned.tar.gz', shell=True) 106 subprocess.check_call(['bin/gbuild', '--skip-image', '--upgrade', '--commit', 'signature='+args.commit, '../qtum/contrib/gitian-descriptors/gitian-osx-signer.yml']) 107 subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-osx-signed', '--destination', '../gitian.sigs/', '../qtum/contrib/gitian-descriptors/gitian-osx-signer.yml']) 108 subprocess.check_call('mv build/out/qtum-osx-signed.dmg ../qtum-binaries/'+args.version+'/qtum-'+args.version+'-osx.dmg', shell=True) 109 110 os.chdir(workdir) 111 112 if args.commit_files: 113 print('\nCommitting '+args.version+' Signed Sigs\n') 114 os.chdir('gitian.sigs') 115 subprocess.check_call(['git', 'add', args.version+'-win-signed/'+args.signer]) 116 subprocess.check_call(['git', 'add', args.version+'-osx-signed/'+args.signer]) 117 subprocess.check_call(['git', 'commit', '-a', '-m', 'Add '+args.version+' signed binary sigs for '+args.signer]) 118 os.chdir(workdir) 119 120def verify(): 121 global args, workdir 122 rc = 0 123 os.chdir('gitian-builder') 124 125 print('\nVerifying v'+args.version+' Linux\n') 126 if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-linux', '../qtum/contrib/gitian-descriptors/gitian-linux.yml']): 127 print('Verifying v'+args.version+' Linux FAILED\n') 128 rc = 1 129 130 print('\nVerifying v'+args.version+' Windows\n') 131 if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-win-unsigned', '../qtum/contrib/gitian-descriptors/gitian-win.yml']): 132 print('Verifying v'+args.version+' Windows FAILED\n') 133 rc = 1 134 135 print('\nVerifying v'+args.version+' MacOS\n') 136 if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-osx-unsigned', '../qtum/contrib/gitian-descriptors/gitian-osx.yml']): 137 print('Verifying v'+args.version+' MacOS FAILED\n') 138 rc = 1 139 140 print('\nVerifying v'+args.version+' Signed Windows\n') 141 if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-win-signed', '../qtum/contrib/gitian-descriptors/gitian-win-signer.yml']): 142 print('Verifying v'+args.version+' Signed Windows FAILED\n') 143 rc = 1 144 145 print('\nVerifying v'+args.version+' Signed MacOS\n') 146 if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-osx-signed', '../qtum/contrib/gitian-descriptors/gitian-osx-signer.yml']): 147 print('Verifying v'+args.version+' Signed MacOS FAILED\n') 148 rc = 1 149 150 os.chdir(workdir) 151 return rc 152 153def main(): 154 global args, workdir 155 156 parser = argparse.ArgumentParser(description='Script for running full Gitian builds.') 157 parser.add_argument('-c', '--commit', action='store_true', dest='commit', help='Indicate that the version argument is for a commit or branch') 158 parser.add_argument('-p', '--pull', action='store_true', dest='pull', help='Indicate that the version argument is the number of a github repository pull request') 159 parser.add_argument('-u', '--url', dest='url', default='https://github.com/qtumproject/qtum', help='Specify the URL of the repository. Default is %(default)s') 160 parser.add_argument('-v', '--verify', action='store_true', dest='verify', help='Verify the Gitian build') 161 parser.add_argument('-b', '--build', action='store_true', dest='build', help='Do a Gitian build') 162 parser.add_argument('-s', '--sign', action='store_true', dest='sign', help='Make signed binaries for Windows and MacOS') 163 parser.add_argument('-B', '--buildsign', action='store_true', dest='buildsign', help='Build both signed and unsigned binaries') 164 parser.add_argument('-o', '--os', dest='os', default='lwm', help='Specify which Operating Systems the build is for. Default is %(default)s. l for Linux, w for Windows, m for MacOS') 165 parser.add_argument('-j', '--jobs', dest='jobs', default='2', help='Number of processes to use. Default %(default)s') 166 parser.add_argument('-m', '--memory', dest='memory', default='2000', help='Memory to allocate in MiB. Default %(default)s') 167 parser.add_argument('-k', '--kvm', action='store_true', dest='kvm', help='Use KVM instead of LXC') 168 parser.add_argument('-d', '--docker', action='store_true', dest='docker', help='Use Docker instead of LXC') 169 parser.add_argument('-S', '--setup', action='store_true', dest='setup', help='Set up the Gitian building environment. Only works on Debian-based systems (Ubuntu, Debian)') 170 parser.add_argument('-D', '--detach-sign', action='store_true', dest='detach_sign', help='Create the assert file for detached signing. Will not commit anything.') 171 parser.add_argument('-n', '--no-commit', action='store_false', dest='commit_files', help='Do not commit anything to git') 172 parser.add_argument('signer', nargs='?', help='GPG signer to sign each build assert file') 173 parser.add_argument('version', nargs='?', help='Version number, commit, or branch to build. If building a commit or branch, the -c option must be specified') 174 175 args = parser.parse_args() 176 workdir = os.getcwd() 177 178 args.is_bionic = b'bionic' in subprocess.check_output(['lsb_release', '-cs']) 179 180 if args.kvm and args.docker: 181 raise Exception('Error: cannot have both kvm and docker') 182 183 # Ensure no more than one environment variable for gitian-builder (USE_LXC, USE_VBOX, USE_DOCKER) is set as they 184 # can interfere (e.g., USE_LXC being set shadows USE_DOCKER; for details see gitian-builder/libexec/make-clean-vm). 185 os.environ['USE_LXC'] = '' 186 os.environ['USE_VBOX'] = '' 187 os.environ['USE_DOCKER'] = '' 188 if args.docker: 189 os.environ['USE_DOCKER'] = '1' 190 elif not args.kvm: 191 os.environ['USE_LXC'] = '1' 192 if 'GITIAN_HOST_IP' not in os.environ.keys(): 193 os.environ['GITIAN_HOST_IP'] = '10.0.3.1' 194 if 'LXC_GUEST_IP' not in os.environ.keys(): 195 os.environ['LXC_GUEST_IP'] = '10.0.3.5' 196 197 if args.setup: 198 setup() 199 200 if args.buildsign: 201 args.build = True 202 args.sign = True 203 204 if not args.build and not args.sign and not args.verify: 205 sys.exit(0) 206 207 args.linux = 'l' in args.os 208 args.windows = 'w' in args.os 209 args.macos = 'm' in args.os 210 211 # Disable for MacOS if no SDK found 212 if args.macos and not os.path.isfile('gitian-builder/inputs/MacOSX10.14.sdk.tar.gz'): 213 print('Cannot build for MacOS, SDK does not exist. Will build for other OSes') 214 args.macos = False 215 216 args.sign_prog = 'true' if args.detach_sign else 'gpg --detach-sign' 217 218 script_name = os.path.basename(sys.argv[0]) 219 if not args.signer: 220 print(script_name+': Missing signer') 221 print('Try '+script_name+' --help for more information') 222 sys.exit(1) 223 if not args.version: 224 print(script_name+': Missing version') 225 print('Try '+script_name+' --help for more information') 226 sys.exit(1) 227 228 # Add leading 'v' for tags 229 if args.commit and args.pull: 230 raise Exception('Cannot have both commit and pull') 231 args.commit = ('' if args.commit else 'v') + args.version 232 233 os.chdir('qtum') 234 if args.pull: 235 subprocess.check_call(['git', 'fetch', args.url, 'refs/pull/'+args.version+'/merge']) 236 os.chdir('../gitian-builder/inputs/qtum') 237 subprocess.check_call(['git', 'fetch', args.url, 'refs/pull/'+args.version+'/merge']) 238 args.commit = subprocess.check_output(['git', 'show', '-s', '--format=%H', 'FETCH_HEAD'], universal_newlines=True, encoding='utf8').strip() 239 args.version = 'pull-' + args.version 240 print(args.commit) 241 subprocess.check_call(['git', 'fetch']) 242 subprocess.check_call(['git', 'checkout', args.commit]) 243 os.chdir(workdir) 244 245 os.chdir('gitian-builder') 246 subprocess.check_call(['git', 'pull']) 247 os.chdir(workdir) 248 249 if args.build: 250 build() 251 252 if args.sign: 253 sign() 254 255 if args.verify: 256 os.chdir('gitian.sigs') 257 subprocess.check_call(['git', 'pull']) 258 os.chdir(workdir) 259 sys.exit(verify()) 260 261if __name__ == '__main__': 262 main() 263