PageRenderTime 64ms CodeModel.GetById 39ms app.highlight 22ms RepoModel.GetById 0ms app.codeStats 0ms

/bin/utils.py

https://github.com/oranheim/infinispan
Python | 414 lines | 408 code | 5 blank | 1 comment | 9 complexity | ba96f377e13ad6a24b80d2625f23a29e MD5 | raw file
  1import os
  2import fnmatch
  3import re
  4import subprocess
  5import sys
  6import readline
  7import shutil
  8import random
  9settings_file = '%s/.infinispan_dev_settings' % os.getenv('HOME')
 10
 11### Known config keys
 12svn_base_key = "svn_base"
 13local_tags_dir_key = "local_tags_dir"
 14local_mvn_repo_dir_key = "local_mvn_repo_dir"
 15maven_pom_xml_namespace = "http://maven.apache.org/POM/4.0.0"
 16default_settings = {'dry_run': False, 'multi_threaded': False, 'verbose': False, 'use_colors': True}
 17boolean_keys = ['dry_run', 'multi_threaded', 'verbose']
 18
 19class Colors(object):
 20  MAGENTA = '\033[95m'
 21  GREEN = '\033[92m'
 22  YELLOW = '\033[93m'
 23  RED = '\033[91m'
 24  CYAN = '\033[96m'  
 25  END = '\033[0m'  
 26  UNDERLINE = '\033[4m'  
 27  
 28  @staticmethod  
 29  def magenta():
 30    if use_colors():
 31      return Colors.MAGENTA
 32    else:
 33      return ""
 34  
 35  @staticmethod  
 36  def green():
 37    if use_colors():
 38      return Colors.GREEN
 39    else:
 40      return ""
 41  
 42  @staticmethod  
 43  def yellow():
 44    if use_colors():
 45      return Colors.YELLOW
 46    else:
 47      return ""
 48  
 49  @staticmethod  
 50  def red():
 51    if use_colors():
 52      return Colors.RED
 53    else:
 54      return ""
 55  
 56  @staticmethod    
 57  def cyan():
 58    if use_colors():
 59      return Colors.CYAN
 60    else:
 61      return ""
 62  
 63  @staticmethod  
 64  def end_color():
 65    if use_colors():
 66      return Colors.END
 67    else:
 68      return ""
 69
 70class Levels(Colors):
 71  C_DEBUG = Colors.CYAN
 72  C_INFO = Colors.GREEN
 73  C_WARNING = Colors.YELLOW
 74  C_FATAL = Colors.RED
 75  C_ENDC = Colors.END 
 76  
 77  DEBUG = "DEBUG"
 78  INFO = "INFO"
 79  WARNING = "WARNING"
 80  FATAL = "FATAL"
 81  
 82  @staticmethod
 83  def get_color(level):
 84    if use_colors():
 85      return getattr(Levels, "C_" + level)
 86    else:
 87      return ""
 88
 89def use_colors():
 90  return ('use_colors' in settings and settings['use_colors']) or ('use_colors' not in settings)
 91
 92def prettyprint(message, level):  
 93  start_color = Levels.get_color(level)
 94  end_color = Levels.end_color()
 95    
 96  print "[%s%s%s] %s" % (start_color, level, end_color, message)
 97
 98def apply_defaults(s):
 99  for e in default_settings.items():
100    if e[0] not in s:
101      s[e[0]] = e[1]
102  return s
103
104def to_bool(x):
105  if type(x) == bool:
106    return x
107  if type(x) == str:
108    return {'true': True, 'false': False}.get(x.strip().lower())  
109
110def get_settings():
111  """Retrieves user-specific settings for all Infinispan tools.  Returns a dict of key/value pairs, or an empty dict if the settings file doesn't exist."""
112  f = None
113  try:
114    settings = {}
115    f = open(settings_file)
116    for l in f:
117      if not l.strip().startswith("#"):
118        kvp = l.split("=")
119        if kvp and len(kvp) > 0 and kvp[0] and len(kvp) > 1:
120          settings[kvp[0].strip()] = kvp[1].strip()
121    settings = apply_defaults(settings)
122    for k in boolean_keys:
123      settings[k] = to_bool(settings[k])
124    return settings
125  except IOError as ioe:
126    return {}
127  finally:
128    if f:
129      f.close()
130
131settings = get_settings()
132
133def input_with_default(msg, default):
134  i = raw_input("%s %s[%s]%s: " % (msg, Colors.magenta(), default, Colors.end_color()))
135  if i.strip() == "":
136    i = default
137  return i
138
139def handle_release_virgin():
140  """This sounds dirty!"""
141  prettyprint("""
142    It appears that this is the first time you are using this script.  I need to ask you a few questions before
143    we can proceed.  Default values are in brackets, just hitting ENTER will accept the default value.
144    
145    Lets get started!
146    """, Levels.WARNING)
147  s = {}  
148  s["verbose"] = input_with_default("Be verbose?", False)
149  s["multi_threaded"] = input_with_default("Run multi-threaded?  (Disable to debug)", True)
150  s["use_colors"] = input_with_default("Use colors?", True)
151  s = apply_defaults(s)  
152  f = open(settings_file, "w")
153  try:
154    for e in s.keys():
155      f.write("  %s = %s \n" % (e, s[e]))
156  finally:
157    f.close()
158    
159def require_settings_file(recursive = False):
160  """Tests whether the settings file exists, and if not prompts the user to create one."""
161  f = None
162  try:
163    f = open(settings_file)
164  except IOError as ioe:
165    if not recursive:
166      handle_release_virgin()
167      require_settings_file(True)
168      prettyprint("User-specific environment settings file %s created!  Please start this script again!" % settings_file, Levels.INFO)
169      sys.exit(4)
170    else:
171      prettyprint("User-specific environment settings file %s is missing!  Cannot proceed!" % settings_file, Levels.FATAL)
172      prettyprint("Please create a file called %s with the following lines:" % settings_file, Levels.FATAL)
173      prettyprint( '''
174       verbose = False
175       use_colors = True
176       multi_threaded = True
177      ''', Levels.INFO)
178      sys.exit(3)
179  finally:
180    if f:
181      f.close()
182
183def get_search_path(executable):
184  """Retrieves a search path based on where the current executable is located.  Returns a string to be prepended to add"""
185  in_bin_dir = re.compile('^.*/?bin/.*.py')
186  if in_bin_dir.search(executable):
187    return "./"
188  else:
189    return "../"
190
191def strip_leading_dots(filename):
192  return filename.strip('/. ')
193
194def to_set(list):
195  """Crappy implementation of creating a Set from a List.  To cope with older Python versions"""
196  temp_dict = {}
197  for entry in list:
198    temp_dict[entry] = "dummy"
199  return temp_dict.keys()
200
201class GlobDirectoryWalker:
202  """A forward iterator that traverses a directory tree"""
203  def __init__(self, directory, pattern="*"):
204    self.stack = [directory]
205    self.pattern = pattern
206    self.files = []
207    self.index = 0
208    
209  def __getitem__(self, index):
210    while True:
211      try:
212        file = self.files[self.index]
213        self.index = self.index + 1
214      except IndexError:
215        # pop next directory from stack
216        self.directory = self.stack.pop()
217        self.files = os.listdir(self.directory)
218        self.index = 0
219      else:
220        # got a filename
221        fullname = os.path.join(self.directory, file)
222        if os.path.isdir(fullname) and not os.path.islink(fullname):
223          self.stack.append(fullname)
224        if fnmatch.fnmatch(file, self.pattern):
225          return fullname
226
227
228class Git(object):  
229  '''Encapsulates git functionality necessary for releasing Infinispan'''
230  cmd = 'git'
231  # Helper functions to clean up branch lists 
232  @staticmethod
233  def clean(e): return e.strip().replace(' ', '').replace('*', '')
234  @staticmethod
235  def non_empty(e): return e != None and e.strip() != ''
236  @staticmethod
237  def current(e): return e != None and e.strip().replace(' ', '').startswith('*') 
238 
239  def __init__(self, branch, tag_name):        
240    if not self.is_git_directory():
241      raise Exception('Attempting to run git outside of a repository. Current directory is %s' % os.path.abspath(os.path.curdir))    
242      
243    self.branch = branch
244    self.tag = tag_name
245    self.verbose = False 
246    if settings['verbose']:
247      self.verbose = True
248    rand = '%x'.upper() % (random.random() * 100000)
249    self.working_branch = '__temp_%s' % rand
250    self.original_branch = self.current_branch() 
251  
252  def run_git(self, opts):
253    call = [self.cmd]
254    if type(opts) == list:
255      for o in opts:
256        call.append(o)
257    elif type(opts) == str:
258      for o in opts.split(' '):
259        if o != '':
260          call.append(o)
261    else:
262      raise Error("Cannot handle argument of type %s" % type(opts))
263    if settings['verbose']:
264      prettyprint( 'Executing %s' % call, Levels.DEBUG )
265    return subprocess.Popen(call, stdout=subprocess.PIPE).communicate()[0].split('\n')
266  
267  def is_git_directory(self):
268    return self.run_git('branch')[0] != ''
269 
270  def is_upstream_clone(self):
271    r = self.run_git('remote show -n origin')
272    cleaned = map(self.clean, r)
273    def push(e): return e.startswith('PushURL:')
274    def remove_noise(e): return e.replace('PushURL:', '')
275    push_urls = map(remove_noise, filter(push, cleaned))
276    return len(push_urls) == 1 and push_urls[0] == 'git@github.com:infinispan/infinispan.git'
277 
278  def clean_branches(self, raw_branch_list):
279    return map(self.clean, filter(self.non_empty, raw_branch_list))
280  
281  def remote_branch_exists(self):
282    '''Tests whether the branch exists on the remote origin'''
283    branches = self.clean_branches(self.run_git("branch -r"))
284    def replace_origin(b): return b.replace('origin/', '')
285    
286    return self.branch in map(replace_origin, branches)
287  
288  def switch_to_branch(self):
289    '''Switches the local repository to the specified branch.  Creates it if it doesn't already exist.'''
290    local_branches = self.clean_branches(self.run_git("branch"))
291    if self.branch not in local_branches:      
292      self.run_git("branch %s origin/%s" % (self.branch, self.branch))
293    self.run_git("checkout %s" % self.branch)      
294  
295  def create_tag_branch(self):
296    '''Creates and switches to a temp tagging branch, based off the release branch.'''
297    self.run_git("checkout -b %s %s" % (self.working_branch, self.branch))
298  
299  def commit(self, files):
300    '''Commits the set of files to the current branch with a generated commit message.'''    
301    for f in files:
302      self.run_git("add %s" % f)
303    
304    self.run_git(["commit", "-m", "'Release Script: update versions for %s'" % self.tag])
305  
306  def tag_for_release(self):
307    '''Tags the current branch for release using the tag name.'''
308    self.run_git(["tag", "-a", "-m", "'Release Script: tag %s'" % self.tag, self.tag])
309  
310  def push_to_origin(self):
311    '''Pushes the updated tags to origin'''
312    self.run_git("push origin --tags")
313 
314  def current_branch(self):
315    '''Returns the current branch you are on'''
316    return map(self.clean, filter(self.current, self.run_git('branch')))[0]
317
318  def cleanup(self): 
319    '''Cleans up any temporary branches created'''
320    self.run_git("checkout %s" % self.original_branch)
321    self.run_git("branch -D %s" % self.working_branch)
322
323class DryRun(object):
324  location_root = "%s/%s" % (os.getenv("HOME"), "infinispan_release_dry_run")
325  
326  def find_version(self, url):
327    return os.path.split(url)[1]
328      
329  def copy(self, src, dst):
330    prettyprint( "  DryRun: Executing %s" % ['rsync', '-rv', '--protocol=28', src, dst], Levels.DEBUG)
331    try:
332      os.makedirs(dst)
333    except:
334      pass
335    subprocess.check_call(['rsync', '-rv', '--protocol=28', src, dst])  
336
337class Uploader(object):
338  def __init__(self):
339    if settings['verbose']:
340      self.scp_cmd = ['scp', '-rv']
341      self.rsync_cmd = ['rsync', '-rv', '--protocol=28']
342    else:
343      self.scp_cmd = ['scp', '-r']
344      self.rsync_cmd = ['rsync', '-r', '--protocol=28']
345      
346  def upload_scp(self, fr, to, flags = []):
347    self.upload(fr, to, flags, list(self.scp_cmd))
348  
349  def upload_rsync(self, fr, to, flags = []):
350    self.upload(fr, to, flags, list(self.rsync_cmd))    
351  
352  def upload(self, fr, to, flags, cmd):
353    for e in flags:
354      cmd.append(e)
355    cmd.append(fr)
356    cmd.append(to)
357    subprocess.check_call(cmd)    
358  
359
360
361class DryRunUploader(DryRun):
362  def upload_scp(self, fr, to, flags = []):
363    self.upload(fr, to, "scp")
364  
365  def upload_rsync(self, fr, to, flags = []):
366    self.upload(fr, to.replace(':', '____').replace('@', "__"), "rsync")
367  
368  def upload(self, fr, to, type):
369    self.copy(fr, "%s/%s/%s" % (self.location_root, type, to))    
370
371def maven_build_distribution(version):
372  """Builds the distribution in the current working dir"""
373  mvn_commands = [["clean"], ["install", "-Pjmxdoc"],["install", "-Pconfigdoc"], ["deploy", "-Pdistribution"]]
374    
375  for c in mvn_commands:
376    c.append("-Dmaven.test.skip.exec=true")
377    if settings['dry_run']:
378      c.append("-Dmaven.deploy.skip=true")
379    if not settings['verbose']:
380      c.insert(0, '-q')
381    c.insert(0, 'mvn')
382    subprocess.check_call(c)
383  
384  print "Verifying build"
385  # Check contents of XSD in core/target/classes/schema/infinispan-config-{VMajor.VMinor}.xsd
386  fn = "core/target/classes/schema/infinispan-config-%s.%s.xsd" % ('5', '0')
387  if os.path.isfile(fn):
388	f = open(fn)
389	xsd = f.read()
390	f.close()
391	xsd.find("urn:infinispan:config:5.0")
392
393
394def get_version_pattern(): 
395  return re.compile("^([4-9]\.[0-9])\.[0-9]\.(Final|(ALPHA|BETA|CR)[1-9][0-9]?)$", re.IGNORECASE)
396
397def get_version_major_minor(full_version):
398  pattern = get_version_pattern()
399  matcher = pattern.match(full_version)
400  return matcher.group(1)
401
402def assert_python_minimum_version(major, minor):
403  e = re.compile('([0-9])\.([0-9])\.([0-9]).*')
404  m = e.match(sys.version)
405  major_ok = int(m.group(1)) == major
406  minor_ok = int(m.group(2)) >= minor
407  if not (minor_ok and major_ok):
408    prettyprint( "This script requires Python >= %s.%s.0.  You have %s" % (major, minor, sys.version), Levels.FATAL)
409    sys.exit(3)
410  
411    
412  
413
414