1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Licensed to the Apache Software Foundation (ASF) under one or more 4# contributor license agreements. See the NOTICE file distributed with 5# this work for additional information regarding copyright ownership. 6# The ASF licenses this file to You under the Apache License, Version 2.0 7# (the "License"); you may not use this file except in compliance with 8# the License. You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17 18import os 19import sys 20sys.path.append(os.path.dirname(__file__)) 21from scriptutil import * 22 23import argparse 24import re 25from configparser import ConfigParser, ExtendedInterpolation 26from textwrap import dedent 27 28def update_changes(filename, new_version, init_changes, headers): 29 print(' adding new section to %s...' % filename, end='', flush=True) 30 matcher = re.compile(r'\d+\.\d+\.\d+\s+===') 31 def edit(buffer, match, line): 32 if new_version.dot in line: 33 return None 34 match = new_version.previous_dot_matcher.search(line) 35 if match is not None: 36 buffer.append(line.replace(match.group(0), new_version.dot)) 37 buffer.append(init_changes) 38 for header in headers: 39 buffer.append('%s\n---------------------\n(No changes)\n\n' % header) 40 buffer.append(line) 41 return match is not None 42 43 changed = update_file(filename, matcher, edit) 44 print('done' if changed else 'uptodate') 45 46def add_constant(new_version, deprecate): 47 filename = 'lucene/core/src/java/org/apache/lucene/util/Version.java' 48 print(' adding constant %s...' % new_version.constant, end='', flush=True) 49 constant_prefix = 'public static final Version LUCENE_' 50 matcher = re.compile(constant_prefix) 51 prev_matcher = new_version.make_previous_matcher(prefix=constant_prefix, sep='_') 52 53 def ensure_deprecated(buffer): 54 last = buffer[-1] 55 if last.strip() != '@Deprecated': 56 spaces = ' ' * (len(last) - len(last.lstrip()) - 1) 57 del buffer[-1] # Remove comment closer line 58 if (len(buffer) >= 4 and re.search('for Lucene.\s*$', buffer[-1]) != None): 59 del buffer[-3:] # drop the trailing lines '<p> / Use this to get the latest ... / ... for Lucene.' 60 buffer.append(( '{0} * @deprecated ({1}) Use latest\n' 61 + '{0} */\n' 62 + '{0}@Deprecated\n').format(spaces, new_version)) 63 64 def buffer_constant(buffer, line): 65 spaces = ' ' * (len(line) - len(line.lstrip())) 66 buffer.append(( '\n{0}/**\n' 67 + '{0} * Match settings and bugs in Lucene\'s {1} release.\n') 68 .format(spaces, new_version)) 69 if deprecate: 70 buffer.append('%s * @deprecated Use latest\n' % spaces) 71 else: 72 buffer.append(( '{0} * <p>Use this to get the latest & greatest settings, bug fixes, etc, for Lucene.\n').format(spaces)) 73 buffer.append('%s */\n' % spaces) 74 if deprecate: 75 buffer.append('%s@Deprecated\n' % spaces) 76 buffer.append('{0}public static final Version {1} = new Version({2}, {3}, {4});\n'.format 77 (spaces, new_version.constant, new_version.major, new_version.minor, new_version.bugfix)) 78 79 class Edit(object): 80 found = -1 81 def __call__(self, buffer, match, line): 82 if new_version.constant in line: 83 return None # constant already exists 84 # outer match is just to find lines declaring version constants 85 match = prev_matcher.search(line) 86 if match is not None: 87 ensure_deprecated(buffer) # old version should be deprecated 88 self.found = len(buffer) + 1 # extra 1 for buffering current line below 89 elif self.found != -1: 90 # we didn't match, but we previously had a match, so insert new version here 91 # first find where to insert (first empty line before current constant) 92 c = [] 93 buffer_constant(c, line) 94 tmp = buffer[self.found:] 95 buffer[self.found:] = c 96 buffer.extend(tmp) 97 buffer.append(line) 98 return True 99 100 buffer.append(line) 101 return False 102 103 changed = update_file(filename, matcher, Edit()) 104 print('done' if changed else 'uptodate') 105 106def update_build_version(new_version): 107 print(' changing baseVersion...', end='', flush=True) 108 filename = 'build.gradle' 109 def edit(buffer, match, line): 110 if new_version.dot in line: 111 return None 112 buffer.append(' String baseVersion = \'' + new_version.dot + '\'\n') 113 return True 114 115 version_prop_re = re.compile(r'baseVersion\s*=\s*([\'"])(.*)\1') 116 changed = update_file(filename, version_prop_re, edit) 117 print('done' if changed else 'uptodate') 118 119def update_latest_constant(new_version): 120 print(' changing Version.LATEST to %s...' % new_version.constant, end='', flush=True) 121 filename = 'lucene/core/src/java/org/apache/lucene/util/Version.java' 122 matcher = re.compile('public static final Version LATEST') 123 def edit(buffer, match, line): 124 if new_version.constant in line: 125 return None 126 buffer.append(line.rpartition('=')[0] + ('= %s;\n' % new_version.constant)) 127 return True 128 129 changed = update_file(filename, matcher, edit) 130 print('done' if changed else 'uptodate') 131 132def onerror(x): 133 raise x 134 135def check_lucene_version_tests(): 136 print(' checking lucene version tests...', end='', flush=True) 137 run('./gradlew -p lucene/core test --tests TestVersion') 138 print('ok') 139 140def read_config(current_version): 141 parser = argparse.ArgumentParser(description='Add a new version to CHANGES, to Version.java and build.gradle files') 142 parser.add_argument('version', type=Version.parse) 143 newconf = parser.parse_args() 144 145 newconf.branch_type = find_branch_type() 146 newconf.is_latest_version = newconf.version.on_or_after(current_version) 147 148 print ("branch_type is %s " % newconf.branch_type) 149 150 return newconf 151 152# Hack ConfigParser, designed to parse INI files, to parse & interpolate Java .properties files 153def parse_properties_file(filename): 154 contents = open(filename, encoding='ISO-8859-1').read().replace('%', '%%') # Escape interpolation metachar 155 parser = ConfigParser(interpolation=ExtendedInterpolation()) # Handle ${property-name} interpolation 156 parser.read_string("[DUMMY_SECTION]\n" + contents) # Add required section 157 return dict(parser.items('DUMMY_SECTION')) 158 159 160def main(): 161 if not os.path.exists('build.gradle'): 162 sys.exit("Tool must be run from the root of a source checkout.") 163 current_version = Version.parse(find_current_version()) 164 newconf = read_config(current_version) 165 is_bugfix = newconf.version.is_bugfix_release() 166 167 print('\nAdding new version %s' % newconf.version) 168 # See LUCENE-8883 for some thoughts on which categories to use 169 update_changes('lucene/CHANGES.txt', newconf.version, '\n', 170 ['Bug Fixes'] if is_bugfix else ['API Changes', 'New Features', 'Improvements', 'Optimizations', 'Bug Fixes', 'Other']) 171 172 latest_or_backcompat = newconf.is_latest_version or current_version.is_back_compat_with(newconf.version) 173 if latest_or_backcompat: 174 add_constant(newconf.version, not newconf.is_latest_version) 175 else: 176 print('\nNot adding constant for version %s because it is no longer supported' % newconf.version) 177 178 if newconf.is_latest_version: 179 print('\nUpdating latest version') 180 update_build_version(newconf.version) 181 update_latest_constant(newconf.version) 182 183 if newconf.version.is_major_release(): 184 print('\nTODO: ') 185 print(' - Move backcompat oldIndexes to unsupportedIndexes in TestBackwardsCompatibility') 186 print(' - Update IndexFormatTooOldException throw cases') 187 elif latest_or_backcompat: 188 print('\nTesting changes') 189 check_lucene_version_tests() 190 191 print() 192 193if __name__ == '__main__': 194 try: 195 main() 196 except KeyboardInterrupt: 197 print('\nReceived Ctrl-C, exiting early') 198