mirror of
https://github.com/dashpay/dash.git
synced 2024-12-28 13:32:47 +01:00
6c797b13e8
2ecaf214331b506ebfac4f4922241744357d652b gitian: remove execstack workaround for ricv64 & powerpc64le (fanquake)
5baff2b31840bdbc465f55b875aa6e9480288215 build: use focal in gitian descriptors (fanquake)
Pull request description:
This PR changes the gitian descriptors to use Ubuntu Focal (20.04), over Bionic (18.04), moving from GCC 7.5 to GCC 8.4 for native Linux builds, mingw-w64 GCC 7.3 to mingw-w64 GCC 9.3 for Windows builds, while continuing to use GCC 8.4 for all cross builds and Clang 8.0.0 for macOS builds.
It also drops the `-Wl,-z,noexecstack` workaround we've been using for the riscv64 and powerpc64le hosts, as it's no-longer needed. One new package is installed in the osx build, `libtinfo5`, as libtinfo5.so is required by our downloaded Clang 8.
A bump to Focal will at least be required if we want to update to a newer Qt (5.15, #19716) for 22.0, as we need a newer version of [`g++-mingw-w64`](https://packages.ubuntu.com/focal/g++-mingw-w64-x86-64) and the [`mingw-w64`](https://mingw-w64.org/doku.php) headers. This can still be done while continuing to use GCC 8.4 for Linux builds (see below), however the newer `g++-mingw-w64` will be based off of GCC 9.3.
**Some considerations**
GCC 9 is affected by #20005 "memcmp with constants that contain zero bytes are broken in GCC", and the newer `g++-mingw-w64` will be based off of GCC 9.3.
The `--no-*` variants of the Windows linker flags (i.e `--no-dynamicbase`) we use to [test our `security-check.py` script](16b784d953/contrib/devtools/test-security-check.py (L53)
) are not patched into the mingw binutils in Focal (they have been re-added in Groovy (20.10)). This isn't currently an issue, however, we might add a call to `test-security-check` for Guix (#20980), and if we wanted to do the same for gitian, it would not work. Note how it's quite "easy" for us to apply the `--no-*` variant patch to our Guix build; it would be quite a bit harder to do in Gitian.
Gitian Builds @ 2ecaf214331b506ebfac4f4922241744357d652b
#### Linux
```bash
8882ea78486fbae4fac574b9089eb1107c6372d0dd7dfcda4f0f930576f9d6c1 bitcoin-2ecaf214331b-aarch64-linux-gnu-debug.tar.gz
50a9e30943b4eee5163edff3331241e745ff32a2c4463c21a6fdc5986e2d0383 bitcoin-2ecaf214331b-aarch64-linux-gnu.tar.gz
ec4e55a447fddf033fee33cd5f22bfeda3c3612f059194bcf6238859f7989d7a bitcoin-2ecaf214331b-arm-linux-gnueabihf-debug.tar.gz
444fe1b3b933c00bcbd4a9d86888cff3b61c1215b1debccd2843e842d1224777 bitcoin-2ecaf214331b-arm-linux-gnueabihf.tar.gz
88e486ff465980dc1a4aab9687d142ec6f727ed2c52cf539f69db2877dee83b2 bitcoin-2ecaf214331b-powerpc64-linux-gnu-debug.tar.gz
66144ac264c65cada9d86446e6026c85b04fb88198b8f41b42840f6031db3e6c bitcoin-2ecaf214331b-powerpc64-linux-gnu.tar.gz
34bcc13d78d929d575e34e77a6672f23ca7ea23230b28ec2eed563889352ba86 bitcoin-2ecaf214331b-powerpc64le-linux-gnu-debug.tar.gz
b4c5f959664f3063df4330edfe343c17120eb6b556ee1c15c4aeb2c1c54ffd49 bitcoin-2ecaf214331b-powerpc64le-linux-gnu.tar.gz
918fa72ab6f6ebce4e9663c93f72fe26651c260477cbb54749f7eb61438b5cc1 bitcoin-2ecaf214331b-riscv64-linux-gnu-debug.tar.gz
f704f9f8c053ffe37d854e2e81e0f4c0614c435dad7f5d82518c681b73a76ae6 bitcoin-2ecaf214331b-riscv64-linux-gnu.tar.gz
b59e3a62f1df9d79f30e916b3c9655f654036fe3a420040c53acc8dd9f4162c5 bitcoin-2ecaf214331b-x86_64-linux-gnu-debug.tar.gz
a4dc9ca877cc97544e65db11be38406d16f15d74fcdcd2318bb92474729bc60d bitcoin-2ecaf214331b-x86_64-linux-gnu.tar.gz
b40ba2d5da498330ade92a4ccebcceb1452b94c8ffeacb336f87e93b5c88d8af src/bitcoin-2ecaf214331b.tar.gz
af6ebc91147778e4e6705eade62608dde4d6e60522d79087fa9129bdb7c01199 bitcoin-core-linux-22-res.yml
```
#### Windows
```bash
121a3970a6911cb8c453b2ce37d03f6cbb43333e29db8fa516c68563fb367f43 bitcoin-2ecaf214331b-win-unsigned.tar.gz
6294e9efebe935092f9ba119dc60ad4094f18b51c4181324e54d3057524d6101 bitcoin-2ecaf214331b-win64-debug.zip
5b5a236b63e67f5f6c07ad9aa716aa7b72fb63722c96798b332c6d164738f9cf bitcoin-2ecaf214331b-win64-setup-unsigned.exe
c1fa5894c5e02a201637567c80b9bde9024f44673dcd06fd4d489c1709179279 bitcoin-2ecaf214331b-win64.zip
b40ba2d5da498330ade92a4ccebcceb1452b94c8ffeacb336f87e93b5c88d8af src/bitcoin-2ecaf214331b.tar.gz
665fd7eb61aed368150db58a254f15fb5efb51a4efa5abcc52571cb7a1a5de22 bitcoin-core-win-22-res.yml
```
#### macOS
```bash
6a1deae7662aa782baa82a42590f862c6bcdc4f4e38daa9b8c2a9eed1fbb5397 bitcoin-2ecaf214331b-osx-unsigned.dmg
1ee843266e84928a4323fa255c833528c2617a2c9fd2f98fb26ba19bbfc1227b bitcoin-2ecaf214331b-osx-unsigned.tar.gz
097b64dadc167d8e5b733421bf1541a40760ad952990f7cf3f35adc6ae2616d0 bitcoin-2ecaf214331b-osx64.tar.gz
b40ba2d5da498330ade92a4ccebcceb1452b94c8ffeacb336f87e93b5c88d8af src/bitcoin-2ecaf214331b.tar.gz
6e378fb543928e40c7119b96be6ff773d38506a9a888f8b02c7f1b8a0801a80e bitcoin-core-osx-22-res.yml
```
ACKs for top commit:
laanwj:
Build script changes review ACK 2ecaf214331b506ebfac4f4922241744357d652b
Tree-SHA512: 975d5830b787d2e08988f43cbc6e839294171c1d94c8219636308b05f9b77041421612ae67be24a631674670cfc9c2d96d8177f2b3158a78fc3deea19631febf
263 lines
14 KiB
Python
Executable File
263 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
def setup():
|
|
global args, workdir
|
|
programs = ['ruby', 'git', 'make', 'wget', 'curl']
|
|
if args.kvm:
|
|
programs += ['apt-cacher-ng', 'python-vm-builder', 'qemu-kvm', 'qemu-utils']
|
|
elif args.docker:
|
|
if not os.path.isfile('/lib/systemd/system/docker.service'):
|
|
dockers = ['docker.io', 'docker-ce']
|
|
for i in dockers:
|
|
return_code = subprocess.call(['sudo', 'apt-get', 'install', '-qq', i])
|
|
if return_code == 0:
|
|
break
|
|
if return_code != 0:
|
|
print('Cannot find any way to install Docker.', file=sys.stderr)
|
|
sys.exit(1)
|
|
else:
|
|
programs += ['apt-cacher-ng', 'lxc', 'debootstrap']
|
|
subprocess.check_call(['sudo', 'apt-get', 'install', '-qq'] + programs)
|
|
if not os.path.isdir('gitian.sigs'):
|
|
subprocess.check_call(['git', 'clone', 'https://github.com/dashpay/gitian.sigs.git'])
|
|
if not os.path.isdir('dash-detached-sigs'):
|
|
subprocess.check_call(['git', 'clone', 'https://github.com/dashpay/dash-detached-sigs.git'])
|
|
if not os.path.isdir('gitian-builder'):
|
|
subprocess.check_call(['git', 'clone', 'https://github.com/devrandom/gitian-builder.git'])
|
|
if not os.path.isdir('dash'):
|
|
subprocess.check_call(['git', 'clone', 'https://github.com/dashpay/dash.git'])
|
|
os.chdir('gitian-builder')
|
|
make_image_prog = ['bin/make-base-vm', '--suite', 'focal', '--arch', 'amd64']
|
|
if args.docker:
|
|
make_image_prog += ['--docker']
|
|
elif args.lxc:
|
|
make_image_prog += ['--lxc', '--disksize', '13000']
|
|
subprocess.check_call(make_image_prog)
|
|
os.chdir(workdir)
|
|
if args.is_focal and not args.kvm and not args.docker:
|
|
subprocess.check_call(['sudo', 'sed', '-i', 's/lxcbr0/br0/', '/etc/default/lxc-net'])
|
|
print('Reboot is required')
|
|
sys.exit(0)
|
|
|
|
def build():
|
|
global args, workdir
|
|
|
|
os.makedirs('dashcore-binaries/' + args.version, exist_ok=True)
|
|
print('\nBuilding Dependencies\n')
|
|
os.chdir('gitian-builder')
|
|
os.makedirs('inputs', exist_ok=True)
|
|
|
|
subprocess.check_call(['wget', '-O', 'inputs/osslsigncode-2.0.tar.gz', 'https://github.com/mtrojnar/osslsigncode/archive/2.0.tar.gz'])
|
|
subprocess.check_call(["echo '5a60e0a4b3e0b4d655317b2f12a810211c50242138322b16e7e01c6fbb89d92f inputs/osslsigncode-2.0.tar.gz' | sha256sum -c"], shell=True)
|
|
subprocess.check_call(['make', '-C', '../dash/depends', 'download', 'SOURCES_PATH=' + os.getcwd() + '/cache/common'])
|
|
|
|
if args.linux:
|
|
print('\nCompiling ' + args.version + ' Linux')
|
|
subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'dash='+args.commit, '--url', 'dash='+args.url, '../dash/contrib/gitian-descriptors/gitian-linux.yml'])
|
|
subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-linux', '--destination', '../gitian.sigs/', '../dash/contrib/gitian-descriptors/gitian-linux.yml'])
|
|
subprocess.check_call('mv build/out/dashcore-*.tar.gz build/out/src/dashcore-*.tar.gz ../dashcore-binaries/'+args.version, shell=True)
|
|
|
|
if args.windows:
|
|
print('\nCompiling ' + args.version + ' Windows')
|
|
subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'dash='+args.commit, '--url', 'dash='+args.url, '../dash/contrib/gitian-descriptors/gitian-win.yml'])
|
|
subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-win-unsigned', '--destination', '../gitian.sigs/', '../dash/contrib/gitian-descriptors/gitian-win.yml'])
|
|
subprocess.check_call('mv build/out/dashcore-*-win-unsigned.tar.gz inputs/', shell=True)
|
|
subprocess.check_call('mv build/out/dashcore-*.zip build/out/dashcore-*.exe build/out/src/dashcore-*.tar.gz ../dashcore-binaries/'+args.version, shell=True)
|
|
|
|
if args.macos:
|
|
print('\nCompiling ' + args.version + ' MacOS')
|
|
subprocess.check_call(['wget', '-N', '-P', 'inputs', 'https://bitcoincore.org/depends-sources/sdks/Xcode-12.1-12A7403-extracted-SDK-with-libcxx-headers.tar.gz'])
|
|
subprocess.check_output(["echo 'be17f48fd0b08fb4dcd229f55a6ae48d9f781d210839b4ea313ef17dd12d6ea5 inputs/Xcode-12.1-12A7403-extracted-SDK-with-libcxx-headers.tar.gz' | sha256sum -c"], shell=True)
|
|
subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'dash='+args.commit, '--url', 'dash='+args.url, '../dash/contrib/gitian-descriptors/gitian-osx.yml'])
|
|
subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-osx-unsigned', '--destination', '../gitian.sigs/', '../dash/contrib/gitian-descriptors/gitian-osx.yml'])
|
|
subprocess.check_call('mv build/out/dashcore-*-osx-unsigned.tar.gz inputs/', shell=True)
|
|
subprocess.check_call('mv build/out/dashcore-*.tar.gz build/out/dashcore-*.dmg build/out/src/dashcore-*.tar.gz ../dashcore-binaries/'+args.version, shell=True)
|
|
|
|
os.chdir(workdir)
|
|
|
|
if args.commit_files:
|
|
print('\nCommitting '+args.version+' Unsigned Sigs\n')
|
|
os.chdir('gitian.sigs')
|
|
subprocess.check_call(['git', 'add', args.version+'-linux/'+args.signer])
|
|
subprocess.check_call(['git', 'add', args.version+'-win-unsigned/'+args.signer])
|
|
subprocess.check_call(['git', 'add', args.version+'-osx-unsigned/'+args.signer])
|
|
subprocess.check_call(['git', 'commit', '-m', 'Add '+args.version+' unsigned sigs for '+args.signer])
|
|
os.chdir(workdir)
|
|
|
|
def sign():
|
|
global args, workdir
|
|
os.chdir('gitian-builder')
|
|
|
|
if args.windows:
|
|
print('\nSigning ' + args.version + ' Windows')
|
|
subprocess.check_call('cp inputs/dashcore-' + args.version + '-win-unsigned.tar.gz inputs/dashcore-win-unsigned.tar.gz', shell=True)
|
|
subprocess.check_call(['bin/gbuild', '--skip-image', '--upgrade', '--commit', 'signature='+args.commit, '../dash/contrib/gitian-descriptors/gitian-win-signer.yml'])
|
|
subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-win-signed', '--destination', '../gitian.sigs/', '../dash/contrib/gitian-descriptors/gitian-win-signer.yml'])
|
|
subprocess.check_call('mv build/out/dashcore-*win64-setup.exe ../dashcore-binaries/'+args.version, shell=True)
|
|
|
|
if args.macos:
|
|
print('\nSigning ' + args.version + ' MacOS')
|
|
subprocess.check_call('cp inputs/dashcore-' + args.version + '-osx-unsigned.tar.gz inputs/dashcore-osx-unsigned.tar.gz', shell=True)
|
|
subprocess.check_call(['bin/gbuild', '--skip-image', '--upgrade', '--commit', 'signature='+args.commit, '../dash/contrib/gitian-descriptors/gitian-osx-signer.yml'])
|
|
subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-osx-signed', '--destination', '../gitian.sigs/', '../dash/contrib/gitian-descriptors/gitian-osx-signer.yml'])
|
|
subprocess.check_call('mv build/out/dashcore-osx-signed.dmg ../dashcore-binaries/'+args.version+'/dashcore-'+args.version+'-osx.dmg', shell=True)
|
|
|
|
os.chdir(workdir)
|
|
|
|
if args.commit_files:
|
|
print('\nCommitting '+args.version+' Signed Sigs\n')
|
|
os.chdir('gitian.sigs')
|
|
subprocess.check_call(['git', 'add', args.version+'-win-signed/'+args.signer])
|
|
subprocess.check_call(['git', 'add', args.version+'-osx-signed/'+args.signer])
|
|
subprocess.check_call(['git', 'commit', '-a', '-m', 'Add '+args.version+' signed binary sigs for '+args.signer])
|
|
os.chdir(workdir)
|
|
|
|
def verify():
|
|
global args, workdir
|
|
rc = 0
|
|
os.chdir('gitian-builder')
|
|
|
|
print('\nVerifying v'+args.version+' Linux\n')
|
|
if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-linux', '../dash/contrib/gitian-descriptors/gitian-linux.yml']):
|
|
print('Verifying v'+args.version+' Linux FAILED\n')
|
|
rc = 1
|
|
|
|
print('\nVerifying v'+args.version+' Windows\n')
|
|
if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-win-unsigned', '../dash/contrib/gitian-descriptors/gitian-win.yml']):
|
|
print('Verifying v'+args.version+' Windows FAILED\n')
|
|
rc = 1
|
|
|
|
print('\nVerifying v'+args.version+' MacOS\n')
|
|
if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-osx-unsigned', '../dash/contrib/gitian-descriptors/gitian-osx.yml']):
|
|
print('Verifying v'+args.version+' MacOS FAILED\n')
|
|
rc = 1
|
|
|
|
print('\nVerifying v'+args.version+' Signed Windows\n')
|
|
if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-win-signed', '../dash/contrib/gitian-descriptors/gitian-win-signer.yml']):
|
|
print('Verifying v'+args.version+' Signed Windows FAILED\n')
|
|
rc = 1
|
|
|
|
print('\nVerifying v'+args.version+' Signed MacOS\n')
|
|
if subprocess.call(['bin/gverify', '-v', '-d', '../gitian.sigs/', '-r', args.version+'-osx-signed', '../dash/contrib/gitian-descriptors/gitian-osx-signer.yml']):
|
|
print('Verifying v'+args.version+' Signed MacOS FAILED\n')
|
|
rc = 1
|
|
|
|
os.chdir(workdir)
|
|
return rc
|
|
|
|
def main():
|
|
global args, workdir
|
|
|
|
parser = argparse.ArgumentParser(description='Script for running full Gitian builds.')
|
|
parser.add_argument('-c', '--commit', action='store_true', dest='commit', help='Indicate that the version argument is for a commit or branch')
|
|
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')
|
|
parser.add_argument('-u', '--url', dest='url', default='https://github.com/dashpay/dash', help='Specify the URL of the repository. Default is %(default)s')
|
|
parser.add_argument('-v', '--verify', action='store_true', dest='verify', help='Verify the Gitian build')
|
|
parser.add_argument('-b', '--build', action='store_true', dest='build', help='Do a Gitian build')
|
|
parser.add_argument('-s', '--sign', action='store_true', dest='sign', help='Make signed binaries for Windows and MacOS')
|
|
parser.add_argument('-B', '--buildsign', action='store_true', dest='buildsign', help='Build both signed and unsigned binaries')
|
|
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')
|
|
parser.add_argument('-j', '--jobs', dest='jobs', default='2', help='Number of processes to use. Default %(default)s')
|
|
parser.add_argument('-m', '--memory', dest='memory', default='2000', help='Memory to allocate in MiB. Default %(default)s')
|
|
parser.add_argument('-V', '--virtualization', dest='virtualization', default='docker', help='Specify virtualization technology to use: lxc for LXC, kvm for KVM, docker for Docker. Default is %(default)s')
|
|
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)')
|
|
parser.add_argument('-D', '--detach-sign', action='store_true', dest='detach_sign', help='Create the assert file for detached signing. Will not commit anything.')
|
|
parser.add_argument('-n', '--no-commit', action='store_false', dest='commit_files', help='Do not commit anything to git')
|
|
parser.add_argument('signer', nargs='?', help='GPG signer to sign each build assert file')
|
|
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')
|
|
|
|
args = parser.parse_args()
|
|
workdir = os.getcwd()
|
|
|
|
args.is_focal = b'focal' in subprocess.check_output(['lsb_release', '-cs'])
|
|
|
|
args.lxc = (args.virtualization == 'lxc')
|
|
args.kvm = (args.virtualization == 'kvm')
|
|
args.docker = (args.virtualization == 'docker')
|
|
|
|
script_name = os.path.basename(sys.argv[0])
|
|
if not args.lxc and not args.kvm and not args.docker:
|
|
print(script_name+': Wrong virtualization option.')
|
|
print('Try '+script_name+' --help for more information')
|
|
sys.exit(1)
|
|
|
|
# Ensure no more than one environment variable for gitian-builder (USE_LXC, USE_VBOX, USE_DOCKER) is set as they
|
|
# can interfere (e.g., USE_LXC being set shadows USE_DOCKER; for details see gitian-builder/libexec/make-clean-vm).
|
|
os.environ['USE_LXC'] = ''
|
|
os.environ['USE_VBOX'] = ''
|
|
os.environ['USE_DOCKER'] = ''
|
|
if args.docker:
|
|
os.environ['USE_DOCKER'] = '1'
|
|
elif not args.kvm:
|
|
os.environ['USE_LXC'] = '1'
|
|
if 'GITIAN_HOST_IP' not in os.environ.keys():
|
|
os.environ['GITIAN_HOST_IP'] = '10.0.3.1'
|
|
if 'LXC_GUEST_IP' not in os.environ.keys():
|
|
os.environ['LXC_GUEST_IP'] = '10.0.3.5'
|
|
|
|
if args.setup:
|
|
setup()
|
|
|
|
if args.buildsign:
|
|
args.build = True
|
|
args.sign = True
|
|
|
|
if not args.build and not args.sign and not args.verify:
|
|
sys.exit(0)
|
|
|
|
args.linux = 'l' in args.os
|
|
args.windows = 'w' in args.os
|
|
args.macos = 'm' in args.os
|
|
|
|
args.sign_prog = 'true' if args.detach_sign else 'gpg --detach-sign'
|
|
|
|
if not args.signer:
|
|
print(script_name+': Missing signer')
|
|
print('Try '+script_name+' --help for more information')
|
|
sys.exit(1)
|
|
if not args.version:
|
|
print(script_name+': Missing version')
|
|
print('Try '+script_name+' --help for more information')
|
|
sys.exit(1)
|
|
|
|
# Add leading 'v' for tags
|
|
if args.commit and args.pull:
|
|
raise Exception('Cannot have both commit and pull')
|
|
args.commit = ('' if args.commit else 'v') + args.version
|
|
|
|
os.chdir('dash')
|
|
if args.pull:
|
|
subprocess.check_call(['git', 'fetch', args.url, 'refs/pull/'+args.version+'/merge'])
|
|
os.chdir('../gitian-builder/inputs/dash')
|
|
subprocess.check_call(['git', 'fetch', args.url, 'refs/pull/'+args.version+'/merge'])
|
|
args.commit = subprocess.check_output(['git', 'show', '-s', '--format=%H', 'FETCH_HEAD'], universal_newlines=True, encoding='utf8').strip()
|
|
args.version = 'pull-' + args.version
|
|
print(args.commit)
|
|
subprocess.check_call(['git', 'fetch'])
|
|
subprocess.check_call(['git', 'checkout', args.commit])
|
|
os.chdir(workdir)
|
|
|
|
os.chdir('gitian-builder')
|
|
subprocess.check_call(['git', 'pull'])
|
|
os.chdir(workdir)
|
|
|
|
if args.build:
|
|
build()
|
|
|
|
if args.sign:
|
|
sign()
|
|
|
|
if args.verify:
|
|
os.chdir('gitian.sigs')
|
|
subprocess.check_call(['git', 'pull'])
|
|
os.chdir(workdir)
|
|
sys.exit(verify())
|
|
|
|
if __name__ == '__main__':
|
|
main()
|