xref: /Lucene/dev-tools/scripts/addVersion.py (revision f9be01d5cc2aa713a706cc9502d386863f49675a)
187c131baSJan Høydahl#!/usr/bin/env python3
287c131baSJan Høydahl# -*- coding: utf-8 -*-
31db06937SRyan Ernst# Licensed to the Apache Software Foundation (ASF) under one or more
41db06937SRyan Ernst# contributor license agreements.  See the NOTICE file distributed with
51db06937SRyan Ernst# this work for additional information regarding copyright ownership.
61db06937SRyan Ernst# The ASF licenses this file to You under the Apache License, Version 2.0
71db06937SRyan Ernst# (the "License"); you may not use this file except in compliance with
81db06937SRyan Ernst# the License.  You may obtain a copy of the License at
91db06937SRyan Ernst#
101db06937SRyan Ernst#     http://www.apache.org/licenses/LICENSE-2.0
111db06937SRyan Ernst#
121db06937SRyan Ernst# Unless required by applicable law or agreed to in writing, software
131db06937SRyan Ernst# distributed under the License is distributed on an "AS IS" BASIS,
141db06937SRyan Ernst# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
151db06937SRyan Ernst# See the License for the specific language governing permissions and
161db06937SRyan Ernst# limitations under the License.
171db06937SRyan Ernst
181db06937SRyan Ernstimport os
191db06937SRyan Ernstimport sys
201db06937SRyan Ernstsys.path.append(os.path.dirname(__file__))
2103f49700SAnshum Guptafrom scriptutil import *
221db06937SRyan Ernst
231db06937SRyan Ernstimport argparse
241db06937SRyan Ernstimport re
25843adfb7SSteve Rowefrom configparser import ConfigParser, ExtendedInterpolation
26843adfb7SSteve Rowefrom textwrap import dedent
271db06937SRyan Ernst
28742e6b7eSDavid Smileydef update_changes(filename, new_version, init_changes, headers):
291db06937SRyan Ernst  print('  adding new section to %s...' % filename, end='', flush=True)
301db06937SRyan Ernst  matcher = re.compile(r'\d+\.\d+\.\d+\s+===')
311db06937SRyan Ernst  def edit(buffer, match, line):
321db06937SRyan Ernst    if new_version.dot in line:
331db06937SRyan Ernst      return None
341db06937SRyan Ernst    match = new_version.previous_dot_matcher.search(line)
351db06937SRyan Ernst    if match is not None:
361db06937SRyan Ernst      buffer.append(line.replace(match.group(0), new_version.dot))
37843adfb7SSteve Rowe      buffer.append(init_changes)
38742e6b7eSDavid Smiley      for header in headers:
39742e6b7eSDavid Smiley        buffer.append('%s\n---------------------\n(No changes)\n\n' % header)
401db06937SRyan Ernst    buffer.append(line)
411db06937SRyan Ernst    return match is not None
421db06937SRyan Ernst
4303f49700SAnshum Gupta  changed = update_file(filename, matcher, edit)
441db06937SRyan Ernst  print('done' if changed else 'uptodate')
451db06937SRyan Ernst
461db06937SRyan Ernstdef add_constant(new_version, deprecate):
471db06937SRyan Ernst  filename = 'lucene/core/src/java/org/apache/lucene/util/Version.java'
481db06937SRyan Ernst  print('  adding constant %s...' % new_version.constant, end='', flush=True)
491db06937SRyan Ernst  constant_prefix = 'public static final Version LUCENE_'
501db06937SRyan Ernst  matcher = re.compile(constant_prefix)
511db06937SRyan Ernst  prev_matcher = new_version.make_previous_matcher(prefix=constant_prefix, sep='_')
521db06937SRyan Ernst
531db06937SRyan Ernst  def ensure_deprecated(buffer):
541db06937SRyan Ernst    last = buffer[-1]
551db06937SRyan Ernst    if last.strip() != '@Deprecated':
561db06937SRyan Ernst      spaces = ' ' * (len(last) - len(last.lstrip()) - 1)
5704a133f9SSteve Rowe      del buffer[-1] # Remove comment closer line
5804a133f9SSteve Rowe      if (len(buffer) >= 4 and re.search('for Lucene.\s*$', buffer[-1]) != None):
5904a133f9SSteve Rowe        del buffer[-3:] # drop the trailing lines '<p> / Use this to get the latest ... / ... for Lucene.'
6004a133f9SSteve Rowe      buffer.append(( '{0} * @deprecated ({1}) Use latest\n'
6104a133f9SSteve Rowe                    + '{0} */\n'
6204a133f9SSteve Rowe                    + '{0}@Deprecated\n').format(spaces, new_version))
631db06937SRyan Ernst
641db06937SRyan Ernst  def buffer_constant(buffer, line):
651db06937SRyan Ernst    spaces = ' ' * (len(line) - len(line.lstrip()))
6604a133f9SSteve Rowe    buffer.append(( '\n{0}/**\n'
6704a133f9SSteve Rowe                  + '{0} * Match settings and bugs in Lucene\'s {1} release.\n')
6804a133f9SSteve Rowe                  .format(spaces, new_version))
691db06937SRyan Ernst    if deprecate:
7004a133f9SSteve Rowe      buffer.append('%s * @deprecated Use latest\n' % spaces)
7104a133f9SSteve Rowe    else:
72*f9be01d5SAdrien Grand      buffer.append(( '{0} * <p>Use this to get the latest &amp; greatest settings, bug fixes, etc, for Lucene.\n').format(spaces))
7304a133f9SSteve Rowe    buffer.append('%s */\n' % spaces)
741db06937SRyan Ernst    if deprecate:
7504a133f9SSteve Rowe      buffer.append('%s@Deprecated\n' % spaces)
7604a133f9SSteve Rowe    buffer.append('{0}public static final Version {1} = new Version({2}, {3}, {4});\n'.format
7704a133f9SSteve Rowe                  (spaces, new_version.constant, new_version.major, new_version.minor, new_version.bugfix))
781db06937SRyan Ernst
791db06937SRyan Ernst  class Edit(object):
801db06937SRyan Ernst    found = -1
811db06937SRyan Ernst    def __call__(self, buffer, match, line):
821db06937SRyan Ernst      if new_version.constant in line:
831db06937SRyan Ernst        return None # constant already exists
8404a133f9SSteve Rowe      # outer match is just to find lines declaring version constants
851db06937SRyan Ernst      match = prev_matcher.search(line)
861db06937SRyan Ernst      if match is not None:
871db06937SRyan Ernst        ensure_deprecated(buffer) # old version should be deprecated
881db06937SRyan Ernst        self.found = len(buffer) + 1 # extra 1 for buffering current line below
891db06937SRyan Ernst      elif self.found != -1:
901db06937SRyan Ernst        # we didn't match, but we previously had a match, so insert new version here
911db06937SRyan Ernst        # first find where to insert (first empty line before current constant)
921db06937SRyan Ernst        c = []
931db06937SRyan Ernst        buffer_constant(c, line)
941db06937SRyan Ernst        tmp = buffer[self.found:]
951db06937SRyan Ernst        buffer[self.found:] = c
961db06937SRyan Ernst        buffer.extend(tmp)
971db06937SRyan Ernst        buffer.append(line)
981db06937SRyan Ernst        return True
991db06937SRyan Ernst
1001db06937SRyan Ernst      buffer.append(line)
1011db06937SRyan Ernst      return False
1021db06937SRyan Ernst
10303f49700SAnshum Gupta  changed = update_file(filename, matcher, Edit())
1041db06937SRyan Ernst  print('done' if changed else 'uptodate')
1051db06937SRyan Ernst
1061db06937SRyan Ernstdef update_build_version(new_version):
10708e38d34SMike Drob  print('  changing baseVersion...', end='', flush=True)
10808e38d34SMike Drob  filename = 'build.gradle'
1091db06937SRyan Ernst  def edit(buffer, match, line):
1101db06937SRyan Ernst    if new_version.dot in line:
1111db06937SRyan Ernst      return None
112674b66ddSJan Høydahl    buffer.append('  String baseVersion = \'' + new_version.dot + '\'\n')
1131db06937SRyan Ernst    return True
1141db06937SRyan Ernst
115674b66ddSJan Høydahl  version_prop_re = re.compile(r'baseVersion\s*=\s*([\'"])(.*)\1')
116674b66ddSJan Høydahl  changed = update_file(filename, version_prop_re, edit)
1171db06937SRyan Ernst  print('done' if changed else 'uptodate')
1181db06937SRyan Ernst
1191db06937SRyan Ernstdef update_latest_constant(new_version):
1201db06937SRyan Ernst  print('  changing Version.LATEST to %s...' % new_version.constant, end='', flush=True)
1211db06937SRyan Ernst  filename = 'lucene/core/src/java/org/apache/lucene/util/Version.java'
1221db06937SRyan Ernst  matcher = re.compile('public static final Version LATEST')
1231db06937SRyan Ernst  def edit(buffer, match, line):
1241db06937SRyan Ernst    if new_version.constant in line:
1251db06937SRyan Ernst      return None
1261db06937SRyan Ernst    buffer.append(line.rpartition('=')[0] + ('= %s;\n' % new_version.constant))
1271db06937SRyan Ernst    return True
1281db06937SRyan Ernst
12903f49700SAnshum Gupta  changed = update_file(filename, matcher, edit)
1301db06937SRyan Ernst  print('done' if changed else 'uptodate')
1311db06937SRyan Ernst
1321e83e339STimothy Potterdef onerror(x):
1331e83e339STimothy Potter  raise x
1341e83e339STimothy Potter
1351db06937SRyan Ernstdef check_lucene_version_tests():
1361db06937SRyan Ernst  print('  checking lucene version tests...', end='', flush=True)
137178d83d6SJason Gerlowski  run('./gradlew -p lucene/core test --tests TestVersion')
1381db06937SRyan Ernst  print('ok')
1391db06937SRyan Ernst
14046c827e3SSteve Rowedef read_config(current_version):
141674b66ddSJan Høydahl  parser = argparse.ArgumentParser(description='Add a new version to CHANGES, to Version.java and build.gradle files')
1421db06937SRyan Ernst  parser.add_argument('version', type=Version.parse)
143cd8592c8SSteve Rowe  newconf = parser.parse_args()
1441db06937SRyan Ernst
145cd8592c8SSteve Rowe  newconf.branch_type = find_branch_type()
146cd8592c8SSteve Rowe  newconf.is_latest_version = newconf.version.on_or_after(current_version)
1471db06937SRyan Ernst
148cd8592c8SSteve Rowe  print ("branch_type is %s " % newconf.branch_type)
1491db06937SRyan Ernst
150cd8592c8SSteve Rowe  return newconf
1511db06937SRyan Ernst
152843adfb7SSteve Rowe# Hack ConfigParser, designed to parse INI files, to parse & interpolate Java .properties files
153843adfb7SSteve Rowedef parse_properties_file(filename):
154843adfb7SSteve Rowe  contents = open(filename, encoding='ISO-8859-1').read().replace('%', '%%') # Escape interpolation metachar
155843adfb7SSteve Rowe  parser = ConfigParser(interpolation=ExtendedInterpolation())               # Handle ${property-name} interpolation
156843adfb7SSteve Rowe  parser.read_string("[DUMMY_SECTION]\n" + contents)                         # Add required section
157843adfb7SSteve Rowe  return dict(parser.items('DUMMY_SECTION'))
158843adfb7SSteve Rowe
159843adfb7SSteve Rowe
1601db06937SRyan Ernstdef main():
16108e38d34SMike Drob  if not os.path.exists('build.gradle'):
16287c131baSJan Høydahl    sys.exit("Tool must be run from the root of a source checkout.")
16346c827e3SSteve Rowe  current_version = Version.parse(find_current_version())
164cd8592c8SSteve Rowe  newconf = read_config(current_version)
165742e6b7eSDavid Smiley  is_bugfix = newconf.version.is_bugfix_release()
1661db06937SRyan Ernst
167cd8592c8SSteve Rowe  print('\nAdding new version %s' % newconf.version)
168742e6b7eSDavid Smiley  # See LUCENE-8883 for some thoughts on which categories to use
169742e6b7eSDavid Smiley  update_changes('lucene/CHANGES.txt', newconf.version, '\n',
170742e6b7eSDavid Smiley                 ['Bug Fixes'] if is_bugfix else ['API Changes', 'New Features', 'Improvements', 'Optimizations', 'Bug Fixes', 'Other'])
17146c827e3SSteve Rowe
172cd8592c8SSteve Rowe  latest_or_backcompat = newconf.is_latest_version or current_version.is_back_compat_with(newconf.version)
173cd8592c8SSteve Rowe  if latest_or_backcompat:
174cd8592c8SSteve Rowe    add_constant(newconf.version, not newconf.is_latest_version)
17546c827e3SSteve Rowe  else:
176cd8592c8SSteve Rowe    print('\nNot adding constant for version %s because it is no longer supported' % newconf.version)
1771db06937SRyan Ernst
178cd8592c8SSteve Rowe  if newconf.is_latest_version:
1791db06937SRyan Ernst    print('\nUpdating latest version')
180cd8592c8SSteve Rowe    update_build_version(newconf.version)
181cd8592c8SSteve Rowe    update_latest_constant(newconf.version)
1821db06937SRyan Ernst
183cd8592c8SSteve Rowe  if newconf.version.is_major_release():
1841db06937SRyan Ernst    print('\nTODO: ')
1851db06937SRyan Ernst    print('  - Move backcompat oldIndexes to unsupportedIndexes in TestBackwardsCompatibility')
1861db06937SRyan Ernst    print('  - Update IndexFormatTooOldException throw cases')
187cd8592c8SSteve Rowe  elif latest_or_backcompat:
1881db06937SRyan Ernst    print('\nTesting changes')
1891db06937SRyan Ernst    check_lucene_version_tests()
1901db06937SRyan Ernst
1911db06937SRyan Ernst  print()
1921db06937SRyan Ernst
1931db06937SRyan Ernstif __name__ == '__main__':
1941db06937SRyan Ernst  try:
1951db06937SRyan Ernst    main()
1961db06937SRyan Ernst  except KeyboardInterrupt:
1971db06937SRyan Ernst    print('\nReceived Ctrl-C, exiting early')
198