187c131baSJan Høydahl#!/usr/bin/env python3 287c131baSJan Høydahl# -*- coding: utf-8 -*- 3d6ab323dSMichael McCandless# Licensed to the Apache Software Foundation (ASF) under one or more 4d6ab323dSMichael McCandless# contributor license agreements. See the NOTICE file distributed with 5d6ab323dSMichael McCandless# this work for additional information regarding copyright ownership. 6d6ab323dSMichael McCandless# The ASF licenses this file to You under the Apache License, Version 2.0 7d6ab323dSMichael McCandless# (the "License"); you may not use this file except in compliance with 8d6ab323dSMichael McCandless# the License. You may obtain a copy of the License at 9d6ab323dSMichael McCandless# 10d6ab323dSMichael McCandless# http://www.apache.org/licenses/LICENSE-2.0 11d6ab323dSMichael McCandless# 12d6ab323dSMichael McCandless# Unless required by applicable law or agreed to in writing, software 13d6ab323dSMichael McCandless# distributed under the License is distributed on an "AS IS" BASIS, 14d6ab323dSMichael McCandless# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15d6ab323dSMichael McCandless# See the License for the specific language governing permissions and 16d6ab323dSMichael McCandless# limitations under the License. 17d6ab323dSMichael McCandless 1880e60002SRyan Ernstimport argparse 19d6ab323dSMichael McCandlessimport datetime 20d6ab323dSMichael McCandlessimport re 219c56dccfSMichael McCandlessimport time 22d6ab323dSMichael McCandlessimport os 23d6ab323dSMichael McCandlessimport sys 249c56dccfSMichael McCandlessimport subprocess 25df1775ffSJan Høydahlfrom subprocess import TimeoutExpired 2680e60002SRyan Ernstimport textwrap 27d60849f3SSteve Roweimport urllib.request, urllib.error, urllib.parse 28d60849f3SSteve Roweimport xml.etree.ElementTree as ET 29d6ab323dSMichael McCandless 3008e38d34SMike Drobimport scriptutil 3108e38d34SMike Drob 32d6ab323dSMichael McCandlessLOG = '/tmp/release.log' 33cdfa11b1SJan Høydahldev_mode = False 34d6ab323dSMichael McCandless 35d6ab323dSMichael McCandlessdef log(msg): 360178e8e6SSteven Rowe f = open(LOG, mode='ab') 370178e8e6SSteven Rowe f.write(msg.encode('utf-8')) 38d6ab323dSMichael McCandless f.close() 39d6ab323dSMichael McCandless 40d6ab323dSMichael McCandlessdef run(command): 41d6ab323dSMichael McCandless log('\n\n%s: RUN: %s\n' % (datetime.datetime.now(), command)) 42d6ab323dSMichael McCandless if os.system('%s >> %s 2>&1' % (command, LOG)): 43d6ab323dSMichael McCandless msg = ' FAILED: %s [see log %s]' % (command, LOG) 440178e8e6SSteven Rowe print(msg) 45d6ab323dSMichael McCandless raise RuntimeError(msg) 46d6ab323dSMichael McCandless 47cdfa11b1SJan Høydahl 489c56dccfSMichael McCandlessdef runAndSendGPGPassword(command, password): 49fdff37f3SShalin Shekhar Mangar p = subprocess.Popen(command, shell=True, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE) 509c56dccfSMichael McCandless f = open(LOG, 'ab') 519c56dccfSMichael McCandless while True: 52d0b01a2bSShalin Shekhar Mangar p.stdout.flush() 539c56dccfSMichael McCandless line = p.stdout.readline() 549c56dccfSMichael McCandless if len(line) == 0: 559c56dccfSMichael McCandless break 569c56dccfSMichael McCandless f.write(line) 579c56dccfSMichael McCandless if line.find(b'Enter GPG keystore password:') != -1: 589c56dccfSMichael McCandless time.sleep(1.0) 599c56dccfSMichael McCandless p.stdin.write((password + '\n').encode('UTF-8')) 609c56dccfSMichael McCandless p.stdin.write('\n'.encode('UTF-8')) 619c56dccfSMichael McCandless 622a13e783SSteve Rowe try: 632a13e783SSteve Rowe result = p.wait(timeout=120) 642a13e783SSteve Rowe if result != 0: 659c56dccfSMichael McCandless msg = ' FAILED: %s [see log %s]' % (command, LOG) 669c56dccfSMichael McCandless print(msg) 679c56dccfSMichael McCandless raise RuntimeError(msg) 682a13e783SSteve Rowe except TimeoutExpired: 692a13e783SSteve Rowe msg = ' FAILED: %s [timed out after 2 minutes; see log %s]' % (command, LOG) 702a13e783SSteve Rowe print(msg) 712a13e783SSteve Rowe raise RuntimeError(msg) 729c56dccfSMichael McCandless 73982ee393SJan Høydahldef load(urlString, encoding="utf-8"): 74d60849f3SSteve Rowe try: 75982ee393SJan Høydahl content = urllib.request.urlopen(urlString).read().decode(encoding) 76d60849f3SSteve Rowe except Exception as e: 77d60849f3SSteve Rowe print('Retrying download of url %s after exception: %s' % (urlString, e)) 78982ee393SJan Høydahl content = urllib.request.urlopen(urlString).read().decode(encoding) 79d60849f3SSteve Rowe return content 80d60849f3SSteve Rowe 81f8be973bSMike McCandlessdef getGitRev(): 82cdfa11b1SJan Høydahl if not dev_mode: 83f8be973bSMike McCandless status = os.popen('git status').read().strip() 84a6e14ec6SIshan Chattopadhyaya if 'nothing to commit, working directory clean' not in status and 'nothing to commit, working tree clean' not in status: 85de9d4ac3SMike McCandless raise RuntimeError('git clone is dirty:\n\n%s' % status) 86d23f37d0SDawid Weiss if 'Your branch is ahead of' in status: 87d23f37d0SDawid Weiss raise RuntimeError('Your local branch is ahead of the remote? git status says:\n%s' % status) 887509b9c9SMike McCandless print(' git clone is clean') 89cdfa11b1SJan Høydahl else: 90cdfa11b1SJan Høydahl print(' Ignoring dirty git clone due to dev-mode') 917509b9c9SMike McCandless return os.popen('git rev-parse HEAD').read().strip() 92d6ab323dSMichael McCandless 93cdfa11b1SJan Høydahl 94cdfa11b1SJan Høydahldef prepare(root, version, gpg_key_id, gpg_password, gpg_home=None, sign_gradle=False): 950178e8e6SSteven Rowe print() 960178e8e6SSteven Rowe print('Prepare release...') 97d6ab323dSMichael McCandless if os.path.exists(LOG): 98d6ab323dSMichael McCandless os.remove(LOG) 99d6ab323dSMichael McCandless 100cdfa11b1SJan Høydahl if not dev_mode: 101d6ab323dSMichael McCandless os.chdir(root) 1027263491dSnknize print(' git pull...') 1037263491dSnknize run('git pull') 104cdfa11b1SJan Høydahl else: 105cdfa11b1SJan Høydahl print(' Development mode, not running git pull') 106d6ab323dSMichael McCandless 107f8be973bSMike McCandless rev = getGitRev() 108f8be973bSMike McCandless print(' git rev: %s' % rev) 109f8be973bSMike McCandless log('\nGIT rev: %s\n' % rev) 110d6ab323dSMichael McCandless 111d60849f3SSteve Rowe print(' Check DOAP files') 112d60849f3SSteve Rowe checkDOAPfiles(version) 113d60849f3SSteve Rowe 114cdfa11b1SJan Høydahl if not dev_mode: 115*f605b4a6SAdrien Grand print(' ./gradlew --no-daemon clean check') 116*f605b4a6SAdrien Grand run('./gradlew --no-daemon clean check') 117cdfa11b1SJan Høydahl else: 118cdfa11b1SJan Høydahl print(' skipping precommit check due to dev-mode') 119d6ab323dSMichael McCandless 1203134f10aSMike Drob print(' prepare-release') 121f5486d13SJan Høydahl cmd = './gradlew --no-daemon assembleRelease' \ 122cdfa11b1SJan Høydahl ' -Dversion.release=%s' % version 123cdfa11b1SJan Høydahl if dev_mode: 124cdfa11b1SJan Høydahl cmd += ' -Pvalidation.git.failOnModified=false' 125cdfa11b1SJan Høydahl if gpg_key_id is not None: 126f5486d13SJan Høydahl cmd += ' -Psign --max-workers 2' 127cdfa11b1SJan Høydahl if sign_gradle: 128cdfa11b1SJan Høydahl print(" Signing method is gradle java-plugin") 129cdfa11b1SJan Høydahl cmd += ' -Psigning.keyId="%s"' % gpg_key_id 130cdfa11b1SJan Høydahl if gpg_home is not None: 131cdfa11b1SJan Høydahl cmd += ' -Psigning.secretKeyRingFile="%s"' % os.path.join(gpg_home, 'secring.gpg') 132cdfa11b1SJan Høydahl if gpg_password is not None: 133cdfa11b1SJan Høydahl # Pass gpg passphrase as env.var to gradle rather than as plaintext argument 134cdfa11b1SJan Høydahl os.environ['ORG_GRADLE_PROJECT_signingPassword'] = gpg_password 135cdfa11b1SJan Høydahl else: 136cdfa11b1SJan Høydahl print(" Signing method is gpg tool") 137cdfa11b1SJan Høydahl cmd += ' -PuseGpg -Psigning.gnupg.keyName="%s"' % gpg_key_id 138cdfa11b1SJan Høydahl if gpg_home is not None: 139cdfa11b1SJan Høydahl cmd += ' -Psigning.gnupg.homeDir="%s"' % gpg_home 1409c56dccfSMichael McCandless 141cdfa11b1SJan Høydahl print(" Running: %s" % cmd) 142cdfa11b1SJan Høydahl if gpg_password is not None: 143cdfa11b1SJan Høydahl runAndSendGPGPassword(cmd, gpg_password) 1449c56dccfSMichael McCandless else: 145879e8250SMichael McCandless run(cmd) 1469c56dccfSMichael McCandless 1470178e8e6SSteven Rowe print(' done!') 1480178e8e6SSteven Rowe print() 149d6ab323dSMichael McCandless return rev 150d6ab323dSMichael McCandless 151d60849f3SSteve RowereVersion1 = re.compile(r'\>(\d+)\.(\d+)\.(\d+)(-alpha|-beta)?/\<', re.IGNORECASE) 152d60849f3SSteve RowereVersion2 = re.compile(r'-(\d+)\.(\d+)\.(\d+)(-alpha|-beta)?\.zip<', re.IGNORECASE) 153d60849f3SSteve RowereDoapRevision = re.compile(r'(\d+)\.(\d+)(?:\.(\d+))?(-alpha|-beta)?', re.IGNORECASE) 154d60849f3SSteve Rowedef checkDOAPfiles(version): 155674b66ddSJan Høydahl # In Lucene DOAP file, verify presence of all releases less than the one being produced. 156d60849f3SSteve Rowe errorMessages = [] 157674b66ddSJan Høydahl for product in ['lucene']: 158d60849f3SSteve Rowe url = 'https://archive.apache.org/dist/lucene/%s' % ('java' if product == 'lucene' else product) 159d60849f3SSteve Rowe distpage = load(url) 160d60849f3SSteve Rowe releases = set() 161d60849f3SSteve Rowe for regex in reVersion1, reVersion2: 162d60849f3SSteve Rowe for tup in regex.findall(distpage): 163d60849f3SSteve Rowe if tup[0] in ('1', '2'): # Ignore 1.X and 2.X releases 164d60849f3SSteve Rowe continue 165d60849f3SSteve Rowe releases.add(normalizeVersion(tup)) 166d60849f3SSteve Rowe doapNS = '{http://usefulinc.com/ns/doap#}' 167d60849f3SSteve Rowe xpathRevision = '{0}Project/{0}release/{0}Version/{0}revision'.format(doapNS) 168d60849f3SSteve Rowe doapFile = "dev-tools/doap/%s.rdf" % product 169d60849f3SSteve Rowe treeRoot = ET.parse(doapFile).getroot() 170d60849f3SSteve Rowe doapRevisions = set() 171d60849f3SSteve Rowe for revision in treeRoot.findall(xpathRevision): 172d60849f3SSteve Rowe match = reDoapRevision.match(revision.text) 173d60849f3SSteve Rowe if (match is not None): 174d60849f3SSteve Rowe if (match.group(1) not in ('0', '1', '2')): # Ignore 0.X, 1.X and 2.X revisions 175d60849f3SSteve Rowe doapRevisions.add(normalizeVersion(match.groups())) 176d60849f3SSteve Rowe else: 177d60849f3SSteve Rowe errorMessages.append('ERROR: Failed to parse revision: %s in %s' % (revision.text, doapFile)) 178d60849f3SSteve Rowe missingDoapRevisions = set() 179d60849f3SSteve Rowe for release in releases: 180d60849f3SSteve Rowe if release not in doapRevisions and release < version: # Ignore releases greater than the one being produced 181d60849f3SSteve Rowe missingDoapRevisions.add(release) 182d60849f3SSteve Rowe if len(missingDoapRevisions) > 0: 183d60849f3SSteve Rowe errorMessages.append('ERROR: Missing revision(s) in %s: %s' % (doapFile, ', '.join(sorted(missingDoapRevisions)))) 184d60849f3SSteve Rowe if (len(errorMessages) > 0): 185d60849f3SSteve Rowe raise RuntimeError('\n%s\n(Hint: copy/paste from the stable branch version of the file(s).)' 186d60849f3SSteve Rowe % '\n'.join(errorMessages)) 187d60849f3SSteve Rowe 188d60849f3SSteve Rowedef normalizeVersion(tup): 189d60849f3SSteve Rowe suffix = '' 190d60849f3SSteve Rowe if tup[-1] is not None and tup[-1].lower() == '-alpha': 191d60849f3SSteve Rowe tup = tup[:(len(tup) - 1)] 192d60849f3SSteve Rowe suffix = '-ALPHA' 193d60849f3SSteve Rowe elif tup[-1] is not None and tup[-1].lower() == '-beta': 194d60849f3SSteve Rowe tup = tup[:(len(tup) - 1)] 195d60849f3SSteve Rowe suffix = '-BETA' 196d60849f3SSteve Rowe while tup[-1] in ('', None): 197d60849f3SSteve Rowe tup = tup[:(len(tup) - 1)] 198d60849f3SSteve Rowe while len(tup) < 3: 199d60849f3SSteve Rowe tup = tup + ('0',) 200d60849f3SSteve Rowe return '.'.join(tup) + suffix 201d60849f3SSteve Rowe 202cdfa11b1SJan Høydahl 2030544819bSDawid Weissdef pushLocal(version, root, rcNum, localDir): 2040178e8e6SSteven Rowe print('Push local [%s]...' % localDir) 205879e8250SMichael McCandless os.makedirs(localDir) 206879e8250SMichael McCandless 2070544819bSDawid Weiss lucene_dist_dir = '%s/lucene/distribution/build/release' % root 2080544819bSDawid Weiss rev = open('%s/lucene/distribution/build/release/.gitrev' % root, encoding='UTF-8').read() 2090544819bSDawid Weiss 2100544819bSDawid Weiss dir = 'lucene-%s-RC%d-rev-%s' % (version, rcNum, rev) 211879e8250SMichael McCandless os.makedirs('%s/%s/lucene' % (localDir, dir)) 2120178e8e6SSteven Rowe print(' Lucene') 21308e38d34SMike Drob os.chdir(lucene_dist_dir) 2145b8f0a5eSJan Høydahl print(' archive...') 2155b8f0a5eSJan Høydahl if os.path.exists('lucene.tar'): 2165b8f0a5eSJan Høydahl os.remove('lucene.tar') 2175b8f0a5eSJan Høydahl run('tar cf lucene.tar *') 218879e8250SMichael McCandless 219879e8250SMichael McCandless os.chdir('%s/%s/lucene' % (localDir, dir)) 2205b8f0a5eSJan Høydahl print(' extract...') 2215b8f0a5eSJan Høydahl run('tar xf "%s/lucene.tar"' % lucene_dist_dir) 2225b8f0a5eSJan Høydahl os.remove('%s/lucene.tar' % lucene_dist_dir) 223674b66ddSJan Høydahl os.chdir('..') 224879e8250SMichael McCandless 2250178e8e6SSteven Rowe print(' chmod...') 226879e8250SMichael McCandless run('chmod -R a+rX-w .') 227879e8250SMichael McCandless 2280178e8e6SSteven Rowe print(' done!') 229879e8250SMichael McCandless return 'file://%s/%s' % (os.path.abspath(localDir), dir) 230d6ab323dSMichael McCandless 231cdfa11b1SJan Høydahl 23280e60002SRyan Ernstdef read_version(path): 23308e38d34SMike Drob return scriptutil.find_current_version() 234879e8250SMichael McCandless 235cdfa11b1SJan Høydahl 23680e60002SRyan Ernstdef parse_config(): 23780e60002SRyan Ernst epilogue = textwrap.dedent(''' 23880e60002SRyan Ernst Example usage for a Release Manager: 2398cb2773dSSteve Rowe python3 -u dev-tools/scripts/buildAndPushRelease.py --push-local /tmp/releases/6.0.1 --sign 6E68DA61 --rc-num 1 24080e60002SRyan Ernst ''') 24180e60002SRyan Ernst description = 'Utility to build, push, and test a release.' 24280e60002SRyan Ernst parser = argparse.ArgumentParser(description=description, epilog=epilogue, 24380e60002SRyan Ernst formatter_class=argparse.RawDescriptionHelpFormatter) 24480e60002SRyan Ernst parser.add_argument('--no-prepare', dest='prepare', default=True, action='store_false', 24580e60002SRyan Ernst help='Use the already built release in the provided checkout') 246982ee393SJan Høydahl parser.add_argument('--local-keys', metavar='PATH', 247982ee393SJan Høydahl help='Uses local KEYS file to validate presence of RM\'s gpg key') 24880e60002SRyan Ernst parser.add_argument('--push-local', metavar='PATH', 24980e60002SRyan Ernst help='Push the release to the local path') 25080e60002SRyan Ernst parser.add_argument('--sign', metavar='KEYID', 25180e60002SRyan Ernst help='Sign the release with the given gpg key') 252cdfa11b1SJan Høydahl parser.add_argument('--sign-method-gradle', dest='sign_method_gradle', default=False, action='store_true', 253cdfa11b1SJan Høydahl help='Use Gradle built-in GPG signing instead of gpg command for signing artifacts. ' 254cdfa11b1SJan Høydahl ' This may require --gpg-secring argument if your keychain cannot be resolved automatically.') 255cdfa11b1SJan Høydahl parser.add_argument('--gpg-pass-noprompt', dest='gpg_pass_noprompt', default=False, action='store_true', 256cdfa11b1SJan Høydahl help='Do not prompt for gpg passphrase. For the default gnupg method, this means your gpg-agent' 257cdfa11b1SJan Høydahl ' needs a non-TTY pin-entry program. For gradle signing method, passphrase must be provided' 258cdfa11b1SJan Høydahl ' in gradle.properties or by env.var/sysprop. See ./gradlew helpPublishing for more info') 259cdfa11b1SJan Høydahl parser.add_argument('--gpg-home', metavar='PATH', 260cdfa11b1SJan Høydahl help='Path to gpg home containing your secring.gpg' 261cdfa11b1SJan Høydahl ' Optional, will use $HOME/.gnupg/secring.gpg by default') 26280e60002SRyan Ernst parser.add_argument('--rc-num', metavar='NUM', type=int, default=1, 263dbb1fc68SSteve Rowe help='Release Candidate number. Default: 1') 264dbb1fc68SSteve Rowe parser.add_argument('--root', metavar='PATH', default='.', 265674b66ddSJan Høydahl help='Root of Git working tree for lucene. Default: "." (the current directory)') 266df1775ffSJan Høydahl parser.add_argument('--logfile', metavar='PATH', 267df1775ffSJan Høydahl help='Specify log file path (default /tmp/release.log)') 268cdfa11b1SJan Høydahl parser.add_argument('--dev-mode', default=False, action='store_true', 269cdfa11b1SJan Høydahl help='Enable development mode, which disables some strict checks') 27080e60002SRyan Ernst config = parser.parse_args() 271879e8250SMichael McCandless 27280e60002SRyan Ernst if not config.prepare and config.sign: 27380e60002SRyan Ernst parser.error('Cannot sign already built release') 27480e60002SRyan Ernst if config.push_local is not None and os.path.exists(config.push_local): 27580e60002SRyan Ernst parser.error('Cannot push to local path that already exists') 27680e60002SRyan Ernst if config.rc_num <= 0: 27780e60002SRyan Ernst parser.error('Release Candidate number must be a positive integer') 27880e60002SRyan Ernst if not os.path.isdir(config.root): 2798cb2773dSSteve Rowe parser.error('Root path "%s" is not a directory' % config.root) 280982ee393SJan Høydahl if config.local_keys is not None and not os.path.exists(config.local_keys): 281982ee393SJan Høydahl parser.error('Local KEYS file "%s" not found' % config.local_keys) 282cdfa11b1SJan Høydahl if config.gpg_home and not os.path.exists(os.path.join(config.gpg_home, 'secring.gpg')): 283cdfa11b1SJan Høydahl parser.error('Specified gpg home %s does not exist or does not contain a secring.gpg' % config.gpg_home) 284cdfa11b1SJan Høydahl global dev_mode 285cdfa11b1SJan Høydahl if config.dev_mode: 286cdfa11b1SJan Høydahl print("Enabling development mode - DO NOT USE FOR ACTUAL RELEASE!") 287cdfa11b1SJan Høydahl dev_mode = True 2888cb2773dSSteve Rowe cwd = os.getcwd() 2898cb2773dSSteve Rowe os.chdir(config.root) 2908cb2773dSSteve Rowe config.root = os.getcwd() # Absolutize root dir 291674b66ddSJan Høydahl if os.system('git rev-parse') or 2 != len([d for d in ('dev-tools','lucene') if os.path.isdir(d)]): 292674b66ddSJan Høydahl parser.error('Root path "%s" is not a valid lucene checkout' % config.root) 2938cb2773dSSteve Rowe os.chdir(cwd) 294df1775ffSJan Høydahl global LOG 295df1775ffSJan Høydahl if config.logfile: 296df1775ffSJan Høydahl LOG = config.logfile 297cdfa11b1SJan Høydahl print("Logfile is: %s" % LOG) 298879e8250SMichael McCandless 29980e60002SRyan Ernst config.version = read_version(config.root) 30080e60002SRyan Ernst print('Building version: %s' % config.version) 301879e8250SMichael McCandless 30280e60002SRyan Ernst return config 303d6ab323dSMichael McCandless 3048cb2773dSSteve Rowedef check_cmdline_tools(): # Fail fast if there are cmdline tool problems 305532d07f1SSteve Rowe if os.system('git --version >/dev/null 2>/dev/null'): 3068cb2773dSSteve Rowe raise RuntimeError('"git --version" returned a non-zero exit code.') 307f2e3b109SAlan Woodward 308982ee393SJan Høydahldef check_key_in_keys(gpgKeyID, local_keys): 309982ee393SJan Høydahl if gpgKeyID is not None: 310982ee393SJan Høydahl print(' Verify your gpg key is in the main KEYS file') 311982ee393SJan Høydahl if local_keys is not None: 312982ee393SJan Høydahl print(" Using local KEYS file %s" % local_keys) 313982ee393SJan Høydahl keysFileText = open(local_keys, encoding='iso-8859-1').read() 314982ee393SJan Høydahl keysFileLocation = local_keys 315982ee393SJan Høydahl else: 316982ee393SJan Høydahl keysFileURL = "https://archive.apache.org/dist/lucene/KEYS" 317982ee393SJan Høydahl keysFileLocation = keysFileURL 318982ee393SJan Høydahl print(" Using online KEYS file %s" % keysFileURL) 319982ee393SJan Høydahl keysFileText = load(keysFileURL, encoding='iso-8859-1') 320982ee393SJan Høydahl if len(gpgKeyID) > 2 and gpgKeyID[0:2] == '0x': 321982ee393SJan Høydahl gpgKeyID = gpgKeyID[2:] 322982ee393SJan Høydahl if len(gpgKeyID) > 40: 323982ee393SJan Høydahl gpgKeyID = gpgKeyID.replace(" ", "") 324982ee393SJan Høydahl if len(gpgKeyID) == 8: 3255b96f89dSJan Høydahl gpgKeyID8Char = "%s %s" % (gpgKeyID[0:4], gpgKeyID[4:8]) 326daf14981SAlan Woodward re_to_match = r"^pub .*\n\s+(\w{4} \w{4} \w{4} \w{4} \w{4} \w{4} \w{4} \w{4} %s|\w{32}%s)" % (gpgKeyID8Char, gpgKeyID) 327982ee393SJan Høydahl elif len(gpgKeyID) == 40: 328982ee393SJan Høydahl gpgKeyID40Char = "%s %s %s %s %s %s %s %s %s %s" % \ 329982ee393SJan Høydahl (gpgKeyID[0:4], gpgKeyID[4:8], gpgKeyID[8:12], gpgKeyID[12:16], gpgKeyID[16:20], 330982ee393SJan Høydahl gpgKeyID[20:24], gpgKeyID[24:28], gpgKeyID[28:32], gpgKeyID[32:36], gpgKeyID[36:]) 33155c06177SAlan Woodward re_to_match = r"^pub .*\n\s+(%s|%s)" % (gpgKeyID40Char, gpgKeyID) 332982ee393SJan Høydahl else: 33326a32d79SAlan Woodward print('Invalid gpg key id format [%s]. Must be 8 byte short ID or 40 byte fingerprint, with or without 0x prefix, no spaces.' % gpgKeyID) 334982ee393SJan Høydahl exit(2) 335982ee393SJan Høydahl if re.search(re_to_match, keysFileText, re.MULTILINE): 336982ee393SJan Høydahl print(' Found key %s in KEYS file at %s' % (gpgKeyID, keysFileLocation)) 337982ee393SJan Høydahl else: 338982ee393SJan Høydahl print(' ERROR: Did not find your key %s in KEYS file at %s. Please add it and try again.' % (gpgKeyID, keysFileLocation)) 339982ee393SJan Høydahl if local_keys is not None: 340982ee393SJan Høydahl print(' You are using a local KEYS file. Make sure it is up to date or validate against the online version') 341982ee393SJan Høydahl exit(2) 342982ee393SJan Høydahl 343982ee393SJan Høydahl 344cdfa11b1SJan Høydahldef resolve_gpghome(): 345cdfa11b1SJan Høydahl for p in [ 346cdfa11b1SJan Høydahl # Linux, macos 347cdfa11b1SJan Høydahl os.path.join(os.path.expanduser("~"), '.gnupg'), 348cdfa11b1SJan Høydahl # Windows 10 349cdfa11b1SJan Høydahl os.path.expandvars(r'%APPDATA%\GnuPG') 350cdfa11b1SJan Høydahl # TODO: Should we support Cygwin? 351cdfa11b1SJan Høydahl ]: 352cdfa11b1SJan Høydahl if os.path.exists(os.path.join(p, 'secring.gpg')): 353cdfa11b1SJan Høydahl return p 354cdfa11b1SJan Høydahl return None 355cdfa11b1SJan Høydahl 356cdfa11b1SJan Høydahl 35780e60002SRyan Ernstdef main(): 3588cb2773dSSteve Rowe check_cmdline_tools() 3598cb2773dSSteve Rowe 36080e60002SRyan Ernst c = parse_config() 361cdfa11b1SJan Høydahl gpg_home = None 36280e60002SRyan Ernst 3635b96f89dSJan Høydahl if c.sign: 3645b96f89dSJan Høydahl sys.stdout.flush() 3655b96f89dSJan Høydahl c.key_id = c.sign 366982ee393SJan Høydahl check_key_in_keys(c.key_id, c.local_keys) 367cdfa11b1SJan Høydahl if c.gpg_home is not None: 368cdfa11b1SJan Høydahl print("Using custom gpg-home: %s" % c.gpg_home) 369cdfa11b1SJan Høydahl gpg_home = c.gpg_home 370cdfa11b1SJan Høydahl if c.sign_method_gradle: 371cdfa11b1SJan Høydahl if gpg_home is None: 372cdfa11b1SJan Høydahl resolved_gpg_home = resolve_gpghome() 373cdfa11b1SJan Høydahl if resolved_gpg_home is not None: 374cdfa11b1SJan Høydahl print("Resolved gpg home to %s" % resolved_gpg_home) 375cdfa11b1SJan Høydahl gpg_home = resolved_gpg_home 376cdfa11b1SJan Høydahl else: 377cdfa11b1SJan Høydahl print("WARN: Could not locate your gpg secret keyring, and --gpg-home not specified.") 378cdfa11b1SJan Høydahl print(" Falling back to location configured in gradle.properties.") 379cdfa11b1SJan Høydahl print(" See 'gradlew helpPublishing' for details.") 380cdfa11b1SJan Høydahl gpg_home = None 381cdfa11b1SJan Høydahl if c.gpg_pass_noprompt: 382cdfa11b1SJan Høydahl print("Will not prompt for gpg password. Make sure your signing setup supports this.") 383cdfa11b1SJan Høydahl c.key_password = None 384cdfa11b1SJan Høydahl else: 3855b96f89dSJan Høydahl import getpass 3865b96f89dSJan Høydahl c.key_password = getpass.getpass('Enter GPG keystore password: ') 3875b96f89dSJan Høydahl else: 3885b96f89dSJan Høydahl c.key_id = None 3895b96f89dSJan Høydahl c.key_password = None 390982ee393SJan Høydahl 39180e60002SRyan Ernst if c.prepare: 3920544819bSDawid Weiss prepare(c.root, c.version, c.key_id, c.key_password, gpg_home=gpg_home, sign_gradle=c.sign_method_gradle) 393d6ab323dSMichael McCandless else: 394b8e50f38SShalin Shekhar Mangar os.chdir(c.root) 395d6ab323dSMichael McCandless 3968cb2773dSSteve Rowe if c.push_local: 3970544819bSDawid Weiss url = pushLocal(c.version, c.root, c.rc_num, c.push_local) 398879e8250SMichael McCandless else: 3990178e8e6SSteven Rowe url = None 400879e8250SMichael McCandless 401879e8250SMichael McCandless if url is not None: 4020178e8e6SSteven Rowe print(' URL: %s' % url) 4038cb2773dSSteve Rowe print('Next run the smoker tester:') 4048cb2773dSSteve Rowe p = re.compile(".*/") 40522a358ffSAnshum Gupta m = p.match(sys.argv[0]) 406ae956db4SJan Høydahl if not c.sign: 407ae956db4SJan Høydahl signed = "--not-signed" 408ae956db4SJan Høydahl else: 409ae956db4SJan Høydahl signed = "" 410ae956db4SJan Høydahl print('%s -u %ssmokeTestRelease.py %s %s' % (sys.executable, m.group(), signed, url)) 411d6ab323dSMichael McCandless 412d6ab323dSMichael McCandlessif __name__ == '__main__': 4130178e8e6SSteven Rowe try: 414d6ab323dSMichael McCandless main() 41580e60002SRyan Ernst except KeyboardInterrupt: 41680e60002SRyan Ernst print('Keyboard interrupt...exiting') 41780e60002SRyan Ernst 418