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