PageRenderTime 57ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/bin/utils.py

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