xref: /Lucene/dev-tools/scripts/scriptutil.py (revision 57524c6a5e724088d97cb8ae0e81bd43625083f0)
1# Licensed to the Apache Software Foundation (ASF) under one or more
2# contributor license agreements.  See the NOTICE file distributed with
3# this work for additional information regarding copyright ownership.
4# The ASF licenses this file to You under the Apache License, Version 2.0
5# (the "License"); you may not use this file except in compliance with
6# the License.  You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import argparse
17import re
18import subprocess
19import sys
20import os
21from enum import Enum
22import time
23import urllib.request, urllib.error, urllib.parse
24import urllib.parse
25
26class Version(object):
27  def __init__(self, major, minor, bugfix, prerelease):
28    self.major = major
29    self.minor = minor
30    self.bugfix = bugfix
31    self.prerelease = prerelease
32    self.previous_dot_matcher = self.make_previous_matcher()
33    self.dot = '%d.%d.%d' % (self.major, self.minor, self.bugfix)
34    self.constant = 'LUCENE_%d_%d_%d' % (self.major, self.minor, self.bugfix)
35
36  @classmethod
37  def parse(cls, value):
38    match = re.search(r'(\d+)\.(\d+).(\d+)(.1|.2)?', value)
39    if match is None:
40      raise argparse.ArgumentTypeError('Version argument must be of format x.y.z(.1|.2)?')
41    parts = [int(v) for v in match.groups()[:-1]]
42    parts.append({ None: 0, '.1': 1, '.2': 2 }[match.groups()[-1]])
43    return Version(*parts)
44
45  def __str__(self):
46    return self.dot
47
48  def make_previous_matcher(self, prefix='', suffix='', sep='\\.'):
49    if self.is_bugfix_release():
50      pattern = '%s%s%s%s%d' % (self.major, sep, self.minor, sep, self.bugfix - 1)
51    elif self.is_minor_release():
52      pattern = '%s%s%d%s\\d+' % (self.major, sep, self.minor - 1, sep)
53    else:
54      pattern = '%d%s\\d+%s\\d+' % (self.major - 1, sep, sep)
55
56    return re.compile(prefix + '(' + pattern + ')' + suffix)
57
58  def is_bugfix_release(self):
59    return self.bugfix != 0
60
61  def is_minor_release(self):
62    return self.bugfix == 0 and self.minor != 0
63
64  def is_major_release(self):
65    return self.bugfix == 0 and self.minor == 0
66
67  def on_or_after(self, other):
68    return (self.major > other.major or self.major == other.major and
69           (self.minor > other.minor or self.minor == other.minor and
70           (self.bugfix > other.bugfix or self.bugfix == other.bugfix and
71           self.prerelease >= other.prerelease)))
72
73  def gt(self, other):
74    return (self.major > other.major or
75           (self.major == other.major and self.minor > other.minor) or
76           (self.major == other.major and self.minor == other.minor and self.bugfix > other.bugfix))
77
78  def is_back_compat_with(self, other):
79    if not self.on_or_after(other):
80      raise Exception('Back compat check disallowed for newer version: %s < %s' % (self, other))
81    return other.major + 1 >= self.major
82
83def run(cmd, cwd=None):
84  try:
85    output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, cwd=cwd)
86  except subprocess.CalledProcessError as e:
87    print(e.output.decode('utf-8'))
88    raise e
89  return output.decode('utf-8')
90
91def update_file(filename, line_re, edit):
92  infile = open(filename, 'r')
93  buffer = []
94
95  changed = False
96  for line in infile:
97    if not changed:
98      match = line_re.search(line)
99      if match:
100        changed = edit(buffer, match, line)
101        if changed is None:
102          return False
103        continue
104    buffer.append(line)
105  if not changed:
106    raise Exception('Could not find %s in %s' % (line_re, filename))
107  with open(filename, 'w') as f:
108    f.write(''.join(buffer))
109  return True
110
111
112# branch types are "release", "stable" and "unstable"
113class BranchType(Enum):
114  unstable = 1
115  stable   = 2
116  release  = 3
117
118def find_branch_type():
119  output = subprocess.check_output('git status', shell=True)
120  for line in output.split(b'\n'):
121    if line.startswith(b'On branch '):
122      branchName = line.split(b' ')[-1]
123      break
124  else:
125    raise Exception('git status missing branch name')
126
127  if branchName == b'main':
128    return BranchType.unstable
129  if re.match(r'branch_(\d+)x', branchName.decode('UTF-8')):
130    return BranchType.stable
131  if re.match(r'branch_(\d+)_(\d+)', branchName.decode('UTF-8')):
132    return BranchType.release
133  raise Exception('Cannot run %s on feature branch' % sys.argv[0].rsplit('/', 1)[-1])
134
135
136def download(name, urlString, tmpDir, quiet=False, force_clean=True):
137  if not quiet:
138      print("Downloading %s" % urlString)
139  startTime = time.time()
140  fileName = '%s/%s' % (tmpDir, name)
141  if not force_clean and os.path.exists(fileName):
142    if not quiet and fileName.find('.asc') == -1:
143      print('    already done: %.1f MB' % (os.path.getsize(fileName)/1024./1024.))
144    return
145  try:
146    attemptDownload(urlString, fileName)
147  except Exception as e:
148    print('Retrying download of url %s after exception: %s' % (urlString, e))
149    try:
150      attemptDownload(urlString, fileName)
151    except Exception as e:
152      raise RuntimeError('failed to download url "%s"' % urlString) from e
153  if not quiet and fileName.find('.asc') == -1:
154    t = time.time()-startTime
155    sizeMB = os.path.getsize(fileName)/1024./1024.
156    print('    %.1f MB in %.2f sec (%.1f MB/sec)' % (sizeMB, t, sizeMB/t))
157
158
159def attemptDownload(urlString, fileName):
160  fIn = urllib.request.urlopen(urlString)
161  fOut = open(fileName, 'wb')
162  success = False
163  try:
164    while True:
165      s = fIn.read(65536)
166      if s == b'':
167        break
168      fOut.write(s)
169    fOut.close()
170    fIn.close()
171    success = True
172  finally:
173    fIn.close()
174    fOut.close()
175    if not success:
176      os.remove(fileName)
177
178version_prop_re = re.compile(r'baseVersion\s*=\s*([\'"])(.*)\1')
179def find_current_version():
180  script_path = os.path.dirname(os.path.realpath(__file__))
181  top_level_dir = os.path.join(os.path.abspath("%s/" % script_path), os.path.pardir, os.path.pardir)
182  return version_prop_re.search(open('%s/build.gradle' % top_level_dir).read()).group(2).strip()
183
184if __name__ == '__main__':
185  print('This is only a support module, it cannot be run')
186  sys.exit(1)
187