187c131baSJan Høydahl#!/usr/bin/env python3 287c131baSJan Høydahl# -*- coding: utf-8 -*- 387c131baSJan Høydahl# Licensed to the Apache Software Foundation (ASF) under one or more 487c131baSJan Høydahl# contributor license agreements. See the NOTICE file distributed with 587c131baSJan Høydahl# this work for additional information regarding copyright ownership. 687c131baSJan Høydahl# The ASF licenses this file to You under the Apache License, Version 2.0 787c131baSJan Høydahl# (the "License"); you may not use this file except in compliance with 887c131baSJan Høydahl# the License. You may obtain a copy of the License at 987c131baSJan Høydahl# 1087c131baSJan Høydahl# http://www.apache.org/licenses/LICENSE-2.0 1187c131baSJan Høydahl# 1287c131baSJan Høydahl# Unless required by applicable law or agreed to in writing, software 1387c131baSJan Høydahl# distributed under the License is distributed on an "AS IS" BASIS, 1487c131baSJan Høydahl# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1587c131baSJan Høydahl# See the License for the specific language governing permissions and 1687c131baSJan Høydahl# limitations under the License. 1787c131baSJan Høydahl 18462c6c70SJan Høydahl# This script is the Release Manager's best friend, ensuring all details of a release are handled correctly. 19462c6c70SJan Høydahl# It will walk you through the steps of the release process, asking for decisions or input along the way. 20462c6c70SJan Høydahl# CAUTION: You still need to use your head! Please read the HELP section in the main menu. 2187c131baSJan Høydahl# 2287c131baSJan Høydahl# Requirements: 2387c131baSJan Høydahl# Install requirements with this command: 2487c131baSJan Høydahl# pip3 install -r requirements.txt 2587c131baSJan Høydahl# 2687c131baSJan Høydahl# Usage: 2787c131baSJan Høydahl# releaseWizard.py [-h] [--dry-run] [--root PATH] 2887c131baSJan Høydahl# 2987c131baSJan Høydahl# optional arguments: 3087c131baSJan Høydahl# -h, --help show this help message and exit 3187c131baSJan Høydahl# --dry-run Do not execute any commands, but echo them instead. Display 3287c131baSJan Høydahl# extra debug info 3387c131baSJan Høydahl# --root PATH Specify different root folder than ~/.lucene-releases 3487c131baSJan Høydahl 3587c131baSJan Høydahlimport argparse 3687c131baSJan Høydahlimport copy 3787c131baSJan Høydahlimport fcntl 3887c131baSJan Høydahlimport json 3987c131baSJan Høydahlimport os 4087c131baSJan Høydahlimport platform 4187c131baSJan Høydahlimport re 4287c131baSJan Høydahlimport shlex 4387c131baSJan Høydahlimport shutil 4487c131baSJan Høydahlimport subprocess 4587c131baSJan Høydahlimport sys 4687c131baSJan Høydahlimport textwrap 4787c131baSJan Høydahlimport time 4887c131baSJan Høydahlimport urllib 4987c131baSJan Høydahlfrom collections import OrderedDict 5087c131baSJan Høydahlfrom datetime import datetime 5187c131baSJan Høydahlfrom datetime import timedelta 5287c131baSJan Høydahl 5387c131baSJan Høydahltry: 5487c131baSJan Høydahl import holidays 5587c131baSJan Høydahl import yaml 5687c131baSJan Høydahl from ics import Calendar, Event 5787c131baSJan Høydahl from jinja2 import Environment 5887c131baSJan Høydahlexcept: 5987c131baSJan Høydahl print("You lack some of the module dependencies to run this script.") 6087c131baSJan Høydahl print("Please run 'pip3 install -r requirements.txt' and try again.") 6187c131baSJan Høydahl sys.exit(1) 6287c131baSJan Høydahl 6387c131baSJan Høydahlimport scriptutil 6487c131baSJan Høydahlfrom consolemenu import ConsoleMenu 6587c131baSJan Høydahlfrom consolemenu.items import FunctionItem, SubmenuItem, ExitItem 6687c131baSJan Høydahlfrom consolemenu.screen import Screen 673134f10aSMike Drobfrom scriptutil import BranchType, Version, download, run 6887c131baSJan Høydahl 69674b66ddSJan Høydahl# Lucene-to-Java version mapping 70*8c48475cSJan Høydahljava_versions = {6: 8, 7: 8, 8: 8, 9: 11, 10: 11} 71329e7c7bSJan Høydahleditor = None 7287c131baSJan Høydahl 7387c131baSJan Høydahl# Edit this to add other global jinja2 variables or filters 7487c131baSJan Høydahldef expand_jinja(text, vars=None): 7587c131baSJan Høydahl global_vars = OrderedDict({ 7687c131baSJan Høydahl 'script_version': state.script_version, 7787c131baSJan Høydahl 'release_version': state.release_version, 7887c131baSJan Høydahl 'release_version_underscore': state.release_version.replace('.', '_'), 7987c131baSJan Høydahl 'release_date': state.get_release_date(), 8087c131baSJan Høydahl 'ivy2_folder': os.path.expanduser("~/.ivy2/"), 8187c131baSJan Høydahl 'config_path': state.config_path, 8287c131baSJan Høydahl 'rc_number': state.rc_number, 8387c131baSJan Høydahl 'script_branch': state.script_branch, 8487c131baSJan Høydahl 'release_folder': state.get_release_folder(), 8587c131baSJan Høydahl 'git_checkout_folder': state.get_git_checkout_folder(), 86329e7c7bSJan Høydahl 'git_website_folder': state.get_website_git_folder(), 8787c131baSJan Høydahl 'dist_url_base': 'https://dist.apache.org/repos/dist/dev/lucene', 8887c131baSJan Høydahl 'm2_repository_url': 'https://repository.apache.org/service/local/staging/deploy/maven2', 8987c131baSJan Høydahl 'dist_file_path': state.get_dist_folder(), 9087c131baSJan Høydahl 'rc_folder': state.get_rc_folder(), 9187c131baSJan Høydahl 'base_branch': state.get_base_branch_name(), 9287c131baSJan Høydahl 'release_branch': state.release_branch, 9387c131baSJan Høydahl 'stable_branch': state.get_stable_branch_name(), 9487c131baSJan Høydahl 'minor_branch': state.get_minor_branch_name(), 9587c131baSJan Høydahl 'release_type': state.release_type, 9687c131baSJan Høydahl 'is_feature_release': state.release_type in ['minor', 'major'], 9787c131baSJan Høydahl 'release_version_major': state.release_version_major, 9887c131baSJan Høydahl 'release_version_minor': state.release_version_minor, 9987c131baSJan Høydahl 'release_version_bugfix': state.release_version_bugfix, 10087c131baSJan Høydahl 'state': state, 101daf14981SAlan Woodward 'gpg_key' : state.get_gpg_key(), 1023134f10aSMike Drob 'gradle_cmd' : 'gradlew.bat' if is_windows() else './gradlew', 10387c131baSJan Høydahl 'epoch': unix_time_millis(datetime.utcnow()), 10487c131baSJan Høydahl 'get_next_version': state.get_next_version(), 10587c131baSJan Høydahl 'current_git_rev': state.get_current_git_rev(), 10687c131baSJan Høydahl 'keys_downloaded': keys_downloaded(), 10787c131baSJan Høydahl 'editor': get_editor(), 10887c131baSJan Høydahl 'rename_cmd': 'ren' if is_windows() else 'mv', 10987c131baSJan Høydahl 'vote_close_72h': vote_close_72h_date().strftime("%Y-%m-%d %H:00 UTC"), 11087c131baSJan Høydahl 'vote_close_72h_epoch': unix_time_millis(vote_close_72h_date()), 111d86b473aSJan Høydahl 'vote_close_72h_holidays': vote_close_72h_holidays(), 11287c131baSJan Høydahl 'lucene_news_file': lucene_news_file, 11387c131baSJan Høydahl 'load_lines': load_lines, 11487c131baSJan Høydahl 'set_java_home': set_java_home, 11587c131baSJan Høydahl 'latest_version': state.get_latest_version(), 11687c131baSJan Høydahl 'latest_lts_version': state.get_latest_lts_version(), 11757524c6aSChristine Poerschke 'main_version': state.get_main_version(), 11887c131baSJan Høydahl 'mirrored_versions': state.get_mirrored_versions(), 11987c131baSJan Høydahl 'mirrored_versions_to_delete': state.get_mirrored_versions_to_delete(), 12087c131baSJan Høydahl 'home': os.path.expanduser("~") 12187c131baSJan Høydahl }) 12287c131baSJan Høydahl global_vars.update(state.get_todo_states()) 12387c131baSJan Høydahl if vars: 12487c131baSJan Høydahl global_vars.update(vars) 12587c131baSJan Høydahl 12687c131baSJan Høydahl filled = replace_templates(text) 12787c131baSJan Høydahl 12887c131baSJan Høydahl try: 12987c131baSJan Høydahl env = Environment(lstrip_blocks=True, keep_trailing_newline=False, trim_blocks=True) 13087c131baSJan Høydahl env.filters['path_join'] = lambda paths: os.path.join(*paths) 13187c131baSJan Høydahl env.filters['expanduser'] = lambda path: os.path.expanduser(path) 13287c131baSJan Høydahl env.filters['formatdate'] = lambda date: (datetime.strftime(date, "%-d %B %Y") if date else "<date>" ) 13387c131baSJan Høydahl template = env.from_string(str(filled), globals=global_vars) 13487c131baSJan Høydahl filled = template.render() 13587c131baSJan Høydahl except Exception as e: 13687c131baSJan Høydahl print("Exception while rendering jinja template %s: %s" % (str(filled)[:10], e)) 13787c131baSJan Høydahl return filled 13887c131baSJan Høydahl 13987c131baSJan Høydahl 14087c131baSJan Høydahldef replace_templates(text): 14187c131baSJan Høydahl tpl_lines = [] 14287c131baSJan Høydahl for line in text.splitlines(): 14387c131baSJan Høydahl if line.startswith("(( template="): 14487c131baSJan Høydahl match = re.search(r"^\(\( template=(.+?) \)\)", line) 14587c131baSJan Høydahl name = match.group(1) 14687c131baSJan Høydahl tpl_lines.append(replace_templates(templates[name].strip())) 14787c131baSJan Høydahl else: 14887c131baSJan Høydahl tpl_lines.append(line) 14987c131baSJan Høydahl return "\n".join(tpl_lines) 15087c131baSJan Høydahl 15187c131baSJan Høydahldef getScriptVersion(): 15208e38d34SMike Drob return scriptutil.find_current_version() 15387c131baSJan Høydahl 15487c131baSJan Høydahl 15587c131baSJan Høydahldef get_editor(): 156329e7c7bSJan Høydahl global editor 157329e7c7bSJan Høydahl if editor is None: 158329e7c7bSJan Høydahl if 'EDITOR' in os.environ: 159329e7c7bSJan Høydahl if os.environ['EDITOR'] in ['vi', 'vim', 'nano', 'pico', 'emacs']: 160329e7c7bSJan Høydahl print("WARNING: You have EDITOR set to %s, which will not work when launched from this tool. Please use an editor that launches a separate window/process" % os.environ['EDITOR']) 161329e7c7bSJan Høydahl editor = os.environ['EDITOR'] 162329e7c7bSJan Høydahl elif is_windows(): 163329e7c7bSJan Høydahl editor = 'notepad.exe' 164329e7c7bSJan Høydahl elif is_mac(): 165329e7c7bSJan Høydahl editor = 'open -a TextEdit' 166329e7c7bSJan Høydahl else: 167329e7c7bSJan Høydahl sys.exit("On Linux you have to set EDITOR variable to a command that will start an editor in its own window") 168329e7c7bSJan Høydahl return editor 16987c131baSJan Høydahl 17087c131baSJan Høydahl 17187c131baSJan Høydahldef check_prerequisites(todo=None): 17287c131baSJan Høydahl if sys.version_info < (3, 4): 17387c131baSJan Høydahl sys.exit("Script requires Python v3.4 or later") 17487c131baSJan Høydahl try: 17587c131baSJan Høydahl gpg_ver = run("gpg --version").splitlines()[0] 17687c131baSJan Høydahl except: 17787c131baSJan Høydahl sys.exit("You will need gpg installed") 17826a32d79SAlan Woodward if not 'GPG_TTY' in os.environ: 179674c2c28SMike Drob print("WARNING: GPG_TTY environment variable is not set, GPG signing may not work correctly (try 'export GPG_TTY=$(tty)'") 1803134f10aSMike Drob if not 'JAVA11_HOME' in os.environ: 1813134f10aSMike Drob sys.exit("Please set environment variables JAVA11_HOME") 18287c131baSJan Høydahl try: 18387c131baSJan Høydahl asciidoc_ver = run("asciidoctor -V").splitlines()[0] 18487c131baSJan Høydahl except: 18587c131baSJan Høydahl asciidoc_ver = "" 18687c131baSJan Høydahl print("WARNING: In order to export asciidoc version to HTML, you will need asciidoctor installed") 18787c131baSJan Høydahl try: 18887c131baSJan Høydahl git_ver = run("git --version").splitlines()[0] 18987c131baSJan Høydahl except: 19087c131baSJan Høydahl sys.exit("You will need git installed") 19187c131baSJan Høydahl if not 'EDITOR' in os.environ: 19287c131baSJan Høydahl print("WARNING: Environment variable $EDITOR not set, using %s" % get_editor()) 19387c131baSJan Høydahl 19487c131baSJan Høydahl if todo: 19587c131baSJan Høydahl print("%s\n%s\n%s\n" % (gpg_ver, asciidoc_ver, git_ver)) 19687c131baSJan Høydahl return True 19787c131baSJan Høydahl 19887c131baSJan Høydahl 19987c131baSJan Høydahlepoch = datetime.utcfromtimestamp(0) 20087c131baSJan Høydahl 20187c131baSJan Høydahl 20287c131baSJan Høydahldef unix_time_millis(dt): 20387c131baSJan Høydahl return int((dt - epoch).total_seconds() * 1000.0) 20487c131baSJan Høydahl 20587c131baSJan Høydahl 20687c131baSJan Høydahldef bootstrap_todos(todo_list): 20787c131baSJan Høydahl # Establish links from commands to to_do for finding todo vars 20887c131baSJan Høydahl for tg in todo_list: 20987c131baSJan Høydahl if dry_run: 21087c131baSJan Høydahl print("Group %s" % tg.id) 21187c131baSJan Høydahl for td in tg.get_todos(): 21287c131baSJan Høydahl if dry_run: 21387c131baSJan Høydahl print(" Todo %s" % td.id) 21487c131baSJan Høydahl cmds = td.commands 21587c131baSJan Høydahl if cmds: 21687c131baSJan Høydahl if dry_run: 21787c131baSJan Høydahl print(" Commands") 21887c131baSJan Høydahl cmds.todo_id = td.id 21987c131baSJan Høydahl for cmd in cmds.commands: 22087c131baSJan Høydahl if dry_run: 22187c131baSJan Høydahl print(" Command %s" % cmd.cmd) 22287c131baSJan Høydahl cmd.todo_id = td.id 22387c131baSJan Høydahl 22487c131baSJan Høydahl print("Loaded TODO definitions from releaseWizard.yaml") 22587c131baSJan Høydahl return todo_list 22687c131baSJan Høydahl 22787c131baSJan Høydahl 22887c131baSJan Høydahldef maybe_remove_rc_from_svn(): 22987c131baSJan Høydahl todo = state.get_todo_by_id('import_svn') 23087c131baSJan Høydahl if todo and todo.is_done(): 23187c131baSJan Høydahl print("import_svn done") 23287c131baSJan Høydahl Commands(state.get_git_checkout_folder(), 23387c131baSJan Høydahl """Looks like you uploaded artifacts for {{ build_rc.git_rev | default("<git_rev>", True) }} to svn which needs to be removed.""", 23487c131baSJan Høydahl [Command( 235674b66ddSJan Høydahl """svn -m "Remove cancelled Lucene {{ release_version }} RC{{ rc_number }}" rm {{ dist_url }}""", 23687c131baSJan Høydahl logfile="svn_rm.log", 23787c131baSJan Høydahl tee=True, 23887c131baSJan Høydahl vars={ 239674b66ddSJan Høydahl 'dist_folder': """lucene-{{ release_version }}-RC{{ rc_number }}-rev{{ build_rc.git_rev | default("<git_rev>", True) }}""", 24087c131baSJan Høydahl 'dist_url': "{{ dist_url_base }}/{{ dist_folder }}" 24187c131baSJan Høydahl } 24287c131baSJan Høydahl )], 24387c131baSJan Høydahl enable_execute=True, confirm_each_command=False).run() 24487c131baSJan Høydahl 24587c131baSJan Høydahl 24687c131baSJan Høydahl# To be able to hide fields when dumping Yaml 24787c131baSJan Høydahlclass SecretYamlObject(yaml.YAMLObject): 24887c131baSJan Høydahl hidden_fields = [] 24987c131baSJan Høydahl @classmethod 25087c131baSJan Høydahl def to_yaml(cls,dumper,data): 25187c131baSJan Høydahl print("Dumping object %s" % type(data)) 25287c131baSJan Høydahl 25387c131baSJan Høydahl new_data = copy.deepcopy(data) 25487c131baSJan Høydahl for item in cls.hidden_fields: 25587c131baSJan Høydahl if item in new_data.__dict__: 25687c131baSJan Høydahl del new_data.__dict__[item] 25787c131baSJan Høydahl for item in data.__dict__: 25887c131baSJan Høydahl if item in new_data.__dict__ and new_data.__dict__[item] is None: 25987c131baSJan Høydahl del new_data.__dict__[item] 26087c131baSJan Høydahl return dumper.represent_yaml_object(cls.yaml_tag, new_data, cls, 26187c131baSJan Høydahl flow_style=cls.yaml_flow_style) 26287c131baSJan Høydahl 26387c131baSJan Høydahl 26487c131baSJan Høydahldef str_presenter(dumper, data): 26587c131baSJan Høydahl if len(data.split('\n')) > 1: # check for multiline string 26687c131baSJan Høydahl return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') 26787c131baSJan Høydahl return dumper.represent_scalar('tag:yaml.org,2002:str', data) 26887c131baSJan Høydahl 26987c131baSJan Høydahl 27087c131baSJan Høydahlclass ReleaseState: 27187c131baSJan Høydahl def __init__(self, config_path, release_version, script_version): 27287c131baSJan Høydahl self.script_version = script_version 27387c131baSJan Høydahl self.config_path = config_path 27487c131baSJan Høydahl self.todo_groups = None 27587c131baSJan Høydahl self.todos = None 27687c131baSJan Høydahl self.latest_version = None 27787c131baSJan Høydahl self.previous_rcs = {} 27887c131baSJan Høydahl self.rc_number = 1 27987c131baSJan Høydahl self.start_date = unix_time_millis(datetime.utcnow()) 28087c131baSJan Høydahl self.script_branch = run("git rev-parse --abbrev-ref HEAD").strip() 28187c131baSJan Høydahl self.mirrored_versions = None 28287c131baSJan Høydahl try: 28387c131baSJan Høydahl self.script_branch_type = scriptutil.find_branch_type() 28487c131baSJan Høydahl except: 28587c131baSJan Høydahl print("WARNING: This script shold (ideally) run from the release branch, not a feature branch (%s)" % self.script_branch) 28687c131baSJan Høydahl self.script_branch_type = 'feature' 28787c131baSJan Høydahl self.set_release_version(release_version) 28887c131baSJan Høydahl 28987c131baSJan Høydahl def set_release_version(self, version): 29087c131baSJan Høydahl self.validate_release_version(self.script_branch_type, self.script_branch, version) 29187c131baSJan Høydahl self.release_version = version 29287c131baSJan Høydahl v = Version.parse(version) 29387c131baSJan Høydahl self.release_version_major = v.major 29487c131baSJan Høydahl self.release_version_minor = v.minor 29587c131baSJan Høydahl self.release_version_bugfix = v.bugfix 29687c131baSJan Høydahl self.release_branch = "branch_%s_%s" % (v.major, v.minor) 29787c131baSJan Høydahl if v.is_major_release(): 29887c131baSJan Høydahl self.release_type = 'major' 29987c131baSJan Høydahl elif v.is_minor_release(): 30087c131baSJan Høydahl self.release_type = 'minor' 30187c131baSJan Høydahl else: 30287c131baSJan Høydahl self.release_type = 'bugfix' 30387c131baSJan Høydahl 30487c131baSJan Høydahl def is_released(self): 30587c131baSJan Høydahl return self.get_todo_by_id('announce_lucene').is_done() 30687c131baSJan Høydahl 307daf14981SAlan Woodward def get_gpg_key(self): 308daf14981SAlan Woodward gpg_task = self.get_todo_by_id('gpg') 309daf14981SAlan Woodward if gpg_task.is_done(): 310daf14981SAlan Woodward return gpg_task.get_state()['gpg_key'] 311daf14981SAlan Woodward else: 312daf14981SAlan Woodward return None 313daf14981SAlan Woodward 31487c131baSJan Høydahl def get_release_date(self): 31587c131baSJan Høydahl publish_task = self.get_todo_by_id('publish_maven') 31687c131baSJan Høydahl if publish_task.is_done(): 31787c131baSJan Høydahl return unix_to_datetime(publish_task.get_state()['done_date']) 31887c131baSJan Høydahl else: 31987c131baSJan Høydahl return None 32087c131baSJan Høydahl 321329e7c7bSJan Høydahl def get_release_date_iso(self): 322329e7c7bSJan Høydahl release_date = self.get_release_date() 323329e7c7bSJan Høydahl if release_date is None: 324329e7c7bSJan Høydahl return "yyyy-mm-dd" 325329e7c7bSJan Høydahl else: 326329e7c7bSJan Høydahl return release_date.isoformat()[:10] 327329e7c7bSJan Høydahl 32887c131baSJan Høydahl def get_latest_version(self): 32987c131baSJan Høydahl if self.latest_version is None: 33087c131baSJan Høydahl versions = self.get_mirrored_versions() 33187c131baSJan Høydahl latest = versions[0] 33287c131baSJan Høydahl for ver in versions: 33387c131baSJan Høydahl if Version.parse(ver).gt(Version.parse(latest)): 33487c131baSJan Høydahl latest = ver 33587c131baSJan Høydahl self.latest_version = latest 33687c131baSJan Høydahl self.save() 33787c131baSJan Høydahl return state.latest_version 33887c131baSJan Høydahl 33987c131baSJan Høydahl def get_mirrored_versions(self): 34087c131baSJan Høydahl if state.mirrored_versions is None: 34187c131baSJan Høydahl releases_str = load("https://projects.apache.org/json/foundation/releases.json", "utf-8") 34287c131baSJan Høydahl releases = json.loads(releases_str)['lucene'] 34387c131baSJan Høydahl state.mirrored_versions = [ r for r in list(map(lambda y: y[7:], filter(lambda x: x.startswith('lucene-'), list(releases.keys())))) ] 34487c131baSJan Høydahl return state.mirrored_versions 34587c131baSJan Høydahl 34687c131baSJan Høydahl def get_mirrored_versions_to_delete(self): 34787c131baSJan Høydahl versions = self.get_mirrored_versions() 34887c131baSJan Høydahl to_keep = versions 34987c131baSJan Høydahl if state.release_type == 'major': 35087c131baSJan Høydahl to_keep = [self.release_version, self.get_latest_version()] 35187c131baSJan Høydahl if state.release_type == 'minor': 35287c131baSJan Høydahl to_keep = [self.release_version, self.get_latest_lts_version()] 35387c131baSJan Høydahl if state.release_type == 'bugfix': 35487c131baSJan Høydahl if Version.parse(state.release_version).major == Version.parse(state.get_latest_version()).major: 35587c131baSJan Høydahl to_keep = [self.release_version, self.get_latest_lts_version()] 35687c131baSJan Høydahl elif Version.parse(state.release_version).major == Version.parse(state.get_latest_lts_version()).major: 357329e7c7bSJan Høydahl to_keep = [self.get_latest_version(), self.release_version] 35887c131baSJan Høydahl else: 35987c131baSJan Høydahl raise Exception("Release version %s must have same major version as current minor or lts release") 36087c131baSJan Høydahl return [ver for ver in versions if ver not in to_keep] 36187c131baSJan Høydahl 36257524c6aSChristine Poerschke def get_main_version(self): 36387c131baSJan Høydahl v = Version.parse(self.get_latest_version()) 36487c131baSJan Høydahl return "%s.%s.%s" % (v.major + 1, 0, 0) 36587c131baSJan Høydahl 36687c131baSJan Høydahl def get_latest_lts_version(self): 36787c131baSJan Høydahl versions = self.get_mirrored_versions() 36887c131baSJan Høydahl latest = self.get_latest_version() 36987c131baSJan Høydahl lts_prefix = "%s." % (Version.parse(latest).major - 1) 37087c131baSJan Høydahl lts_versions = list(filter(lambda x: x.startswith(lts_prefix), versions)) 37187c131baSJan Høydahl latest_lts = lts_versions[0] 37287c131baSJan Høydahl for ver in lts_versions: 37387c131baSJan Høydahl if Version.parse(ver).gt(Version.parse(latest_lts)): 37487c131baSJan Høydahl latest_lts = ver 37587c131baSJan Høydahl return latest_lts 37687c131baSJan Høydahl 37787c131baSJan Høydahl def validate_release_version(self, branch_type, branch, release_version): 37887c131baSJan Høydahl ver = Version.parse(release_version) 37987c131baSJan Høydahl # print("release_version=%s, ver=%s" % (release_version, ver)) 38087c131baSJan Høydahl if branch_type == BranchType.release: 38187c131baSJan Høydahl if not branch.startswith('branch_'): 38287c131baSJan Høydahl sys.exit("Incompatible branch and branch_type") 38387c131baSJan Høydahl if not ver.is_bugfix_release(): 38487c131baSJan Høydahl sys.exit("You can only release bugfix releases from an existing release branch") 38587c131baSJan Høydahl elif branch_type == BranchType.stable: 38687c131baSJan Høydahl if not branch.startswith('branch_') and branch.endswith('x'): 38787c131baSJan Høydahl sys.exit("Incompatible branch and branch_type") 38887c131baSJan Høydahl if not ver.is_minor_release(): 38987c131baSJan Høydahl sys.exit("You can only release minor releases from an existing stable branch") 39087c131baSJan Høydahl elif branch_type == BranchType.unstable: 39157524c6aSChristine Poerschke if not branch == 'main': 39287c131baSJan Høydahl sys.exit("Incompatible branch and branch_type") 39387c131baSJan Høydahl if not ver.is_major_release(): 39457524c6aSChristine Poerschke sys.exit("You can only release a new major version from main branch") 39587c131baSJan Høydahl if not getScriptVersion() == release_version: 39687c131baSJan Høydahl print("WARNING: Expected release version %s when on branch %s, but got %s" % ( 39787c131baSJan Høydahl getScriptVersion(), branch, release_version)) 39887c131baSJan Høydahl 39987c131baSJan Høydahl def get_base_branch_name(self): 40087c131baSJan Høydahl v = Version.parse(self.release_version) 40187c131baSJan Høydahl if v.is_major_release(): 40257524c6aSChristine Poerschke return 'main' 40387c131baSJan Høydahl elif v.is_minor_release(): 40487c131baSJan Høydahl return self.get_stable_branch_name() 40587c131baSJan Høydahl elif v.major == Version.parse(self.get_latest_version()).major: 40687c131baSJan Høydahl return self.get_minor_branch_name() 40787c131baSJan Høydahl else: 40887c131baSJan Høydahl return self.release_branch 40987c131baSJan Høydahl 41087c131baSJan Høydahl def clear_rc(self): 41187c131baSJan Høydahl if ask_yes_no("Are you sure? This will clear and restart RC%s" % self.rc_number): 41287c131baSJan Høydahl maybe_remove_rc_from_svn() 41387c131baSJan Høydahl dict = {} 41487c131baSJan Høydahl for g in list(filter(lambda x: x.in_rc_loop(), self.todo_groups)): 41587c131baSJan Høydahl for t in g.get_todos(): 41687c131baSJan Høydahl t.clear() 41787c131baSJan Høydahl print("Cleared RC TODO state") 41887c131baSJan Høydahl try: 41987c131baSJan Høydahl shutil.rmtree(self.get_rc_folder()) 42087c131baSJan Høydahl print("Cleared folder %s" % self.get_rc_folder()) 42187c131baSJan Høydahl except Exception as e: 42287c131baSJan Høydahl print("WARN: Failed to clear %s, please do it manually with higher privileges" % self.get_rc_folder()) 42387c131baSJan Høydahl self.save() 42487c131baSJan Høydahl 42587c131baSJan Høydahl def new_rc(self): 42687c131baSJan Høydahl if ask_yes_no("Are you sure? This will abort current RC"): 42787c131baSJan Høydahl maybe_remove_rc_from_svn() 42887c131baSJan Høydahl dict = {} 42987c131baSJan Høydahl for g in list(filter(lambda x: x.in_rc_loop(), self.todo_groups)): 43087c131baSJan Høydahl for t in g.get_todos(): 43187c131baSJan Høydahl if t.applies(self.release_type): 43287c131baSJan Høydahl dict[t.id] = copy.deepcopy(t.state) 43387c131baSJan Høydahl t.clear() 43487c131baSJan Høydahl self.previous_rcs["RC%d" % self.rc_number] = dict 43587c131baSJan Høydahl self.rc_number += 1 43687c131baSJan Høydahl self.save() 43787c131baSJan Høydahl 43887c131baSJan Høydahl def to_dict(self): 43987c131baSJan Høydahl tmp_todos = {} 44087c131baSJan Høydahl for todo_id in self.todos: 44187c131baSJan Høydahl t = self.todos[todo_id] 44287c131baSJan Høydahl tmp_todos[todo_id] = copy.deepcopy(t.state) 44387c131baSJan Høydahl dict = { 44487c131baSJan Høydahl 'script_version': self.script_version, 44587c131baSJan Høydahl 'release_version': self.release_version, 44687c131baSJan Høydahl 'start_date': self.start_date, 44787c131baSJan Høydahl 'rc_number': self.rc_number, 44887c131baSJan Høydahl 'script_branch': self.script_branch, 44987c131baSJan Høydahl 'todos': tmp_todos, 45087c131baSJan Høydahl 'previous_rcs': self.previous_rcs 45187c131baSJan Høydahl } 45287c131baSJan Høydahl if self.latest_version: 45387c131baSJan Høydahl dict['latest_version'] = self.latest_version 45487c131baSJan Høydahl return dict 45587c131baSJan Høydahl 45687c131baSJan Høydahl def restore_from_dict(self, dict): 45787c131baSJan Høydahl self.script_version = dict['script_version'] 45887c131baSJan Høydahl assert dict['release_version'] == self.release_version 45987c131baSJan Høydahl if 'start_date' in dict: 46087c131baSJan Høydahl self.start_date = dict['start_date'] 46187c131baSJan Høydahl if 'latest_version' in dict: 46287c131baSJan Høydahl self.latest_version = dict['latest_version'] 46387c131baSJan Høydahl else: 46487c131baSJan Høydahl self.latest_version = None 46587c131baSJan Høydahl self.rc_number = dict['rc_number'] 46687c131baSJan Høydahl self.script_branch = dict['script_branch'] 46787c131baSJan Høydahl self.previous_rcs = copy.deepcopy(dict['previous_rcs']) 46887c131baSJan Høydahl for todo_id in dict['todos']: 46987c131baSJan Høydahl if todo_id in self.todos: 47087c131baSJan Høydahl t = self.todos[todo_id] 47187c131baSJan Høydahl for k in dict['todos'][todo_id]: 47287c131baSJan Høydahl t.state[k] = dict['todos'][todo_id][k] 47387c131baSJan Høydahl else: 47487c131baSJan Høydahl print("Warning: Could not restore state for %s, Todo definition not found" % todo_id) 47587c131baSJan Høydahl 47687c131baSJan Høydahl def load(self): 47787c131baSJan Høydahl if os.path.exists(os.path.join(self.config_path, self.release_version, 'state.yaml')): 47887c131baSJan Høydahl state_file = os.path.join(self.config_path, self.release_version, 'state.yaml') 47987c131baSJan Høydahl with open(state_file, 'r') as fp: 48087c131baSJan Høydahl try: 48187c131baSJan Høydahl dict = yaml.load(fp, Loader=yaml.Loader) 48287c131baSJan Høydahl self.restore_from_dict(dict) 48387c131baSJan Høydahl print("Loaded state from %s" % state_file) 48487c131baSJan Høydahl except Exception as e: 48587c131baSJan Høydahl print("Failed to load state from %s: %s" % (state_file, e)) 48687c131baSJan Høydahl 48787c131baSJan Høydahl def save(self): 48887c131baSJan Høydahl print("Saving") 48987c131baSJan Høydahl if not os.path.exists(os.path.join(self.config_path, self.release_version)): 49087c131baSJan Høydahl print("Creating folder %s" % os.path.join(self.config_path, self.release_version)) 49187c131baSJan Høydahl os.makedirs(os.path.join(self.config_path, self.release_version)) 49287c131baSJan Høydahl 49387c131baSJan Høydahl with open(os.path.join(self.config_path, self.release_version, 'state.yaml'), 'w') as fp: 49487c131baSJan Høydahl yaml.dump(self.to_dict(), fp, sort_keys=False, default_flow_style=False) 49587c131baSJan Høydahl 49687c131baSJan Høydahl def clear(self): 49787c131baSJan Høydahl self.previous_rcs = {} 49887c131baSJan Høydahl self.rc_number = 1 49987c131baSJan Høydahl for t_id in self.todos: 50087c131baSJan Høydahl t = self.todos[t_id] 50187c131baSJan Høydahl t.state = {} 50287c131baSJan Høydahl self.save() 50387c131baSJan Høydahl 50487c131baSJan Høydahl def get_rc_number(self): 50587c131baSJan Høydahl return self.rc_number 50687c131baSJan Høydahl 50787c131baSJan Høydahl def get_current_git_rev(self): 50887c131baSJan Høydahl try: 50987c131baSJan Høydahl return run("git rev-parse HEAD", cwd=self.get_git_checkout_folder()).strip() 51087c131baSJan Høydahl except: 51187c131baSJan Høydahl return "<git-rev>" 51287c131baSJan Høydahl 51387c131baSJan Høydahl def get_group_by_id(self, id): 51487c131baSJan Høydahl lst = list(filter(lambda x: x.id == id, self.todo_groups)) 51587c131baSJan Høydahl if len(lst) == 1: 51687c131baSJan Høydahl return lst[0] 51787c131baSJan Høydahl else: 51887c131baSJan Høydahl return None 51987c131baSJan Høydahl 52087c131baSJan Høydahl def get_todo_by_id(self, id): 52187c131baSJan Høydahl lst = list(filter(lambda x: x.id == id, self.todos.values())) 52287c131baSJan Høydahl if len(lst) == 1: 52387c131baSJan Høydahl return lst[0] 52487c131baSJan Høydahl else: 52587c131baSJan Høydahl return None 52687c131baSJan Høydahl 52787c131baSJan Høydahl def get_todo_state_by_id(self, id): 52887c131baSJan Høydahl lst = list(filter(lambda x: x.id == id, self.todos.values())) 52987c131baSJan Høydahl if len(lst) == 1: 53087c131baSJan Høydahl return lst[0].state 53187c131baSJan Høydahl else: 53287c131baSJan Høydahl return {} 53387c131baSJan Høydahl 53487c131baSJan Høydahl def get_release_folder(self): 53587c131baSJan Høydahl folder = os.path.join(self.config_path, self.release_version) 53687c131baSJan Høydahl if not os.path.exists(folder): 53787c131baSJan Høydahl print("Creating folder %s" % folder) 53887c131baSJan Høydahl os.makedirs(folder) 53987c131baSJan Høydahl return folder 54087c131baSJan Høydahl 54187c131baSJan Høydahl def get_rc_folder(self): 54287c131baSJan Høydahl folder = os.path.join(self.get_release_folder(), "RC%d" % self.rc_number) 54387c131baSJan Høydahl if not os.path.exists(folder): 54487c131baSJan Høydahl print("Creating folder %s" % folder) 54587c131baSJan Høydahl os.makedirs(folder) 54687c131baSJan Høydahl return folder 54787c131baSJan Høydahl 54887c131baSJan Høydahl def get_dist_folder(self): 54987c131baSJan Høydahl folder = os.path.join(self.get_rc_folder(), "dist") 55087c131baSJan Høydahl return folder 55187c131baSJan Høydahl 55287c131baSJan Høydahl def get_git_checkout_folder(self): 553674b66ddSJan Høydahl folder = os.path.join(self.get_release_folder(), "lucene") 55487c131baSJan Høydahl return folder 55587c131baSJan Høydahl 556329e7c7bSJan Høydahl def get_website_git_folder(self): 557329e7c7bSJan Høydahl folder = os.path.join(self.get_release_folder(), "lucene-site") 558329e7c7bSJan Høydahl return folder 559329e7c7bSJan Høydahl 56087c131baSJan Høydahl def get_minor_branch_name(self): 56187c131baSJan Høydahl latest = state.get_latest_version() 56287c131baSJan Høydahl if latest is not None: 56387c131baSJan Høydahl v = Version.parse(latest) 56487c131baSJan Høydahl return "branch_%s_%s" % (v.major, v.minor) 56587c131baSJan Høydahl else: 56687c131baSJan Høydahl raise Exception("Cannot find latest version") 56787c131baSJan Høydahl 56887c131baSJan Høydahl def get_stable_branch_name(self): 5693134f10aSMike Drob if self.release_type == 'major': 57057524c6aSChristine Poerschke v = Version.parse(self.get_main_version()) 5713134f10aSMike Drob else: 57287c131baSJan Høydahl v = Version.parse(self.get_latest_version()) 57387c131baSJan Høydahl return "branch_%sx" % v.major 57487c131baSJan Høydahl 57587c131baSJan Høydahl def get_next_version(self): 57687c131baSJan Høydahl if self.release_type == 'major': 5773134f10aSMike Drob return "%s.0.0" % (self.release_version_major + 1) 57887c131baSJan Høydahl if self.release_type == 'minor': 5793134f10aSMike Drob return "%s.%s.0" % (self.release_version_major, self.release_version_minor + 1) 58087c131baSJan Høydahl if self.release_type == 'bugfix': 58187c131baSJan Høydahl return "%s.%s.%s" % (self.release_version_major, self.release_version_minor, self.release_version_bugfix + 1) 58287c131baSJan Høydahl 58387c131baSJan Høydahl def get_java_home(self): 58487c131baSJan Høydahl return self.get_java_home_for_version(self.release_version) 58587c131baSJan Høydahl 58687c131baSJan Høydahl def get_java_home_for_version(self, version): 58787c131baSJan Høydahl v = Version.parse(version) 58887c131baSJan Høydahl java_ver = java_versions[v.major] 58987c131baSJan Høydahl java_home_var = "JAVA%s_HOME" % java_ver 59087c131baSJan Høydahl if java_home_var in os.environ: 59187c131baSJan Høydahl return os.environ.get(java_home_var) 59287c131baSJan Høydahl else: 59387c131baSJan Høydahl raise Exception("Script needs environment variable %s" % java_home_var ) 59487c131baSJan Høydahl 59587c131baSJan Høydahl def get_java_cmd_for_version(self, version): 59687c131baSJan Høydahl return os.path.join(self.get_java_home_for_version(version), "bin", "java") 59787c131baSJan Høydahl 59887c131baSJan Høydahl def get_java_cmd(self): 59987c131baSJan Høydahl return os.path.join(self.get_java_home(), "bin", "java") 60087c131baSJan Høydahl 60187c131baSJan Høydahl def get_todo_states(self): 60287c131baSJan Høydahl states = {} 60387c131baSJan Høydahl if self.todos: 60487c131baSJan Høydahl for todo_id in self.todos: 60587c131baSJan Høydahl t = self.todos[todo_id] 60687c131baSJan Høydahl states[todo_id] = copy.deepcopy(t.state) 60787c131baSJan Høydahl return states 60887c131baSJan Høydahl 60987c131baSJan Høydahl def init_todos(self, groups): 61087c131baSJan Høydahl self.todo_groups = groups 61187c131baSJan Høydahl self.todos = {} 61287c131baSJan Høydahl for g in self.todo_groups: 61387c131baSJan Høydahl for t in g.get_todos(): 61487c131baSJan Høydahl self.todos[t.id] = t 61587c131baSJan Høydahl 61687c131baSJan Høydahl 61787c131baSJan Høydahlclass TodoGroup(SecretYamlObject): 61887c131baSJan Høydahl yaml_tag = u'!TodoGroup' 61987c131baSJan Høydahl hidden_fields = [] 62087c131baSJan Høydahl def __init__(self, id, title, description, todos, is_in_rc_loop=None, depends=None): 62187c131baSJan Høydahl self.id = id 62287c131baSJan Høydahl self.title = title 62387c131baSJan Høydahl self.description = description 62487c131baSJan Høydahl self.depends = depends 62587c131baSJan Høydahl self.is_in_rc_loop = is_in_rc_loop 62687c131baSJan Høydahl self.todos = todos 62787c131baSJan Høydahl 62887c131baSJan Høydahl @classmethod 62987c131baSJan Høydahl def from_yaml(cls, loader, node): 63087c131baSJan Høydahl fields = loader.construct_mapping(node, deep = True) 63187c131baSJan Høydahl return TodoGroup(**fields) 63287c131baSJan Høydahl 63387c131baSJan Høydahl def num_done(self): 63487c131baSJan Høydahl return sum(1 for x in self.todos if x.is_done() > 0) 63587c131baSJan Høydahl 63687c131baSJan Høydahl def num_applies(self): 63787c131baSJan Høydahl count = sum(1 for x in self.todos if x.applies(state.release_type)) 63887c131baSJan Høydahl # print("num_applies=%s" % count) 63987c131baSJan Høydahl return count 64087c131baSJan Høydahl 64187c131baSJan Høydahl def is_done(self): 64287c131baSJan Høydahl # print("Done=%s, applies=%s" % (self.num_done(), self.num_applies())) 64387c131baSJan Høydahl return self.num_done() >= self.num_applies() 64487c131baSJan Høydahl 64587c131baSJan Høydahl def get_title(self): 64687c131baSJan Høydahl # print("get_title: %s" % self.is_done()) 64787c131baSJan Høydahl prefix = "" 64887c131baSJan Høydahl if self.is_done(): 64987c131baSJan Høydahl prefix = "✓ " 65087c131baSJan Høydahl return "%s%s (%d/%d)" % (prefix, self.title, self.num_done(), self.num_applies()) 65187c131baSJan Høydahl 65287c131baSJan Høydahl def get_submenu(self): 65387c131baSJan Høydahl menu = UpdatableConsoleMenu(title=self.title, subtitle=self.get_subtitle, prologue_text=self.get_description(), 65487c131baSJan Høydahl screen=MyScreen()) 65587c131baSJan Høydahl menu.exit_item = CustomExitItem("Return") 65687c131baSJan Høydahl for todo in self.get_todos(): 65787c131baSJan Høydahl if todo.applies(state.release_type): 65887c131baSJan Høydahl menu.append_item(todo.get_menu_item()) 65987c131baSJan Høydahl return menu 66087c131baSJan Høydahl 66187c131baSJan Høydahl def get_menu_item(self): 66287c131baSJan Høydahl item = UpdatableSubmenuItem(self.get_title, self.get_submenu()) 66387c131baSJan Høydahl return item 66487c131baSJan Høydahl 66587c131baSJan Høydahl def get_todos(self): 66687c131baSJan Høydahl return self.todos 66787c131baSJan Høydahl 66887c131baSJan Høydahl def in_rc_loop(self): 66987c131baSJan Høydahl return self.is_in_rc_loop is True 67087c131baSJan Høydahl 67187c131baSJan Høydahl def get_description(self): 67287c131baSJan Høydahl desc = self.description 67387c131baSJan Høydahl if desc: 67487c131baSJan Høydahl return expand_jinja(desc) 67587c131baSJan Høydahl else: 67687c131baSJan Høydahl return None 67787c131baSJan Høydahl 67887c131baSJan Høydahl def get_subtitle(self): 67987c131baSJan Høydahl if self.depends: 68087c131baSJan Høydahl ret_str = "" 68187c131baSJan Høydahl for dep in ensure_list(self.depends): 68287c131baSJan Høydahl g = state.get_group_by_id(dep) 68387c131baSJan Høydahl if not g: 68487c131baSJan Høydahl g = state.get_todo_by_id(dep) 68587c131baSJan Høydahl if g and not g.is_done(): 68687c131baSJan Høydahl ret_str += "NOTE: Please first complete '%s'\n" % g.title 68787c131baSJan Høydahl return ret_str.strip() 68887c131baSJan Høydahl return None 68987c131baSJan Høydahl 69087c131baSJan Høydahl 69187c131baSJan Høydahlclass Todo(SecretYamlObject): 69287c131baSJan Høydahl yaml_tag = u'!Todo' 69387c131baSJan Høydahl hidden_fields = ['state'] 69487c131baSJan Høydahl def __init__(self, id, title, description=None, post_description=None, done=None, types=None, links=None, 69587c131baSJan Høydahl commands=None, user_input=None, depends=None, vars=None, asciidoc=None, persist_vars=None, 69687c131baSJan Høydahl function=None): 69787c131baSJan Høydahl self.id = id 69887c131baSJan Høydahl self.title = title 69987c131baSJan Høydahl self.description = description 70087c131baSJan Høydahl self.asciidoc = asciidoc 70187c131baSJan Høydahl self.types = types 70287c131baSJan Høydahl self.depends = depends 70387c131baSJan Høydahl self.vars = vars 70487c131baSJan Høydahl self.persist_vars = persist_vars 70587c131baSJan Høydahl self.function = function 70687c131baSJan Høydahl self.user_input = user_input 70787c131baSJan Høydahl self.commands = commands 70887c131baSJan Høydahl self.post_description = post_description 70987c131baSJan Høydahl self.links = links 71087c131baSJan Høydahl self.state = {} 71187c131baSJan Høydahl 71287c131baSJan Høydahl self.set_done(done) 71387c131baSJan Høydahl if self.types: 71487c131baSJan Høydahl self.types = ensure_list(self.types) 71587c131baSJan Høydahl for t in self.types: 71687c131baSJan Høydahl if not t in ['minor', 'major', 'bugfix']: 71787c131baSJan Høydahl sys.exit("Wrong Todo config for '%s'. Type needs to be either 'minor', 'major' or 'bugfix'" % self.id) 71887c131baSJan Høydahl if commands: 71987c131baSJan Høydahl self.commands.todo_id = self.id 72087c131baSJan Høydahl for c in commands.commands: 72187c131baSJan Høydahl c.todo_id = self.id 72287c131baSJan Høydahl 72387c131baSJan Høydahl @classmethod 72487c131baSJan Høydahl def from_yaml(cls, loader, node): 72587c131baSJan Høydahl fields = loader.construct_mapping(node, deep = True) 72687c131baSJan Høydahl return Todo(**fields) 72787c131baSJan Høydahl 72887c131baSJan Høydahl def get_vars(self): 72987c131baSJan Høydahl myvars = {} 73087c131baSJan Høydahl if self.vars: 73187c131baSJan Høydahl for k in self.vars: 73287c131baSJan Høydahl val = self.vars[k] 73387c131baSJan Høydahl if callable(val): 73487c131baSJan Høydahl myvars[k] = expand_jinja(val(), vars=myvars) 73587c131baSJan Høydahl else: 73687c131baSJan Høydahl myvars[k] = expand_jinja(val, vars=myvars) 73787c131baSJan Høydahl return myvars 73887c131baSJan Høydahl 73987c131baSJan Høydahl def set_done(self, is_done): 74087c131baSJan Høydahl if is_done: 74187c131baSJan Høydahl self.state['done_date'] = unix_time_millis(datetime.utcnow()) 74287c131baSJan Høydahl if self.persist_vars: 74387c131baSJan Høydahl for k in self.persist_vars: 74487c131baSJan Høydahl self.state[k] = self.get_vars()[k] 74587c131baSJan Høydahl else: 74687c131baSJan Høydahl self.state.clear() 74787c131baSJan Høydahl self.state['done'] = is_done 74887c131baSJan Høydahl 74987c131baSJan Høydahl def applies(self, type): 75087c131baSJan Høydahl if self.types: 75187c131baSJan Høydahl return type in self.types 75287c131baSJan Høydahl return True 75387c131baSJan Høydahl 75487c131baSJan Høydahl def is_done(self): 75587c131baSJan Høydahl return 'done' in self.state and self.state['done'] is True 75687c131baSJan Høydahl 75787c131baSJan Høydahl def get_title(self): 75887c131baSJan Høydahl prefix = "" 75987c131baSJan Høydahl if self.is_done(): 76087c131baSJan Høydahl prefix = "✓ " 76187c131baSJan Høydahl return expand_jinja("%s%s" % (prefix, self.title), self.get_vars_and_state()) 76287c131baSJan Høydahl 76387c131baSJan Høydahl def display_and_confirm(self): 76487c131baSJan Høydahl try: 76587c131baSJan Høydahl if self.depends: 76687c131baSJan Høydahl ret_str = "" 76787c131baSJan Høydahl for dep in ensure_list(self.depends): 76887c131baSJan Høydahl g = state.get_group_by_id(dep) 76987c131baSJan Høydahl if not g: 77087c131baSJan Høydahl g = state.get_todo_by_id(dep) 77187c131baSJan Høydahl if not g.is_done(): 77287c131baSJan Høydahl print("This step depends on '%s'. Please complete that first\n" % g.title) 77387c131baSJan Høydahl return 77487c131baSJan Høydahl desc = self.get_description() 77587c131baSJan Høydahl if desc: 77687c131baSJan Høydahl print("%s" % desc) 77787c131baSJan Høydahl try: 77887c131baSJan Høydahl if self.function and not self.is_done(): 77987c131baSJan Høydahl if not eval(self.function)(self): 78087c131baSJan Høydahl return 78187c131baSJan Høydahl except Exception as e: 78287c131baSJan Høydahl print("Function call to %s for todo %s failed: %s" % (self.function, self.id, e)) 78387c131baSJan Høydahl raise e 78487c131baSJan Høydahl if self.user_input and not self.is_done(): 78587c131baSJan Høydahl ui_list = ensure_list(self.user_input) 78687c131baSJan Høydahl for ui in ui_list: 78787c131baSJan Høydahl ui.run(self.state) 78887c131baSJan Høydahl print() 78987c131baSJan Høydahl if self.links: 79087c131baSJan Høydahl print("\nLinks:\n") 79187c131baSJan Høydahl for link in self.links: 79287c131baSJan Høydahl print("- %s" % expand_jinja(link, self.get_vars_and_state())) 79387c131baSJan Høydahl print() 79487c131baSJan Høydahl cmds = self.get_commands() 79587c131baSJan Høydahl if cmds: 79687c131baSJan Høydahl if not self.is_done(): 79787c131baSJan Høydahl if not cmds.logs_prefix: 79887c131baSJan Høydahl cmds.logs_prefix = self.id 79987c131baSJan Høydahl cmds.run() 80087c131baSJan Høydahl else: 80187c131baSJan Høydahl print("This step is already completed. You have to first set it to 'not completed' in order to execute commands again.") 80287c131baSJan Høydahl print() 80387c131baSJan Høydahl if self.post_description: 80487c131baSJan Høydahl print("%s" % self.get_post_description()) 80587c131baSJan Høydahl todostate = self.get_state() 80687c131baSJan Høydahl if self.is_done() and len(todostate) > 2: 80787c131baSJan Høydahl print("Variables registered\n") 80887c131baSJan Høydahl for k in todostate: 80987c131baSJan Høydahl if k == 'done' or k == 'done_date': 81087c131baSJan Høydahl continue 81187c131baSJan Høydahl print("* %s = %s" % (k, todostate[k])) 81287c131baSJan Høydahl print() 81387c131baSJan Høydahl completed = ask_yes_no("Mark task '%s' as completed?" % self.get_title()) 81487c131baSJan Høydahl self.set_done(completed) 81587c131baSJan Høydahl state.save() 81687c131baSJan Høydahl except Exception as e: 81787c131baSJan Høydahl print("ERROR while executing todo %s (%s)" % (self.get_title(), e)) 81887c131baSJan Høydahl 81987c131baSJan Høydahl def get_menu_item(self): 82087c131baSJan Høydahl return UpdatableFunctionItem(self.get_title, self.display_and_confirm) 82187c131baSJan Høydahl 82287c131baSJan Høydahl def clone(self): 82387c131baSJan Høydahl clone = Todo(self.id, self.title, description=self.description) 82487c131baSJan Høydahl clone.state = copy.deepcopy(self.state) 82587c131baSJan Høydahl return clone 82687c131baSJan Høydahl 82787c131baSJan Høydahl def clear(self): 82887c131baSJan Høydahl self.state.clear() 82987c131baSJan Høydahl self.set_done(False) 83087c131baSJan Høydahl 83187c131baSJan Høydahl def get_state(self): 83287c131baSJan Høydahl return self.state 83387c131baSJan Høydahl 83487c131baSJan Høydahl def get_description(self): 83587c131baSJan Høydahl desc = self.description 83687c131baSJan Høydahl if desc: 83787c131baSJan Høydahl return expand_jinja(desc, vars=self.get_vars_and_state()) 83887c131baSJan Høydahl else: 83987c131baSJan Høydahl return None 84087c131baSJan Høydahl 84187c131baSJan Høydahl def get_post_description(self): 84287c131baSJan Høydahl if self.post_description: 84387c131baSJan Høydahl return expand_jinja(self.post_description, vars=self.get_vars_and_state()) 84487c131baSJan Høydahl else: 84587c131baSJan Høydahl return None 84687c131baSJan Høydahl 84787c131baSJan Høydahl def get_commands(self): 84887c131baSJan Høydahl cmds = self.commands 84987c131baSJan Høydahl return cmds 85087c131baSJan Høydahl 85187c131baSJan Høydahl def get_asciidoc(self): 85287c131baSJan Høydahl if self.asciidoc: 85387c131baSJan Høydahl return expand_jinja(self.asciidoc, vars=self.get_vars_and_state()) 85487c131baSJan Høydahl else: 85587c131baSJan Høydahl return None 85687c131baSJan Høydahl 85787c131baSJan Høydahl def get_vars_and_state(self): 85887c131baSJan Høydahl d = self.get_vars().copy() 85987c131baSJan Høydahl d.update(self.get_state()) 86087c131baSJan Høydahl return d 86187c131baSJan Høydahl 86287c131baSJan Høydahl 86387c131baSJan Høydahldef get_release_version(): 86487c131baSJan Høydahl v = str(input("Which version are you releasing? (x.y.z) ")) 86587c131baSJan Høydahl try: 86687c131baSJan Høydahl version = Version.parse(v) 86787c131baSJan Høydahl except: 86887c131baSJan Høydahl print("Not a valid version %s" % v) 86987c131baSJan Høydahl return get_release_version() 87087c131baSJan Høydahl 87187c131baSJan Høydahl return str(version) 87287c131baSJan Høydahl 87387c131baSJan Høydahl 87487c131baSJan Høydahldef get_subtitle(): 87587c131baSJan Høydahl applying_groups = list(filter(lambda x: x.num_applies() > 0, state.todo_groups)) 87687c131baSJan Høydahl done_groups = sum(1 for x in applying_groups if x.is_done()) 87787c131baSJan Høydahl return "Please complete the below checklist (Complete: %s/%s)" % (done_groups, len(applying_groups)) 87887c131baSJan Høydahl 87987c131baSJan Høydahl 88087c131baSJan Høydahldef get_todo_menuitem_title(): 88187c131baSJan Høydahl return "Go to checklist (RC%d)" % (state.rc_number) 88287c131baSJan Høydahl 88387c131baSJan Høydahl 88487c131baSJan Høydahldef get_releasing_text(): 885674b66ddSJan Høydahl return "Releasing Lucene %s RC%d" % (state.release_version, state.rc_number) 88687c131baSJan Høydahl 88787c131baSJan Høydahl 88887c131baSJan Høydahldef get_start_new_rc_menu_title(): 88987c131baSJan Høydahl return "Abort RC%d and start a new RC%d" % (state.rc_number, state.rc_number + 1) 89087c131baSJan Høydahl 89187c131baSJan Høydahl 89287c131baSJan Høydahldef start_new_rc(): 89387c131baSJan Høydahl state.new_rc() 89487c131baSJan Høydahl print("Started RC%d" % state.rc_number) 89587c131baSJan Høydahl 89687c131baSJan Høydahl 89787c131baSJan Høydahldef reset_state(): 89887c131baSJan Høydahl global state 89987c131baSJan Høydahl if ask_yes_no("Are you sure? This will erase all current progress"): 90087c131baSJan Høydahl maybe_remove_rc_from_svn() 90187c131baSJan Høydahl shutil.rmtree(os.path.join(state.config_path, state.release_version)) 90287c131baSJan Høydahl state.clear() 90387c131baSJan Høydahl 90487c131baSJan Høydahl 90587c131baSJan Høydahldef template(name, vars=None): 90687c131baSJan Høydahl return expand_jinja(templates[name], vars=vars) 90787c131baSJan Høydahl 90887c131baSJan Høydahl 90987c131baSJan Høydahldef help(): 91087c131baSJan Høydahl print(template('help')) 91187c131baSJan Høydahl pause() 91287c131baSJan Høydahl 91387c131baSJan Høydahl 91487c131baSJan Høydahldef ensure_list(o): 91587c131baSJan Høydahl if o is None: 91687c131baSJan Høydahl return [] 91787c131baSJan Høydahl if not isinstance(o, list): 91887c131baSJan Høydahl return [o] 91987c131baSJan Høydahl else: 92087c131baSJan Høydahl return o 92187c131baSJan Høydahl 92287c131baSJan Høydahl 92387c131baSJan Høydahldef open_file(filename): 92487c131baSJan Høydahl print("Opening file %s" % filename) 92587c131baSJan Høydahl if platform.system().startswith("Win"): 92687c131baSJan Høydahl run("start %s" % filename) 92787c131baSJan Høydahl else: 92887c131baSJan Høydahl run("open %s" % filename) 92987c131baSJan Høydahl 93087c131baSJan Høydahl 93187c131baSJan Høydahldef expand_multiline(cmd_txt, indent=0): 93287c131baSJan Høydahl return re.sub(r' +', " %s\n %s" % (Commands.cmd_continuation_char, " "*indent), cmd_txt) 93387c131baSJan Høydahl 93487c131baSJan Høydahl 93587c131baSJan Høydahldef unix_to_datetime(unix_stamp): 93687c131baSJan Høydahl return datetime.utcfromtimestamp(unix_stamp / 1000) 93787c131baSJan Høydahl 93887c131baSJan Høydahl 93987c131baSJan Høydahldef generate_asciidoc(): 94087c131baSJan Høydahl base_filename = os.path.join(state.get_release_folder(), 941674b66ddSJan Høydahl "lucene_release_%s" 94287c131baSJan Høydahl % (state.release_version.replace("\.", "_"))) 94387c131baSJan Høydahl 94487c131baSJan Høydahl filename_adoc = "%s.adoc" % base_filename 94587c131baSJan Høydahl filename_html = "%s.html" % base_filename 94687c131baSJan Høydahl fh = open(filename_adoc, "w") 94787c131baSJan Høydahl 948674b66ddSJan Høydahl fh.write("= Lucene Release %s\n\n" % state.release_version) 949462c6c70SJan Høydahl fh.write("(_Generated by releaseWizard.py v%s at %s_)\n\n" 95087c131baSJan Høydahl % (getScriptVersion(), datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC"))) 95187c131baSJan Høydahl fh.write(":numbered:\n\n") 95287c131baSJan Høydahl fh.write("%s\n\n" % template('help')) 95387c131baSJan Høydahl for group in state.todo_groups: 95487c131baSJan Høydahl if group.num_applies() == 0: 95587c131baSJan Høydahl continue 95687c131baSJan Høydahl fh.write("== %s\n\n" % group.get_title()) 95787c131baSJan Høydahl fh.write("%s\n\n" % group.get_description()) 95887c131baSJan Høydahl for todo in group.get_todos(): 95987c131baSJan Høydahl if not todo.applies(state.release_type): 96087c131baSJan Høydahl continue 96187c131baSJan Høydahl fh.write("=== %s\n\n" % todo.get_title()) 96287c131baSJan Høydahl if todo.is_done(): 96387c131baSJan Høydahl fh.write("_Completed %s_\n\n" % unix_to_datetime(todo.state['done_date']).strftime( 96487c131baSJan Høydahl "%Y-%m-%d %H:%M UTC")) 96587c131baSJan Høydahl if todo.get_asciidoc(): 96687c131baSJan Høydahl fh.write("%s\n\n" % todo.get_asciidoc()) 96787c131baSJan Høydahl else: 96887c131baSJan Høydahl desc = todo.get_description() 96987c131baSJan Høydahl if desc: 97087c131baSJan Høydahl fh.write("%s\n\n" % desc) 97187c131baSJan Høydahl state_copy = copy.deepcopy(todo.state) 97287c131baSJan Høydahl state_copy.pop('done', None) 97387c131baSJan Høydahl state_copy.pop('done_date', None) 97487c131baSJan Høydahl if len(state_copy) > 0 or todo.user_input is not None: 97587c131baSJan Høydahl fh.write(".Variables collected in this step\n") 97687c131baSJan Høydahl fh.write("|===\n") 97787c131baSJan Høydahl fh.write("|Variable |Value\n") 97887c131baSJan Høydahl mykeys = set() 97987c131baSJan Høydahl for e in ensure_list(todo.user_input): 98087c131baSJan Høydahl mykeys.add(e.name) 98187c131baSJan Høydahl for e in state_copy.keys(): 98287c131baSJan Høydahl mykeys.add(e) 98387c131baSJan Høydahl for key in mykeys: 98487c131baSJan Høydahl val = "(not set)" 98587c131baSJan Høydahl if key in state_copy: 98687c131baSJan Høydahl val = state_copy[key] 98787c131baSJan Høydahl fh.write("\n|%s\n|%s\n" % (key, val)) 98887c131baSJan Høydahl fh.write("|===\n\n") 98987c131baSJan Høydahl cmds = todo.get_commands() 99087c131baSJan Høydahl if cmds: 99187c131baSJan Høydahl if cmds.commands_text: 99287c131baSJan Høydahl fh.write("%s\n\n" % cmds.get_commands_text()) 99387c131baSJan Høydahl fh.write("[source,sh]\n----\n") 99487c131baSJan Høydahl if cmds.env: 99587c131baSJan Høydahl for key in cmds.env: 99687c131baSJan Høydahl val = cmds.env[key] 99787c131baSJan Høydahl if is_windows(): 99887c131baSJan Høydahl fh.write("SET %s=%s\n" % (key, val)) 99987c131baSJan Høydahl else: 100087c131baSJan Høydahl fh.write("export %s=%s\n" % (key, val)) 100187c131baSJan Høydahl fh.write(abbreviate_homedir("cd %s\n" % cmds.get_root_folder())) 100287c131baSJan Høydahl cmds2 = ensure_list(cmds.commands) 100387c131baSJan Høydahl for c in cmds2: 100487c131baSJan Høydahl for line in c.display_cmd(): 100587c131baSJan Høydahl fh.write("%s\n" % line) 100687c131baSJan Høydahl fh.write("----\n\n") 100787c131baSJan Høydahl if todo.post_description and not todo.get_asciidoc(): 100887c131baSJan Høydahl fh.write("\n%s\n\n" % todo.get_post_description()) 100987c131baSJan Høydahl if todo.links: 101087c131baSJan Høydahl fh.write("Links:\n\n") 101187c131baSJan Høydahl for l in todo.links: 101287c131baSJan Høydahl fh.write("* %s\n" % expand_jinja(l)) 101387c131baSJan Høydahl fh.write("\n") 101487c131baSJan Høydahl 101587c131baSJan Høydahl fh.close() 101687c131baSJan Høydahl print("Wrote file %s" % os.path.join(state.get_release_folder(), filename_adoc)) 101787c131baSJan Høydahl print("Running command 'asciidoctor %s'" % filename_adoc) 101887c131baSJan Høydahl run_follow("asciidoctor %s" % filename_adoc) 101987c131baSJan Høydahl if os.path.exists(filename_html): 102087c131baSJan Høydahl open_file(filename_html) 102187c131baSJan Høydahl else: 102287c131baSJan Høydahl print("Failed generating HTML version, please install asciidoctor") 102387c131baSJan Høydahl pause() 102487c131baSJan Høydahl 102587c131baSJan Høydahl 102687c131baSJan Høydahldef load_rc(): 102787c131baSJan Høydahl lucenerc = os.path.expanduser("~/.lucenerc") 102887c131baSJan Høydahl try: 102987c131baSJan Høydahl with open(lucenerc, 'r') as fp: 103087c131baSJan Høydahl return json.load(fp) 103187c131baSJan Høydahl except: 103287c131baSJan Høydahl return None 103387c131baSJan Høydahl 103487c131baSJan Høydahl 103587c131baSJan Høydahldef store_rc(release_root, release_version=None): 103687c131baSJan Høydahl lucenerc = os.path.expanduser("~/.lucenerc") 103787c131baSJan Høydahl dict = {} 103887c131baSJan Høydahl dict['root'] = release_root 103987c131baSJan Høydahl if release_version: 104087c131baSJan Høydahl dict['release_version'] = release_version 104187c131baSJan Høydahl with open(lucenerc, "w") as fp: 104287c131baSJan Høydahl json.dump(dict, fp, indent=2) 104387c131baSJan Høydahl 104487c131baSJan Høydahl 104587c131baSJan Høydahldef release_other_version(): 104687c131baSJan Høydahl if not state.is_released(): 104787c131baSJan Høydahl maybe_remove_rc_from_svn() 104887c131baSJan Høydahl store_rc(state.config_path, None) 104987c131baSJan Høydahl print("Please restart the wizard") 105087c131baSJan Høydahl sys.exit(0) 105187c131baSJan Høydahl 105287c131baSJan Høydahldef file_to_string(filename): 105387c131baSJan Høydahl with open(filename, encoding='utf8') as f: 105487c131baSJan Høydahl return f.read().strip() 105587c131baSJan Høydahl 105687c131baSJan Høydahldef download_keys(): 105787c131baSJan Høydahl download('KEYS', "https://archive.apache.org/dist/lucene/KEYS", state.config_path) 105887c131baSJan Høydahl 105987c131baSJan Høydahldef keys_downloaded(): 106087c131baSJan Høydahl return os.path.exists(os.path.join(state.config_path, "KEYS")) 106187c131baSJan Høydahl 106287c131baSJan Høydahl 106387c131baSJan Høydahldef dump_yaml(): 106487c131baSJan Høydahl file = open(os.path.join(script_path, "releaseWizard.yaml"), "w") 106587c131baSJan Høydahl yaml.add_representer(str, str_presenter) 106687c131baSJan Høydahl yaml.Dumper.ignore_aliases = lambda *args : True 106787c131baSJan Høydahl dump_obj = {'templates': templates, 106887c131baSJan Høydahl 'groups': state.todo_groups} 106987c131baSJan Høydahl yaml.dump(dump_obj, width=180, stream=file, sort_keys=False, default_flow_style=False) 107087c131baSJan Høydahl 107187c131baSJan Høydahl 107287c131baSJan Høydahldef parse_config(): 107387c131baSJan Høydahl description = 'Script to guide a RM through the whole release process' 107487c131baSJan Høydahl parser = argparse.ArgumentParser(description=description, epilog="Go push that release!", 107587c131baSJan Høydahl formatter_class=argparse.RawDescriptionHelpFormatter) 107687c131baSJan Høydahl parser.add_argument('--dry-run', dest='dry', action='store_true', default=False, 107787c131baSJan Høydahl help='Do not execute any commands, but echo them instead. Display extra debug info') 107887c131baSJan Høydahl parser.add_argument('--init', action='store_true', default=False, 107987c131baSJan Høydahl help='Re-initialize root and version') 108087c131baSJan Høydahl config = parser.parse_args() 108187c131baSJan Høydahl 108287c131baSJan Høydahl return config 108387c131baSJan Høydahl 108487c131baSJan Høydahl 108587c131baSJan Høydahldef load(urlString, encoding="utf-8"): 108687c131baSJan Høydahl try: 108787c131baSJan Høydahl content = urllib.request.urlopen(urlString).read().decode(encoding) 108887c131baSJan Høydahl except Exception as e: 108987c131baSJan Høydahl print('Retrying download of url %s after exception: %s' % (urlString, e)) 109087c131baSJan Høydahl content = urllib.request.urlopen(urlString).read().decode(encoding) 109187c131baSJan Høydahl return content 109287c131baSJan Høydahl 109387c131baSJan Høydahl 109487c131baSJan Høydahldef configure_pgp(gpg_todo): 109587c131baSJan Høydahl print("Based on your Apache ID we'll lookup your key online\n" 109687c131baSJan Høydahl "and through this complete the 'gpg' prerequisite task.\n") 109787c131baSJan Høydahl gpg_state = gpg_todo.get_state() 109887c131baSJan Høydahl id = str(input("Please enter your Apache id: (ENTER=skip) ")) 109987c131baSJan Høydahl if id.strip() == '': 110087c131baSJan Høydahl return False 1101059d06ceSHouston Putman key_url = "https://home.apache.org/keys/committer/%s.asc" % id.strip() 1102059d06ceSHouston Putman committer_key = load(key_url) 1103059d06ceSHouston Putman lines = committer_key.splitlines() 110487c131baSJan Høydahl keyid_linenum = None 110587c131baSJan Høydahl for idx, line in enumerate(lines): 110687c131baSJan Høydahl if line == 'ASF ID: %s' % id: 110787c131baSJan Høydahl keyid_linenum = idx+1 110887c131baSJan Høydahl break 110987c131baSJan Høydahl if keyid_linenum: 111087c131baSJan Høydahl keyid_line = lines[keyid_linenum] 111187c131baSJan Høydahl assert keyid_line.startswith('LDAP PGP key: ') 111287c131baSJan Høydahl gpg_id = keyid_line[14:].replace(" ", "")[-8:] 1113059d06ceSHouston Putman print("Found gpg key id %s on file at Apache (%s)" % (gpg_id, key_url)) 111487c131baSJan Høydahl else: 111587c131baSJan Høydahl print(textwrap.dedent("""\ 111687c131baSJan Høydahl Could not find your GPG key from Apache servers. 111787c131baSJan Høydahl Please make sure you have registered your key ID in 111887c131baSJan Høydahl id.apache.org, see links for more info.""")) 111987c131baSJan Høydahl gpg_id = str(input("Enter your key ID manually, 8 last characters (ENTER=skip): ")) 112087c131baSJan Høydahl if gpg_id.strip() == '': 112187c131baSJan Høydahl return False 112287c131baSJan Høydahl elif len(gpg_id) != 8: 112387c131baSJan Høydahl print("gpg id must be the last 8 characters of your key id") 112487c131baSJan Høydahl gpg_id = gpg_id.upper() 112587c131baSJan Høydahl try: 112687c131baSJan Høydahl res = run("gpg --list-secret-keys %s" % gpg_id) 112787c131baSJan Høydahl print("Found key %s on your private gpg keychain" % gpg_id) 112887c131baSJan Høydahl # Check rsa and key length >= 4096 112987c131baSJan Høydahl match = re.search(r'^sec +((rsa|dsa)(\d{4})) ', res) 113087c131baSJan Høydahl type = "(unknown)" 113187c131baSJan Høydahl length = -1 113287c131baSJan Høydahl if match: 113387c131baSJan Høydahl type = match.group(2) 113487c131baSJan Høydahl length = int(match.group(3)) 113587c131baSJan Høydahl else: 113687c131baSJan Høydahl match = re.search(r'^sec +((\d{4})([DR])/.*?) ', res) 113787c131baSJan Høydahl if match: 113887c131baSJan Høydahl type = 'rsa' if match.group(3) == 'R' else 'dsa' 113987c131baSJan Høydahl length = int(match.group(2)) 114087c131baSJan Høydahl else: 114187c131baSJan Høydahl print("Could not determine type and key size for your key") 114287c131baSJan Høydahl print("%s" % res) 114387c131baSJan Høydahl if not ask_yes_no("Is your key of type RSA and size >= 2048 (ideally 4096)? "): 114487c131baSJan Høydahl print("Sorry, please generate a new key, add to KEYS and register with id.apache.org") 114587c131baSJan Høydahl return False 114687c131baSJan Høydahl if not type == 'rsa': 114787c131baSJan Høydahl print("We strongly recommend RSA type key, your is '%s'. Consider generating a new key." % type.upper()) 114887c131baSJan Høydahl if length < 2048: 114987c131baSJan Høydahl print("Your key has key length of %s. Cannot use < 2048, please generate a new key before doing the release" % length) 115087c131baSJan Høydahl return False 115187c131baSJan Høydahl if length < 4096: 115287c131baSJan Høydahl print("Your key length is < 4096, Please generate a stronger key.") 115387c131baSJan Høydahl print("Alternatively, follow instructions in http://www.apache.org/dev/release-signing.html#note") 115487c131baSJan Høydahl if not ask_yes_no("Have you configured your gpg to avoid SHA-1?"): 115587c131baSJan Høydahl print("Please either generate a strong key or reconfigure your client") 115687c131baSJan Høydahl return False 115787c131baSJan Høydahl print("Validated that your key is of type RSA and has a length >= 2048 (%s)" % length) 115887c131baSJan Høydahl except: 115987c131baSJan Høydahl print(textwrap.dedent("""\ 116087c131baSJan Høydahl Key not found on your private gpg keychain. In order to sign the release you'll 116187c131baSJan Høydahl need to fix this, then try again""")) 116287c131baSJan Høydahl return False 116387c131baSJan Høydahl try: 116487c131baSJan Høydahl lines = run("gpg --check-signatures %s" % gpg_id).splitlines() 116587c131baSJan Høydahl sigs = 0 116687c131baSJan Høydahl apache_sigs = 0 116787c131baSJan Høydahl for line in lines: 116887c131baSJan Høydahl if line.startswith("sig") and not gpg_id in line: 116987c131baSJan Høydahl sigs += 1 117087c131baSJan Høydahl if '@apache.org' in line: 117187c131baSJan Høydahl apache_sigs += 1 117287c131baSJan Høydahl print("Your key has %s signatures, of which %s are by committers (@apache.org address)" % (sigs, apache_sigs)) 117387c131baSJan Høydahl if apache_sigs < 1: 117487c131baSJan Høydahl print(textwrap.dedent("""\ 117587c131baSJan Høydahl Your key is not signed by any other committer. 117687c131baSJan Høydahl Please review http://www.apache.org/dev/openpgp.html#apache-wot 117787c131baSJan Høydahl and make sure to get your key signed until next time. 117887c131baSJan Høydahl You may want to run 'gpg --refresh-keys' to refresh your keychain.""")) 117987c131baSJan Høydahl uses_apacheid = is_code_signing_key = False 118087c131baSJan Høydahl for line in lines: 118187c131baSJan Høydahl if line.startswith("uid") and "%s@apache" % id in line: 118287c131baSJan Høydahl uses_apacheid = True 118387c131baSJan Høydahl if 'CODE SIGNING KEY' in line.upper(): 118487c131baSJan Høydahl is_code_signing_key = True 118587c131baSJan Høydahl if not uses_apacheid: 118687c131baSJan Høydahl print("WARNING: Your key should use your apache-id email address, see http://www.apache.org/dev/release-signing.html#user-id") 118787c131baSJan Høydahl if not is_code_signing_key: 118887c131baSJan Høydahl print("WARNING: You code signing key should be labeled 'CODE SIGNING KEY', see http://www.apache.org/dev/release-signing.html#key-comment") 118987c131baSJan Høydahl except Exception as e: 119087c131baSJan Høydahl print("Could not check signatures of your key: %s" % e) 119187c131baSJan Høydahl 119287c131baSJan Høydahl download_keys() 119387c131baSJan Høydahl keys_text = file_to_string(os.path.join(state.config_path, "KEYS")) 119487c131baSJan Høydahl if gpg_id in keys_text or "%s %s" % (gpg_id[:4], gpg_id[-4:]) in keys_text: 119587c131baSJan Høydahl print("Found your key ID in official KEYS file. KEYS file is not cached locally.") 119687c131baSJan Høydahl else: 119787c131baSJan Høydahl print(textwrap.dedent("""\ 119887c131baSJan Høydahl Could not find your key ID in official KEYS file. 119987c131baSJan Høydahl Please make sure it is added to https://dist.apache.org/repos/dist/release/lucene/KEYS 120087c131baSJan Høydahl and committed to svn. Then re-try this initialization""")) 120187c131baSJan Høydahl if not ask_yes_no("Do you want to continue without fixing KEYS file? (not recommended) "): 120287c131baSJan Høydahl return False 120387c131baSJan Høydahl 120487c131baSJan Høydahl gpg_state['apache_id'] = id 120587c131baSJan Høydahl gpg_state['gpg_key'] = gpg_id 120687c131baSJan Høydahl return True 120787c131baSJan Høydahl 120887c131baSJan Høydahl 120987c131baSJan Høydahldef pause(fun=None): 121087c131baSJan Høydahl if fun: 121187c131baSJan Høydahl fun() 121287c131baSJan Høydahl input("\nPress ENTER to continue...") 121387c131baSJan Høydahl 121487c131baSJan Høydahl 121587c131baSJan Høydahl# Custom classes for ConsoleMenu, to make menu texts dynamic 121687c131baSJan Høydahl# Needed until https://github.com/aegirhall/console-menu/pull/25 is released 121787c131baSJan Høydahl# See https://pypi.org/project/console-menu/ for other docs 121887c131baSJan Høydahl 121987c131baSJan Høydahlclass UpdatableConsoleMenu(ConsoleMenu): 122087c131baSJan Høydahl 122187c131baSJan Høydahl def __repr__(self): 122287c131baSJan Høydahl return "%s: %s. %d items" % (self.get_title(), self.get_subtitle(), len(self.items)) 122387c131baSJan Høydahl 122487c131baSJan Høydahl def draw(self): 122587c131baSJan Høydahl """ 122687c131baSJan Høydahl Refreshes the screen and redraws the menu. Should be called whenever something changes that needs to be redrawn. 122787c131baSJan Høydahl """ 122887c131baSJan Høydahl self.screen.printf(self.formatter.format(title=self.get_title(), subtitle=self.get_subtitle(), items=self.items, 122987c131baSJan Høydahl prologue_text=self.get_prologue_text(), epilogue_text=self.get_epilogue_text())) 123087c131baSJan Høydahl 123187c131baSJan Høydahl # Getters to get text in case method reference 123287c131baSJan Høydahl def get_title(self): 123387c131baSJan Høydahl return self.title() if callable(self.title) else self.title 123487c131baSJan Høydahl 123587c131baSJan Høydahl def get_subtitle(self): 123687c131baSJan Høydahl return self.subtitle() if callable(self.subtitle) else self.subtitle 123787c131baSJan Høydahl 123887c131baSJan Høydahl def get_prologue_text(self): 123987c131baSJan Høydahl return self.prologue_text() if callable(self.prologue_text) else self.prologue_text 124087c131baSJan Høydahl 124187c131baSJan Høydahl def get_epilogue_text(self): 124287c131baSJan Høydahl return self.epilogue_text() if callable(self.epilogue_text) else self.epilogue_text 124387c131baSJan Høydahl 124487c131baSJan Høydahl 124587c131baSJan Høydahlclass UpdatableSubmenuItem(SubmenuItem): 124687c131baSJan Høydahl def __init__(self, text, submenu, menu=None, should_exit=False): 124787c131baSJan Høydahl """ 124887c131baSJan Høydahl :ivar ConsoleMenu self.submenu: The submenu to be opened when this item is selected 124987c131baSJan Høydahl """ 125087c131baSJan Høydahl super(SubmenuItem, self).__init__(text=text, menu=menu, should_exit=should_exit) 125187c131baSJan Høydahl 125287c131baSJan Høydahl self.submenu = submenu 125387c131baSJan Høydahl if menu: 125487c131baSJan Høydahl self.get_submenu().parent = menu 125587c131baSJan Høydahl 125687c131baSJan Høydahl def show(self, index): 125787c131baSJan Høydahl return "%2d - %s" % (index + 1, self.get_text()) 125887c131baSJan Høydahl 125987c131baSJan Høydahl # Getters to get text in case method reference 126087c131baSJan Høydahl def get_text(self): 126187c131baSJan Høydahl return self.text() if callable(self.text) else self.text 126287c131baSJan Høydahl 126387c131baSJan Høydahl def set_menu(self, menu): 126487c131baSJan Høydahl """ 126587c131baSJan Høydahl Sets the menu of this item. 126687c131baSJan Høydahl Should be used instead of directly accessing the menu attribute for this class. 126787c131baSJan Høydahl 126887c131baSJan Høydahl :param ConsoleMenu menu: the menu 126987c131baSJan Høydahl """ 127087c131baSJan Høydahl self.menu = menu 127187c131baSJan Høydahl self.get_submenu().parent = menu 127287c131baSJan Høydahl 127387c131baSJan Høydahl def action(self): 127487c131baSJan Høydahl """ 127587c131baSJan Høydahl This class overrides this method 127687c131baSJan Høydahl """ 127787c131baSJan Høydahl self.get_submenu().start() 127887c131baSJan Høydahl 127987c131baSJan Høydahl def clean_up(self): 128087c131baSJan Høydahl """ 128187c131baSJan Høydahl This class overrides this method 128287c131baSJan Høydahl """ 128387c131baSJan Høydahl self.get_submenu().join() 128487c131baSJan Høydahl self.menu.clear_screen() 128587c131baSJan Høydahl self.menu.resume() 128687c131baSJan Høydahl 128787c131baSJan Høydahl def get_return(self): 128887c131baSJan Høydahl """ 128987c131baSJan Høydahl :return: The returned value in the submenu 129087c131baSJan Høydahl """ 129187c131baSJan Høydahl return self.get_submenu().returned_value 129287c131baSJan Høydahl 129387c131baSJan Høydahl def get_submenu(self): 129487c131baSJan Høydahl """ 129587c131baSJan Høydahl We unwrap the submenu variable in case it is a reference to a method that returns a submenu 129687c131baSJan Høydahl """ 129787c131baSJan Høydahl return self.submenu if not callable(self.submenu) else self.submenu() 129887c131baSJan Høydahl 129987c131baSJan Høydahl 130087c131baSJan Høydahlclass UpdatableFunctionItem(FunctionItem): 130187c131baSJan Høydahl def show(self, index): 130287c131baSJan Høydahl return "%2d - %s" % (index + 1, self.get_text()) 130387c131baSJan Høydahl 130487c131baSJan Høydahl # Getters to get text in case method reference 130587c131baSJan Høydahl def get_text(self): 130687c131baSJan Høydahl return self.text() if callable(self.text) else self.text 130787c131baSJan Høydahl 130887c131baSJan Høydahl 130987c131baSJan Høydahlclass MyScreen(Screen): 131087c131baSJan Høydahl def clear(self): 131187c131baSJan Høydahl return 131287c131baSJan Høydahl 131387c131baSJan Høydahl 131487c131baSJan Høydahlclass CustomExitItem(ExitItem): 131587c131baSJan Høydahl def show(self, index): 131687c131baSJan Høydahl return super(ExitItem, self).show(index) 131787c131baSJan Høydahl 131887c131baSJan Høydahl def get_return(self): 131987c131baSJan Høydahl return "" 132087c131baSJan Høydahl 132187c131baSJan Høydahl 132287c131baSJan Høydahldef main(): 132387c131baSJan Høydahl global state 132487c131baSJan Høydahl global dry_run 132587c131baSJan Høydahl global templates 132687c131baSJan Høydahl 1327674b66ddSJan Høydahl print("Lucene releaseWizard v%s" % getScriptVersion()) 132887c131baSJan Høydahl c = parse_config() 132987c131baSJan Høydahl 133087c131baSJan Høydahl if c.dry: 133187c131baSJan Høydahl print("Entering dry-run mode where all commands will be echoed instead of executed") 133287c131baSJan Høydahl dry_run = True 133387c131baSJan Høydahl 133487c131baSJan Høydahl release_root = os.path.expanduser("~/.lucene-releases") 133587c131baSJan Høydahl if not load_rc() or c.init: 133687c131baSJan Høydahl print("Initializing") 133787c131baSJan Høydahl dir_ok = False 133887c131baSJan Høydahl root = str(input("Choose root folder: [~/.lucene-releases] ")) 133987c131baSJan Høydahl if os.path.exists(root) and (not os.path.isdir(root) or not os.access(root, os.W_OK)): 134087c131baSJan Høydahl sys.exit("Root %s exists but is not a directory or is not writable" % root) 134187c131baSJan Høydahl if not root == '': 134287c131baSJan Høydahl if root.startswith("~/"): 134387c131baSJan Høydahl release_root = os.path.expanduser(root) 134487c131baSJan Høydahl else: 134587c131baSJan Høydahl release_root = os.path.abspath(root) 134687c131baSJan Høydahl if not os.path.exists(release_root): 134787c131baSJan Høydahl try: 134887c131baSJan Høydahl print("Creating release root %s" % release_root) 134987c131baSJan Høydahl os.makedirs(release_root) 135087c131baSJan Høydahl except Exception as e: 135187c131baSJan Høydahl sys.exit("Error while creating %s: %s" % (release_root, e)) 135287c131baSJan Høydahl release_version = get_release_version() 135387c131baSJan Høydahl else: 135487c131baSJan Høydahl conf = load_rc() 135587c131baSJan Høydahl release_root = conf['root'] 135687c131baSJan Høydahl if 'release_version' in conf: 135787c131baSJan Høydahl release_version = conf['release_version'] 135887c131baSJan Høydahl else: 135987c131baSJan Høydahl release_version = get_release_version() 136087c131baSJan Høydahl store_rc(release_root, release_version) 136187c131baSJan Høydahl 136287c131baSJan Høydahl check_prerequisites() 136387c131baSJan Høydahl 136487c131baSJan Høydahl try: 136587c131baSJan Høydahl y = yaml.load(open(os.path.join(script_path, "releaseWizard.yaml"), "r"), Loader=yaml.Loader) 136687c131baSJan Høydahl templates = y.get('templates') 136787c131baSJan Høydahl todo_list = y.get('groups') 136887c131baSJan Høydahl state = ReleaseState(release_root, release_version, getScriptVersion()) 136987c131baSJan Høydahl state.init_todos(bootstrap_todos(todo_list)) 137087c131baSJan Høydahl state.load() 137187c131baSJan Høydahl except Exception as e: 137287c131baSJan Høydahl sys.exit("Failed initializing. %s" % e) 137387c131baSJan Høydahl 137487c131baSJan Høydahl state.save() 137587c131baSJan Høydahl 13763134f10aSMike Drob # Smoketester requires JAVA11_HOME to point to Java11 137787c131baSJan Høydahl os.environ['JAVA_HOME'] = state.get_java_home() 137887c131baSJan Høydahl os.environ['JAVACMD'] = state.get_java_cmd() 137987c131baSJan Høydahl 138087c131baSJan Høydahl global lucene_news_file 1381329e7c7bSJan Høydahl lucene_news_file = os.path.join(state.get_website_git_folder(), 'content', 'core', 'core_news', 1382329e7c7bSJan Høydahl "%s-%s-available.md" % (state.get_release_date_iso(), state.release_version.replace(".", "-"))) 1383329e7c7bSJan Høydahl website_folder = state.get_website_git_folder() 138487c131baSJan Høydahl 1385674b66ddSJan Høydahl main_menu = UpdatableConsoleMenu(title="Lucene ReleaseWizard", 138687c131baSJan Høydahl subtitle=get_releasing_text, 138787c131baSJan Høydahl prologue_text="Welcome to the release wizard. From here you can manage the process including creating new RCs. " 138887c131baSJan Høydahl "All changes are persisted, so you can exit any time and continue later. Make sure to read the Help section.", 1389674b66ddSJan Høydahl epilogue_text="® 2021 The Lucene project. Licensed under the Apache License 2.0\nScript version v%s)" % getScriptVersion(), 139087c131baSJan Høydahl screen=MyScreen()) 139187c131baSJan Høydahl 139287c131baSJan Høydahl todo_menu = UpdatableConsoleMenu(title=get_releasing_text, 139387c131baSJan Høydahl subtitle=get_subtitle, 139487c131baSJan Høydahl prologue_text=None, 139587c131baSJan Høydahl epilogue_text=None, 139687c131baSJan Høydahl screen=MyScreen()) 139787c131baSJan Høydahl todo_menu.exit_item = CustomExitItem("Return") 139887c131baSJan Høydahl 139987c131baSJan Høydahl for todo_group in state.todo_groups: 140087c131baSJan Høydahl if todo_group.num_applies() >= 0: 140187c131baSJan Høydahl menu_item = todo_group.get_menu_item() 140287c131baSJan Høydahl menu_item.set_menu(todo_menu) 140387c131baSJan Høydahl todo_menu.append_item(menu_item) 140487c131baSJan Høydahl 140587c131baSJan Høydahl main_menu.append_item(UpdatableSubmenuItem(get_todo_menuitem_title, todo_menu, menu=main_menu)) 140687c131baSJan Høydahl main_menu.append_item(UpdatableFunctionItem(get_start_new_rc_menu_title, start_new_rc)) 140787c131baSJan Høydahl main_menu.append_item(UpdatableFunctionItem('Clear and restart current RC', state.clear_rc)) 140887c131baSJan Høydahl main_menu.append_item(UpdatableFunctionItem("Clear all state, restart the %s release" % state.release_version, reset_state)) 140987c131baSJan Høydahl main_menu.append_item(UpdatableFunctionItem('Start release for a different version', release_other_version)) 141087c131baSJan Høydahl main_menu.append_item(UpdatableFunctionItem('Generate Asciidoc guide for this release', generate_asciidoc)) 141187c131baSJan Høydahl # main_menu.append_item(UpdatableFunctionItem('Dump YAML', dump_yaml)) 141287c131baSJan Høydahl main_menu.append_item(UpdatableFunctionItem('Help', help)) 141387c131baSJan Høydahl 141487c131baSJan Høydahl main_menu.show() 141587c131baSJan Høydahl 141687c131baSJan Høydahl 141787c131baSJan Høydahlsys.path.append(os.path.dirname(__file__)) 141887c131baSJan Høydahlcurrent_git_root = os.path.abspath( 141987c131baSJan Høydahl os.path.join(os.path.abspath(os.path.dirname(__file__)), os.path.pardir, os.path.pardir)) 142087c131baSJan Høydahl 142187c131baSJan Høydahldry_run = False 142287c131baSJan Høydahl 142387c131baSJan Høydahlmajor_minor = ['major', 'minor'] 142487c131baSJan Høydahlscript_path = os.path.dirname(os.path.realpath(__file__)) 142587c131baSJan Høydahlos.chdir(script_path) 142687c131baSJan Høydahl 142787c131baSJan Høydahl 142887c131baSJan Høydahldef git_checkout_folder(): 142987c131baSJan Høydahl return state.get_git_checkout_folder() 143087c131baSJan Høydahl 143187c131baSJan Høydahl 143287c131baSJan Høydahldef tail_file(file, lines): 143387c131baSJan Høydahl bufsize = 8192 143487c131baSJan Høydahl fsize = os.stat(file).st_size 143587c131baSJan Høydahl with open(file) as f: 143687c131baSJan Høydahl if bufsize >= fsize: 143787c131baSJan Høydahl bufsize = fsize 143887c131baSJan Høydahl idx = 0 143987c131baSJan Høydahl while True: 144087c131baSJan Høydahl idx += 1 144187c131baSJan Høydahl seek_pos = fsize - bufsize * idx 144287c131baSJan Høydahl if seek_pos < 0: 144387c131baSJan Høydahl seek_pos = 0 144487c131baSJan Høydahl f.seek(seek_pos) 144587c131baSJan Høydahl data = [] 144687c131baSJan Høydahl data.extend(f.readlines()) 144787c131baSJan Høydahl if len(data) >= lines or f.tell() == 0 or seek_pos == 0: 144887c131baSJan Høydahl if not seek_pos == 0: 144987c131baSJan Høydahl print("Tailing last %d lines of file %s" % (lines, file)) 145087c131baSJan Høydahl print(''.join(data[-lines:])) 145187c131baSJan Høydahl break 145287c131baSJan Høydahl 145387c131baSJan Høydahl 145487c131baSJan Høydahldef run_with_log_tail(command, cwd, logfile=None, tail_lines=10, tee=False, live=False, shell=None): 145587c131baSJan Høydahl fh = sys.stdout 145687c131baSJan Høydahl if logfile: 145787c131baSJan Høydahl logdir = os.path.dirname(logfile) 145887c131baSJan Høydahl if not os.path.exists(logdir): 145987c131baSJan Høydahl os.makedirs(logdir) 146087c131baSJan Høydahl fh = open(logfile, 'w') 146187c131baSJan Høydahl rc = run_follow(command, cwd, fh=fh, tee=tee, live=live, shell=shell) 146287c131baSJan Høydahl if logfile: 146387c131baSJan Høydahl fh.close() 146487c131baSJan Høydahl if not tee and tail_lines and tail_lines > 0: 146587c131baSJan Høydahl tail_file(logfile, tail_lines) 146687c131baSJan Høydahl return rc 146787c131baSJan Høydahl 146887c131baSJan Høydahl 146987c131baSJan Høydahldef ask_yes_no(text): 147087c131baSJan Høydahl answer = None 147187c131baSJan Høydahl while answer not in ['y', 'n']: 147287c131baSJan Høydahl answer = str(input("\nQ: %s (y/n): " % text)) 147387c131baSJan Høydahl print("\n") 147487c131baSJan Høydahl return answer == 'y' 147587c131baSJan Høydahl 147687c131baSJan Høydahl 147787c131baSJan Høydahldef abbreviate_line(line, width): 147887c131baSJan Høydahl line = line.rstrip() 147987c131baSJan Høydahl if len(line) > width: 148087c131baSJan Høydahl line = "%s.....%s" % (line[:(width / 2 - 5)], line[-(width / 2):]) 148187c131baSJan Høydahl else: 148287c131baSJan Høydahl line = "%s%s" % (line, " " * (width - len(line) + 2)) 148387c131baSJan Høydahl return line 148487c131baSJan Høydahl 148587c131baSJan Høydahl 148687c131baSJan Høydahldef print_line_cr(line, linenum, stdout=True, tee=False): 148787c131baSJan Høydahl if not tee: 148887c131baSJan Høydahl if not stdout: 148987c131baSJan Høydahl print("[line %s] %s" % (linenum, abbreviate_line(line, 80)), end='\r') 149087c131baSJan Høydahl else: 149187c131baSJan Høydahl if line.endswith("\r"): 149287c131baSJan Høydahl print(line.rstrip(), end='\r') 149387c131baSJan Høydahl else: 149487c131baSJan Høydahl print(line.rstrip()) 149587c131baSJan Høydahl 149687c131baSJan Høydahl 149787c131baSJan Høydahldef run_follow(command, cwd=None, fh=sys.stdout, tee=False, live=False, shell=None): 149887c131baSJan Høydahl doShell = '&&' in command or '&' in command or shell is not None 149987c131baSJan Høydahl if not doShell and not isinstance(command, list): 150087c131baSJan Høydahl command = shlex.split(command) 150187c131baSJan Høydahl process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, 150287c131baSJan Høydahl universal_newlines=True, bufsize=0, close_fds=True, shell=doShell) 150387c131baSJan Høydahl lines_written = 0 150487c131baSJan Høydahl 150587c131baSJan Høydahl fl = fcntl.fcntl(process.stdout, fcntl.F_GETFL) 150687c131baSJan Høydahl fcntl.fcntl(process.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK) 150787c131baSJan Høydahl 150887c131baSJan Høydahl flerr = fcntl.fcntl(process.stderr, fcntl.F_GETFL) 150987c131baSJan Høydahl fcntl.fcntl(process.stderr, fcntl.F_SETFL, flerr | os.O_NONBLOCK) 151087c131baSJan Høydahl 151187c131baSJan Høydahl endstdout = endstderr = False 151287c131baSJan Høydahl errlines = [] 151387c131baSJan Høydahl while not (endstderr and endstdout): 151487c131baSJan Høydahl lines_before = lines_written 151587c131baSJan Høydahl if not endstdout: 151687c131baSJan Høydahl try: 151787c131baSJan Høydahl if live: 151887c131baSJan Høydahl chars = process.stdout.read() 151987c131baSJan Høydahl if chars == '' and process.poll() is not None: 152087c131baSJan Høydahl endstdout = True 152187c131baSJan Høydahl else: 152287c131baSJan Høydahl fh.write(chars) 152387c131baSJan Høydahl fh.flush() 152487c131baSJan Høydahl if '\n' in chars: 152587c131baSJan Høydahl lines_written += 1 152687c131baSJan Høydahl else: 152787c131baSJan Høydahl line = process.stdout.readline() 152887c131baSJan Høydahl if line == '' and process.poll() is not None: 152987c131baSJan Høydahl endstdout = True 153087c131baSJan Høydahl else: 153187c131baSJan Høydahl fh.write("%s\n" % line.rstrip()) 153287c131baSJan Høydahl fh.flush() 153387c131baSJan Høydahl lines_written += 1 153487c131baSJan Høydahl print_line_cr(line, lines_written, stdout=(fh == sys.stdout), tee=tee) 153587c131baSJan Høydahl 153687c131baSJan Høydahl except Exception as ioe: 153787c131baSJan Høydahl pass 153887c131baSJan Høydahl if not endstderr: 153987c131baSJan Høydahl try: 154087c131baSJan Høydahl if live: 154187c131baSJan Høydahl chars = process.stderr.read() 154287c131baSJan Høydahl if chars == '' and process.poll() is not None: 154387c131baSJan Høydahl endstderr = True 154487c131baSJan Høydahl else: 154587c131baSJan Høydahl fh.write(chars) 154687c131baSJan Høydahl fh.flush() 154787c131baSJan Høydahl if '\n' in chars: 154887c131baSJan Høydahl lines_written += 1 154987c131baSJan Høydahl else: 155087c131baSJan Høydahl line = process.stderr.readline() 155187c131baSJan Høydahl if line == '' and process.poll() is not None: 155287c131baSJan Høydahl endstderr = True 155387c131baSJan Høydahl else: 155487c131baSJan Høydahl errlines.append("%s\n" % line.rstrip()) 155587c131baSJan Høydahl lines_written += 1 155687c131baSJan Høydahl print_line_cr(line, lines_written, stdout=(fh == sys.stdout), tee=tee) 155787c131baSJan Høydahl except Exception as e: 155887c131baSJan Høydahl pass 155987c131baSJan Høydahl 156087c131baSJan Høydahl if not lines_written > lines_before: 156187c131baSJan Høydahl # if no output then sleep a bit before checking again 156287c131baSJan Høydahl time.sleep(0.1) 156387c131baSJan Høydahl 156487c131baSJan Høydahl print(" " * 80) 156587c131baSJan Høydahl rc = process.poll() 156687c131baSJan Høydahl if len(errlines) > 0: 156787c131baSJan Høydahl for line in errlines: 156887c131baSJan Høydahl fh.write("%s\n" % line.rstrip()) 156987c131baSJan Høydahl fh.flush() 157087c131baSJan Høydahl return rc 157187c131baSJan Høydahl 157287c131baSJan Høydahl 157387c131baSJan Høydahldef is_windows(): 157487c131baSJan Høydahl return platform.system().startswith("Win") 157587c131baSJan Høydahl 1576329e7c7bSJan Høydahldef is_mac(): 1577329e7c7bSJan Høydahl return platform.system().startswith("Darwin") 1578329e7c7bSJan Høydahl 1579329e7c7bSJan Høydahldef is_linux(): 1580329e7c7bSJan Høydahl return platform.system().startswith("Linux") 158187c131baSJan Høydahl 158287c131baSJan Høydahlclass Commands(SecretYamlObject): 158387c131baSJan Høydahl yaml_tag = u'!Commands' 158487c131baSJan Høydahl hidden_fields = ['todo_id'] 158587c131baSJan Høydahl cmd_continuation_char = "^" if is_windows() else "\\" 158687c131baSJan Høydahl def __init__(self, root_folder, commands_text=None, commands=None, logs_prefix=None, run_text=None, enable_execute=None, 158787c131baSJan Høydahl confirm_each_command=None, env=None, vars=None, todo_id=None, remove_files=None): 158887c131baSJan Høydahl self.root_folder = root_folder 158987c131baSJan Høydahl self.commands_text = commands_text 159087c131baSJan Høydahl self.vars = vars 159187c131baSJan Høydahl self.env = env 159287c131baSJan Høydahl self.run_text = run_text 159387c131baSJan Høydahl self.remove_files = remove_files 159487c131baSJan Høydahl self.todo_id = todo_id 159587c131baSJan Høydahl self.logs_prefix = logs_prefix 159687c131baSJan Høydahl self.enable_execute = enable_execute 159787c131baSJan Høydahl self.confirm_each_command = confirm_each_command 159887c131baSJan Høydahl self.commands = commands 159987c131baSJan Høydahl for c in self.commands: 160087c131baSJan Høydahl c.todo_id = todo_id 160187c131baSJan Høydahl 160287c131baSJan Høydahl @classmethod 160387c131baSJan Høydahl def from_yaml(cls, loader, node): 160487c131baSJan Høydahl fields = loader.construct_mapping(node, deep = True) 160587c131baSJan Høydahl return Commands(**fields) 160687c131baSJan Høydahl 160787c131baSJan Høydahl def run(self): 160887c131baSJan Høydahl root = self.get_root_folder() 160987c131baSJan Høydahl 161087c131baSJan Høydahl if self.commands_text: 161187c131baSJan Høydahl print(self.get_commands_text()) 161287c131baSJan Høydahl if self.env: 161387c131baSJan Høydahl for key in self.env: 161487c131baSJan Høydahl val = self.jinjaify(self.env[key]) 161587c131baSJan Høydahl os.environ[key] = val 161687c131baSJan Høydahl if is_windows(): 161787c131baSJan Høydahl print("\n SET %s=%s" % (key, val)) 161887c131baSJan Høydahl else: 161987c131baSJan Høydahl print("\n export %s=%s" % (key, val)) 162087c131baSJan Høydahl print(abbreviate_homedir("\n cd %s" % root)) 162187c131baSJan Høydahl commands = ensure_list(self.commands) 162287c131baSJan Høydahl for cmd in commands: 162387c131baSJan Høydahl for line in cmd.display_cmd(): 162487c131baSJan Høydahl print(" %s" % line) 162587c131baSJan Høydahl print() 162687c131baSJan Høydahl if not self.enable_execute is False: 162787c131baSJan Høydahl if self.run_text: 162887c131baSJan Høydahl print("\n%s\n" % self.get_run_text()) 162987c131baSJan Høydahl if len(commands) > 1: 163087c131baSJan Høydahl if not self.confirm_each_command is False: 163187c131baSJan Høydahl print("You will get prompted before running each individual command.") 163287c131baSJan Høydahl else: 163387c131baSJan Høydahl print( 163487c131baSJan Høydahl "You will not be prompted for each command but will see the ouput of each. If one command fails the execution will stop.") 163587c131baSJan Høydahl success = True 163687c131baSJan Høydahl if ask_yes_no("Do you want me to run these commands now?"): 163787c131baSJan Høydahl if self.remove_files: 163887c131baSJan Høydahl for _f in ensure_list(self.get_remove_files()): 163987c131baSJan Høydahl f = os.path.join(root, _f) 164087c131baSJan Høydahl if os.path.exists(f): 164187c131baSJan Høydahl filefolder = "File" if os.path.isfile(f) else "Folder" 164287c131baSJan Høydahl if ask_yes_no("%s %s already exists. Shall I remove it now?" % (filefolder, f)) and not dry_run: 164387c131baSJan Høydahl if os.path.isdir(f): 164487c131baSJan Høydahl shutil.rmtree(f) 164587c131baSJan Høydahl else: 164687c131baSJan Høydahl os.remove(f) 164787c131baSJan Høydahl index = 0 164887c131baSJan Høydahl log_folder = self.logs_prefix if len(commands) > 1 else None 164987c131baSJan Høydahl for cmd in commands: 165087c131baSJan Høydahl index += 1 165187c131baSJan Høydahl if len(commands) > 1: 165287c131baSJan Høydahl log_prefix = "%02d_" % index 165387c131baSJan Høydahl else: 165487c131baSJan Høydahl log_prefix = self.logs_prefix if self.logs_prefix else '' 165587c131baSJan Høydahl if not log_prefix[-1:] == '_': 165687c131baSJan Høydahl log_prefix += "_" 165787c131baSJan Høydahl cwd = root 165887c131baSJan Høydahl if cmd.cwd: 165987c131baSJan Høydahl cwd = os.path.join(root, cmd.cwd) 166087c131baSJan Høydahl folder_prefix = '' 166187c131baSJan Høydahl if cmd.cwd: 166287c131baSJan Høydahl folder_prefix = cmd.cwd + "_" 166387c131baSJan Høydahl if self.confirm_each_command is False or len(commands) == 1 or ask_yes_no("Shall I run '%s' in folder '%s'" % (cmd, cwd)): 166487c131baSJan Høydahl if self.confirm_each_command is False: 166587c131baSJan Høydahl print("------------\nRunning '%s' in folder '%s'" % (cmd, cwd)) 166687c131baSJan Høydahl logfilename = cmd.logfile 166787c131baSJan Høydahl logfile = None 166887c131baSJan Høydahl cmd_to_run = "%s%s" % ("echo Dry run, command is: " if dry_run else "", cmd.get_cmd()) 166987c131baSJan Høydahl if cmd.redirect: 167087c131baSJan Høydahl try: 167187c131baSJan Høydahl out = run(cmd_to_run, cwd=cwd) 167287c131baSJan Høydahl mode = 'a' if cmd.redirect_append is True else 'w' 167387c131baSJan Høydahl with open(os.path.join(root, cwd, cmd.get_redirect()), mode) as outfile: 167487c131baSJan Høydahl outfile.write(out) 167587c131baSJan Høydahl outfile.flush() 167687c131baSJan Høydahl print("Wrote %s bytes to redirect file %s" % (len(out), cmd.get_redirect())) 167787c131baSJan Høydahl except Exception as e: 167887c131baSJan Høydahl print("Command %s failed: %s" % (cmd_to_run, e)) 167987c131baSJan Høydahl success = False 168087c131baSJan Høydahl break 168187c131baSJan Høydahl else: 168287c131baSJan Høydahl if not cmd.stdout: 168387c131baSJan Høydahl if not log_folder: 168487c131baSJan Høydahl log_folder = os.path.join(state.get_rc_folder(), "logs") 168587c131baSJan Høydahl elif not os.path.isabs(log_folder): 168687c131baSJan Høydahl log_folder = os.path.join(state.get_rc_folder(), "logs", log_folder) 168787c131baSJan Høydahl if not logfilename: 168887c131baSJan Høydahl logfilename = "%s.log" % re.sub(r"\W", "_", cmd.get_cmd()) 168987c131baSJan Høydahl logfile = os.path.join(log_folder, "%s%s%s" % (log_prefix, folder_prefix, logfilename)) 169087c131baSJan Høydahl if cmd.tee: 169187c131baSJan Høydahl print("Output of command will be printed (logfile=%s)" % logfile) 169287c131baSJan Høydahl elif cmd.live: 169387c131baSJan Høydahl print("Output will be shown live byte by byte") 169487c131baSJan Høydahl logfile = None 169587c131baSJan Høydahl else: 169687c131baSJan Høydahl print("Wait until command completes... Full log in %s\n" % logfile) 169787c131baSJan Høydahl if cmd.comment: 169887c131baSJan Høydahl print("# %s\n" % cmd.get_comment()) 169987c131baSJan Høydahl start_time = time.time() 170087c131baSJan Høydahl returncode = run_with_log_tail(cmd_to_run, cwd, logfile=logfile, tee=cmd.tee, tail_lines=25, 170187c131baSJan Høydahl live=cmd.live, shell=cmd.shell) 170287c131baSJan Høydahl elapsed = time.time() - start_time 170387c131baSJan Høydahl if not returncode == 0: 170487c131baSJan Høydahl if cmd.should_fail: 170587c131baSJan Høydahl print("Command failed, which was expected") 170687c131baSJan Høydahl success = True 170787c131baSJan Høydahl else: 170887c131baSJan Høydahl print("WARN: Command %s returned with error" % cmd.get_cmd()) 170987c131baSJan Høydahl success = False 171087c131baSJan Høydahl break 171187c131baSJan Høydahl else: 171287c131baSJan Høydahl if cmd.should_fail and not dry_run: 171387c131baSJan Høydahl print("Expected command to fail, but it succeeded.") 171487c131baSJan Høydahl success = False 171587c131baSJan Høydahl break 171687c131baSJan Høydahl else: 171787c131baSJan Høydahl if elapsed > 30: 171887c131baSJan Høydahl print("Command completed in %s seconds" % elapsed) 171987c131baSJan Høydahl if not success: 172087c131baSJan Høydahl print("WARNING: One or more commands failed, you may want to check the logs") 172187c131baSJan Høydahl return success 172287c131baSJan Høydahl 172387c131baSJan Høydahl def get_root_folder(self): 172487c131baSJan Høydahl return self.jinjaify(self.root_folder) 172587c131baSJan Høydahl 172687c131baSJan Høydahl def get_commands_text(self): 172787c131baSJan Høydahl return self.jinjaify(self.commands_text) 172887c131baSJan Høydahl 172987c131baSJan Høydahl def get_run_text(self): 173087c131baSJan Høydahl return self.jinjaify(self.run_text) 173187c131baSJan Høydahl 173287c131baSJan Høydahl def get_remove_files(self): 173387c131baSJan Høydahl return self.jinjaify(self.remove_files) 173487c131baSJan Høydahl 173587c131baSJan Høydahl def get_vars(self): 173687c131baSJan Høydahl myvars = {} 173787c131baSJan Høydahl if self.vars: 173887c131baSJan Høydahl for k in self.vars: 173987c131baSJan Høydahl val = self.vars[k] 174087c131baSJan Høydahl if callable(val): 174187c131baSJan Høydahl myvars[k] = expand_jinja(val(), vars=myvars) 174287c131baSJan Høydahl else: 174387c131baSJan Høydahl myvars[k] = expand_jinja(val, vars=myvars) 174487c131baSJan Høydahl return myvars 174587c131baSJan Høydahl 174687c131baSJan Høydahl def jinjaify(self, data, join=False): 174787c131baSJan Høydahl if not data: 174887c131baSJan Høydahl return None 174987c131baSJan Høydahl v = self.get_vars() 175087c131baSJan Høydahl if self.todo_id: 175187c131baSJan Høydahl v.update(state.get_todo_by_id(self.todo_id).get_vars()) 175287c131baSJan Høydahl if isinstance(data, list): 175387c131baSJan Høydahl if join: 175487c131baSJan Høydahl return expand_jinja(" ".join(data), v) 175587c131baSJan Høydahl else: 175687c131baSJan Høydahl res = [] 175787c131baSJan Høydahl for rf in data: 175887c131baSJan Høydahl res.append(expand_jinja(rf, v)) 175987c131baSJan Høydahl return res 176087c131baSJan Høydahl else: 176187c131baSJan Høydahl return expand_jinja(data, v) 176287c131baSJan Høydahl 176387c131baSJan Høydahl 176487c131baSJan Høydahldef abbreviate_homedir(line): 176587c131baSJan Høydahl if is_windows(): 176687c131baSJan Høydahl if 'HOME' in os.environ: 176787c131baSJan Høydahl return re.sub(r'([^/]|\b)%s' % os.path.expanduser('~'), "\\1%HOME%", line) 176887c131baSJan Høydahl elif 'USERPROFILE' in os.environ: 176987c131baSJan Høydahl return re.sub(r'([^/]|\b)%s' % os.path.expanduser('~'), "\\1%USERPROFILE%", line) 177087c131baSJan Høydahl else: 177187c131baSJan Høydahl return re.sub(r'([^/]|\b)%s' % os.path.expanduser('~'), "\\1~", line) 177287c131baSJan Høydahl 177387c131baSJan Høydahl 177487c131baSJan Høydahlclass Command(SecretYamlObject): 177587c131baSJan Høydahl yaml_tag = u'!Command' 177687c131baSJan Høydahl hidden_fields = ['todo_id'] 177787c131baSJan Høydahl def __init__(self, cmd, cwd=None, stdout=None, logfile=None, tee=None, live=None, comment=None, vars=None, 177887c131baSJan Høydahl todo_id=None, should_fail=None, redirect=None, redirect_append=None, shell=None): 177987c131baSJan Høydahl self.cmd = cmd 178087c131baSJan Høydahl self.cwd = cwd 178187c131baSJan Høydahl self.comment = comment 178287c131baSJan Høydahl self.logfile = logfile 178387c131baSJan Høydahl self.vars = vars 178487c131baSJan Høydahl self.tee = tee 178587c131baSJan Høydahl self.live = live 178687c131baSJan Høydahl self.stdout = stdout 178787c131baSJan Høydahl self.should_fail = should_fail 178887c131baSJan Høydahl self.shell = shell 178987c131baSJan Høydahl self.todo_id = todo_id 179087c131baSJan Høydahl self.redirect_append = redirect_append 179187c131baSJan Høydahl self.redirect = redirect 179287c131baSJan Høydahl if tee and stdout: 179387c131baSJan Høydahl self.stdout = None 179487c131baSJan Høydahl print("Command %s specifies 'tee' and 'stdout', using only 'tee'" % self.cmd) 179587c131baSJan Høydahl if live and stdout: 179687c131baSJan Høydahl self.stdout = None 179787c131baSJan Høydahl print("Command %s specifies 'live' and 'stdout', using only 'live'" % self.cmd) 179887c131baSJan Høydahl if live and tee: 179987c131baSJan Høydahl self.tee = None 180087c131baSJan Høydahl print("Command %s specifies 'tee' and 'live', using only 'live'" % self.cmd) 180187c131baSJan Høydahl if redirect and (tee or stdout or live): 180287c131baSJan Høydahl self.tee = self.stdout = self.live = None 180387c131baSJan Høydahl print("Command %s specifies 'redirect' and other out options at the same time. Using redirect only" % self.cmd) 180487c131baSJan Høydahl 180587c131baSJan Høydahl @classmethod 180687c131baSJan Høydahl def from_yaml(cls, loader, node): 180787c131baSJan Høydahl fields = loader.construct_mapping(node, deep = True) 180887c131baSJan Høydahl return Command(**fields) 180987c131baSJan Høydahl 181087c131baSJan Høydahl def get_comment(self): 181187c131baSJan Høydahl return self.jinjaify(self.comment) 181287c131baSJan Høydahl 181387c131baSJan Høydahl def get_redirect(self): 181487c131baSJan Høydahl return self.jinjaify(self.redirect) 181587c131baSJan Høydahl 181687c131baSJan Høydahl def get_cmd(self): 181787c131baSJan Høydahl return self.jinjaify(self.cmd, join=True) 181887c131baSJan Høydahl 181987c131baSJan Høydahl def get_vars(self): 182087c131baSJan Høydahl myvars = {} 182187c131baSJan Høydahl if self.vars: 182287c131baSJan Høydahl for k in self.vars: 182387c131baSJan Høydahl val = self.vars[k] 182487c131baSJan Høydahl if callable(val): 182587c131baSJan Høydahl myvars[k] = expand_jinja(val(), vars=myvars) 182687c131baSJan Høydahl else: 182787c131baSJan Høydahl myvars[k] = expand_jinja(val, vars=myvars) 182887c131baSJan Høydahl return myvars 182987c131baSJan Høydahl 183087c131baSJan Høydahl def __str__(self): 183187c131baSJan Høydahl return self.get_cmd() 183287c131baSJan Høydahl 183387c131baSJan Høydahl def jinjaify(self, data, join=False): 183487c131baSJan Høydahl v = self.get_vars() 183587c131baSJan Høydahl if self.todo_id: 183687c131baSJan Høydahl v.update(state.get_todo_by_id(self.todo_id).get_vars()) 183787c131baSJan Høydahl if isinstance(data, list): 183887c131baSJan Høydahl if join: 183987c131baSJan Høydahl return expand_jinja(" ".join(data), v) 184087c131baSJan Høydahl else: 184187c131baSJan Høydahl res = [] 184287c131baSJan Høydahl for rf in data: 184387c131baSJan Høydahl res.append(expand_jinja(rf, v)) 184487c131baSJan Høydahl return res 184587c131baSJan Høydahl else: 184687c131baSJan Høydahl return expand_jinja(data, v) 184787c131baSJan Høydahl 184887c131baSJan Høydahl def display_cmd(self): 184987c131baSJan Høydahl lines = [] 185087c131baSJan Høydahl pre = post = '' 185187c131baSJan Høydahl if self.comment: 185287c131baSJan Høydahl if is_windows(): 185387c131baSJan Høydahl lines.append("REM %s" % self.get_comment()) 185487c131baSJan Høydahl else: 185587c131baSJan Høydahl lines.append("# %s" % self.get_comment()) 185687c131baSJan Høydahl if self.cwd: 185787c131baSJan Høydahl lines.append("pushd %s" % self.cwd) 185887c131baSJan Høydahl redir = "" if self.redirect is None else " %s %s" % (">" if self.redirect_append is None else ">>" , self.get_redirect()) 185987c131baSJan Høydahl line = "%s%s" % (expand_multiline(self.get_cmd(), indent=2), redir) 186087c131baSJan Høydahl # Print ~ or %HOME% rather than the full expanded homedir path 186187c131baSJan Høydahl line = abbreviate_homedir(line) 186287c131baSJan Høydahl lines.append(line) 186387c131baSJan Høydahl if self.cwd: 186487c131baSJan Høydahl lines.append("popd") 186587c131baSJan Høydahl return lines 186687c131baSJan Høydahl 186787c131baSJan Høydahlclass UserInput(SecretYamlObject): 186887c131baSJan Høydahl yaml_tag = u'!UserInput' 186987c131baSJan Høydahl 187087c131baSJan Høydahl def __init__(self, name, prompt, type=None): 187187c131baSJan Høydahl self.type = type 187287c131baSJan Høydahl self.prompt = prompt 187387c131baSJan Høydahl self.name = name 187487c131baSJan Høydahl 187587c131baSJan Høydahl @classmethod 187687c131baSJan Høydahl def from_yaml(cls, loader, node): 187787c131baSJan Høydahl fields = loader.construct_mapping(node, deep = True) 187887c131baSJan Høydahl return UserInput(**fields) 187987c131baSJan Høydahl 188087c131baSJan Høydahl def run(self, dict=None): 188187c131baSJan Høydahl correct = False 188287c131baSJan Høydahl while not correct: 188387c131baSJan Høydahl try: 188487c131baSJan Høydahl result = str(input("%s : " % self.prompt)) 188587c131baSJan Høydahl if self.type and self.type == 'int': 188687c131baSJan Høydahl result = int(result) 188787c131baSJan Høydahl correct = True 188887c131baSJan Høydahl except Exception as e: 188987c131baSJan Høydahl print("Incorrect input: %s, try again" % e) 189087c131baSJan Høydahl continue 189187c131baSJan Høydahl if dict: 189287c131baSJan Høydahl dict[self.name] = result 189387c131baSJan Høydahl return result 189487c131baSJan Høydahl 189587c131baSJan Høydahl 189687c131baSJan Høydahldef create_ical(todo): 189787c131baSJan Høydahl if ask_yes_no("Do you want to add a Calendar reminder for the close vote time?"): 189887c131baSJan Høydahl c = Calendar() 189987c131baSJan Høydahl e = Event() 1900674b66ddSJan Høydahl e.name = "Lucene %s vote ends" % state.release_version 190187c131baSJan Høydahl e.begin = vote_close_72h_date() 190287c131baSJan Høydahl e.description = "Remember to sum up votes and continue release :)" 190387c131baSJan Høydahl c.events.add(e) 190487c131baSJan Høydahl ics_file = os.path.join(state.get_rc_folder(), 'vote_end.ics') 190587c131baSJan Høydahl with open(ics_file, 'w') as my_file: 190687c131baSJan Høydahl my_file.writelines(c) 190787c131baSJan Høydahl open_file(ics_file) 190887c131baSJan Høydahl return True 190987c131baSJan Høydahl 191087c131baSJan Høydahl 191187c131baSJan Høydahltoday = datetime.utcnow().date() 1912d86b473aSJan Høydahlsundays = {(today + timedelta(days=x)): 'Sunday' for x in range(10) if (today + timedelta(days=x)).weekday() == 6} 191387c131baSJan Høydahly = datetime.utcnow().year 191487c131baSJan Høydahlyears = [y, y+1] 191587c131baSJan Høydahlnon_working = holidays.CA(years=years) + holidays.US(years=years) + holidays.England(years=years) \ 1916d86b473aSJan Høydahl + holidays.DE(years=years) + holidays.NO(years=years) + holidays.IND(years=years) + holidays.RU(years=years) 191787c131baSJan Høydahl 191887c131baSJan Høydahl 191987c131baSJan Høydahldef vote_close_72h_date(): 1920d86b473aSJan Høydahl # Voting open at least 72 hours according to ASF policy 1921d86b473aSJan Høydahl return datetime.utcnow() + timedelta(hours=73) 1922d86b473aSJan Høydahl 1923d86b473aSJan Høydahl 1924d86b473aSJan Høydahldef vote_close_72h_holidays(): 1925d86b473aSJan Høydahl days = 0 192687c131baSJan Høydahl day_offset = -1 1927d86b473aSJan Høydahl holidays = [] 1928d86b473aSJan Høydahl # Warn RM about major holidays coming up that should perhaps extend the voting deadline 1929d86b473aSJan Høydahl # Warning will be given for Sunday or a public holiday observed by 3 or more [CA, US, EN, DE, NO, IND, RU] 1930d86b473aSJan Høydahl while days < 3: 193187c131baSJan Høydahl day_offset += 1 193287c131baSJan Høydahl d = today + timedelta(days=day_offset) 1933d86b473aSJan Høydahl if not (d in sundays or (d in non_working and len(non_working[d]) >= 2)): 1934d86b473aSJan Høydahl days += 1 1935d86b473aSJan Høydahl else: 1936d86b473aSJan Høydahl if d in sundays: 1937d86b473aSJan Høydahl holidays.append("%s (Sunday)" % d) 1938d86b473aSJan Høydahl else: 1939d86b473aSJan Høydahl holidays.append("%s (%s)" % (d, non_working[d])) 1940d86b473aSJan Høydahl return holidays if len(holidays) > 0 else None 194187c131baSJan Høydahl 194287c131baSJan Høydahl 1943329e7c7bSJan Høydahldef prepare_announce_lucene(todo): 1944329e7c7bSJan Høydahl if not os.path.exists(lucene_news_file): 194587c131baSJan Høydahl lucene_text = expand_jinja("(( template=announce_lucene ))") 1946329e7c7bSJan Høydahl with open(lucene_news_file, 'w') as fp: 194787c131baSJan Høydahl fp.write(lucene_text) 194887c131baSJan Høydahl # print("Wrote Lucene announce draft to %s" % lucene_news_file) 1949329e7c7bSJan Høydahl else: 1950329e7c7bSJan Høydahl print("Draft already exist, not re-generating") 1951329e7c7bSJan Høydahl return True 195287c131baSJan Høydahl 195387c131baSJan Høydahl 1954f38c4012SJan Høydahldef check_artifacts_available(todo): 1955f38c4012SJan Høydahl try: 1956f38c4012SJan Høydahl cdnUrl = expand_jinja("https://dlcdn.apache.org/lucene/java/{{ release_version }}/lucene-{{ release_version }}-src.tgz.asc") 1957f38c4012SJan Høydahl load(cdnUrl) 1958f38c4012SJan Høydahl print("Found %s" % cdnUrl) 1959f38c4012SJan Høydahl except Exception as e: 1960f38c4012SJan Høydahl print("Could not fetch %s (%s)" % (cdnUrl, e)) 1961f38c4012SJan Høydahl return False 1962f38c4012SJan Høydahl 1963f38c4012SJan Høydahl try: 1964f38c4012SJan Høydahl mavenUrl = expand_jinja("https://repo1.maven.org/maven2/org/apache/lucene/lucene-core/{{ release_version }}/lucene-core-{{ release_version }}.pom.asc") 1965f38c4012SJan Høydahl load(mavenUrl) 1966f38c4012SJan Høydahl print("Found %s" % mavenUrl) 1967f38c4012SJan Høydahl except Exception as e: 1968f38c4012SJan Høydahl print("Could not fetch %s (%s)" % (mavenUrl, e)) 1969f38c4012SJan Høydahl return False 1970f38c4012SJan Høydahl 1971f38c4012SJan Høydahl return True 1972f38c4012SJan Høydahl 197387c131baSJan Høydahldef set_java_home(version): 197487c131baSJan Høydahl os.environ['JAVA_HOME'] = state.get_java_home_for_version(version) 197587c131baSJan Høydahl os.environ['JAVACMD'] = state.get_java_cmd_for_version(version) 197687c131baSJan Høydahl 197787c131baSJan Høydahl 1978329e7c7bSJan Høydahldef load_lines(file, from_line=0): 197987c131baSJan Høydahl if os.path.exists(file): 198087c131baSJan Høydahl with open(file, 'r') as fp: 1981329e7c7bSJan Høydahl return fp.readlines()[from_line:] 198287c131baSJan Høydahl else: 1983329e7c7bSJan Høydahl return ["<Please paste the announcement text here>\n"] 198487c131baSJan Høydahl 198587c131baSJan Høydahl 198687c131baSJan Høydahlif __name__ == '__main__': 198787c131baSJan Høydahl try: 198887c131baSJan Høydahl main() 198987c131baSJan Høydahl except KeyboardInterrupt: 199087c131baSJan Høydahl print('Keyboard interrupt...exiting') 1991