xref: /Lucene/dev-tools/scripts/addVersion.py (revision f9be01d5cc2aa713a706cc9502d386863f49675a)
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 &amp; 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