PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/manage.py

https://bitbucket.org/runhello/rad
Python | 336 lines | 268 code | 30 blank | 38 comment | 74 complexity | 4bec2c656323e25df16f8e63aca83dea MD5 | raw file
  1. #!/usr/bin/python
  2. # Script for maintaining a "Polycode" symlink within your project dir, such that
  3. # when you switch between revs of your project the script will sync the symlink
  4. # to the version of Polycode you were using at that time.
  5. # Run without arguments for help.
  6. # Notes: In order for this to work, you will need to add "current_polycode.txt"
  7. # (which the script creates) to your repository. Also, add the following to your
  8. # .hg/hgrc file to make sure the syncing is enforced:
  9. # [hooks]
  10. # precommit.polycodemanage = ./manage.py test
  11. # Version 1.0.13m2 (w/ alt url)
  12. # Written by A. McClure -- http://runhello.com
  13. import sys
  14. import os
  15. import re
  16. import subprocess
  17. import platform
  18. import shutil
  19. import optparse
  20. returncode = 0
  21. # Some important constants
  22. current_polycode = "current_polycode.txt"
  23. host_platform = platform.system()
  24. target_platform = host_platform
  25. polycode_dir = "Polycode"
  26. polycode_configuration = "Release"
  27. polycode_working_dir = "../Polycode"
  28. polycode_products_dir = "../Polycode-product"
  29. polycode_url = "https://bitbucket.org/runhello/polycode"
  30. # Command line help
  31. help = "\n"
  32. help += "\t%%prog detach\nPoint '%s' symlink to the current working build\n" % (polycode_dir)
  33. help += "\t%%prog save\nRemember the current Polycode revision as %s\n" % (current_polycode)
  34. help += "\t%%prog load\nPoint '%s' symlink to the revision in %s\n" % (polycode_dir, current_polycode)
  35. help += "\t%prog rebuild\nRebuild current working build\n"
  36. help += "\t%prog test\nReturn 0 if safe to check in, 1 otherwise.\n"
  37. help += "\t%prog info\nPrint current state of things.\n"
  38. help += "\n"
  39. help += "Arguments accepted (for use with \"load\" and \"rebuild\"):\n"
  40. help += "\t-b build (rebuild if necessary; use with \"load\")\n"
  41. help += "\t-d dependencies (when building, build dependencies)\n"
  42. help += "\t-p player (when building, build player)\n"
  43. help += "\t-q qlean (when building, qlean build directory first)\n"
  44. help += "\t--target=[platform] (build for target OS [platform])\n"
  45. help += "\t--host=[platform] (pretend we are building on target OS [platform])\n"
  46. help += "\t--cmake-args=\"[args]\" (pass additional arguments [args] to cmake)\n"
  47. help += "\n"
  48. help += "So for example:\n"
  49. help += "\t%prog load -b -d -p -q --target=Windows\nWould set up the current revision, cleaning then building everything, and force\nthe target platform to Windows."
  50. # Parse command line
  51. parser = optparse.OptionParser(usage=help)
  52. for a in ["q","p","d","b","f"]: # Single letter args, flags
  53. parser.add_option("-"+a, action="store_true")
  54. for a in ["bindings-only", "no-tools"]: # Long args, flags
  55. parser.add_option("--"+a, action="store_true")
  56. for a in ["target", "host", "cmake-args"]: # Long args with arguments
  57. parser.add_option("--"+a, action="store")
  58. (options, cmds) = parser.parse_args()
  59. def flag(a):
  60. try:
  61. return getattr(options, a)
  62. except AttributeError:
  63. return None
  64. # Extract and sanity-check platforms
  65. if flag("target"):
  66. target_platform = flag("target")
  67. if flag("host"):
  68. host_platform = flag("host")
  69. bindings = flag("p") or flag("bindings_only")
  70. myname = sys.argv[0]
  71. supported_platforms = ["Linux", "Darwin", "Windows"]
  72. if host_platform not in supported_platforms or target_platform not in supported_platforms:
  73. print "ERROR: Building on host platform %s for target platform %s\nOnly the following platforms are supported currently: %s" % (host_platform, target_platform, ", ".join(supported_platforms))
  74. sys.exit(1)
  75. if host_platform == "Windows":
  76. print "WARNING: Building on Windows has never been tested with this script."
  77. # Utilities
  78. def backtick(args, cwd = None):
  79. p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
  80. return p.stdout.read()
  81. def hg_id_for(dir):
  82. return backtick(["hg", "id"], dir)
  83. def hg_id():
  84. try:
  85. f = open(path_for([polycode_dir, current_polycode]))
  86. i = f.read()
  87. f.close()
  88. return i
  89. except IOError:
  90. return hg_id_for(polycode_dir)
  91. def cur_id():
  92. f = open(current_polycode)
  93. i = f.read()
  94. f.close()
  95. return i
  96. def just_id(i):
  97. return i.split()[0]
  98. def id_clean(i):
  99. return not("+" in i)
  100. def path_for(seq):
  101. if not isinstance(seq, str):
  102. seq = "/".join(seq) # Construct path
  103. return seq
  104. def p_link(to):
  105. if host_platform == "Darwin":
  106. extra_args = ["-h"]
  107. else:
  108. extra_args = ["-n"]
  109. return subprocess.call(["ln", "-s", "-f"] + extra_args + [path_for(to), polycode_dir])
  110. def mkdir(at):
  111. return subprocess.call(["mkdir", "-p", path_for(at)])
  112. def repo_clone():
  113. print "Checking out a new Polycode."
  114. if subprocess.call(["hg", "clone", polycode_url, polycode_working_dir]):
  115. print "ERROR: Failed to check out Polycode."
  116. return 1
  117. return 0
  118. def repo_update(rev = None):
  119. cmd = ["hg","up"]
  120. if rev:
  121. cmd += [rev]
  122. if subprocess.call(cmd, cwd=polycode_working_dir):
  123. print "ERROR: Updating working directory for build failed (somehow?)."
  124. return 1
  125. return 0
  126. def polycode_installpath(): # Always install to Release
  127. return path_for(["Release", target_platform])
  128. def release_dir():
  129. return [polycode_working_dir, polycode_installpath()]
  130. def build_dir():
  131. return "Build_%s" % (target_platform)
  132. def need_load_error():
  133. print "ERROR: No %s dir?\nTry:\n\t%s load\nOr:\n\t%s detach" % (polycode_dir, myname, myname)
  134. def need_save_error():
  135. print "ERROR: No %s file?\nTry:\n\t%s save" % (current_polycode, myname)
  136. def local_modifications_error():
  137. print "ERROR: Local modifications in %s\nTry:\n\tcd %s\n\tEDITOR=vi hg commit\n\tcd ..\n\t%s save" % (polycode_dir, polycode_dir, myname)
  138. def cmake_args():
  139. if flag("cmake_args"):
  140. return [flag("cmake_args")]
  141. return []
  142. if target_platform == "Darwin":
  143. def cmake_in(dir, extra_args = []):
  144. return subprocess.call(["cmake", "-G", "Xcode"] + extra_args + cmake_args() + [".."], cwd=dir)
  145. def build_in(dir, target, makecmd = None):
  146. return subprocess.call(["xcodebuild", "-target", target, "-configuration", polycode_configuration], cwd=dir)
  147. else: # Linux, Windows
  148. def cmake_in(dir, extra_args = []):
  149. return subprocess.call(["cmake", "-G", "Unix Makefiles", "-DCMAKE_BUILD_TYPE="+polycode_configuration] + extra_args + cmake_args() + [".."], cwd=dir)
  150. def build_in(dir, target, makecmd = None):
  151. return subprocess.call(["make"] + ([makecmd] if makecmd else []) + ["VERBOSE=1"], cwd=dir)
  152. # Command implementations
  153. def cmd_test():
  154. ci = None
  155. hi = None
  156. # Look up claimed current id
  157. try:
  158. ci = just_id(cur_id())
  159. except IOError:
  160. need_save_error()
  161. return 1
  162. # Look up de facto current id
  163. try:
  164. hi = just_id(hg_id())
  165. except OSError:
  166. need_load_error()
  167. return 1
  168. # Scrub for local modifications
  169. # FIXME: If a + sign ever somehow gets into a build product current_polycode.txt, this will give an unhelpful message.
  170. if not id_clean(hi):
  171. local_modifications_error()
  172. return 1
  173. # Check for id mismatch
  174. if ci != hi:
  175. print "ERROR: %s out of date\nTry:\n\t%s save" % (current_polycode, myname)
  176. return 1
  177. return 0
  178. def cmd_save():
  179. try:
  180. # Load and sanity-check de facto current id
  181. hi = hg_id()
  182. if not id_clean(hi) and not flag("f"):
  183. local_modifications_error()
  184. print "Or call with -f to save anyway"
  185. return 1
  186. # Write current_polycode.txt
  187. f = open(current_polycode, "w")
  188. f.write(hi)
  189. f.close()
  190. except OSError:
  191. need_load_error()
  192. return 1
  193. return 0
  194. def cmd_build():
  195. def typical_build(path, extra_args = [], also_install = True):
  196. build = path_for(path)
  197. mkdir(build)
  198. if cmake_in(build, extra_args):
  199. print "ERROR: cmake failed";
  200. sys.exit(1) # TODO: try to snake back out through the stack cleanly...
  201. if build_in(build, "ALL_BUILD"):
  202. print "ERROR: build failed";
  203. sys.exit(1)
  204. if also_install:
  205. build_in(build, "install", "install")
  206. # Clean
  207. if flag("q"):
  208. print "Cleaning previous build."
  209. def do_rmtree(path):
  210. path = path_for(path)
  211. if not path or len(path) <= 3: # Just in case...
  212. return
  213. print "Deleting %s" % (path)
  214. shutil.rmtree(path, True)
  215. if flag("d"):
  216. do_rmtree([polycode_working_dir, "Release", target_platform]) # Only clear product dir if cleaning EVERYTHING
  217. do_rmtree([polycode_working_dir, "Dependencies", build_dir()])
  218. do_rmtree([polycode_working_dir, build_dir()])
  219. # Build dependencies
  220. if flag("d"):
  221. print "Building dependencies."
  222. typical_build([polycode_working_dir, "Dependencies", build_dir()], [], False)
  223. # Build main library
  224. print "Building Polycode."
  225. typical_build([polycode_working_dir, build_dir()],
  226. (["-DPOLYCODE_BUILD_PLAYER=1", "-DPOLYCODE_INSTALL_PLAYER=1"] if flag("p") else []) +
  227. (["-DPOLYCODE_BUILD_BINDINGS=1"] if bindings else []) +
  228. (["-DPOLYCODE_BUILD_TOOLS=0"] if flag("no_tools") else []) )
  229. def cmd_load(): # TODO: Take an argument
  230. # Get current_polycode id
  231. try:
  232. ci = just_id(cur_id())
  233. except IOError:
  234. print "ERROR: No %s file?\nTry:\n\t%s rebuild\n\t%s detach\n\t%s save" % (current_polycode, myname, myname, myname)
  235. return 1
  236. # Check for local modifications
  237. if not id_clean(ci) and not flag("f"):
  238. print "ERROR: Saved Polycode version %s had local modifications,\nwhich won't be possible to recover.\nCall again with -f to load anyway" % ci
  239. return 1
  240. # Check to see if we already have the revision we need
  241. desired_path = path_for([polycode_products_dir, target_platform, ci])
  242. if not os.path.isdir(desired_path):
  243. # We don't; build it
  244. if not flag("b"):
  245. print "ERROR: Don't have a product built for version %s.\nTry again with -b -d to build one (could take awhile)." % ci
  246. return 1
  247. print "Building new product for version %s." % ci
  248. # Check to see if we have a Polycode scratch dir to build from
  249. if not os.path.isdir(polycode_working_dir):
  250. if repo_clone():
  251. return 1
  252. else:
  253. if not id_clean(just_id(hg_id_for(polycode_working_dir))) and not flag("f"): # Redundant?
  254. print "ERROR: Working dir %s has local modifications; script can't build over that. Try:\n\tcd %s\n\thg revert --all\n(This is maybe destructive)" % (polycode_working_dir, polycode_working_dir)
  255. return 1
  256. # Grab the needed copy of Polycode
  257. if repo_update(ci):
  258. return 1
  259. # Actually build
  260. if cmd_build():
  261. print "ERROR: Build failed."
  262. return 1
  263. # Catalogue result
  264. mkdir([polycode_products_dir, target_platform])
  265. if subprocess.call(["cp", "-R", path_for(release_dir()), desired_path]):
  266. print "ERROR: Couldn't copy built product to build directory"
  267. return 1
  268. new_id = hg_id_for(polycode_working_dir)
  269. f = open(path_for([desired_path, current_polycode]), "w")
  270. f.write(new_id)
  271. f.close()
  272. # Make actual symlink
  273. p_link(desired_path)
  274. def cmd_rebuild():
  275. if not os.path.isdir(polycode_working_dir):
  276. if repo_clone():
  277. return 1
  278. return cmd_build()
  279. # Actual action
  280. if len(cmds) == 1:
  281. arg = cmds[0]
  282. if arg == 'detach':
  283. returncode = p_link(release_dir())
  284. elif arg == 'save':
  285. returncode = cmd_save()
  286. elif arg == 'load':
  287. returncode = cmd_load()
  288. elif arg == 'test':
  289. returncode = cmd_test()
  290. elif arg == 'rebuild':
  291. returncode = cmd_rebuild()
  292. elif arg == 'info':
  293. try:
  294. subprocess.call(["ls", "-l", polycode_dir])
  295. print
  296. hi = just_id(hg_id())
  297. print "Building against Polycode revision %s%s\n" % (hi, " (with local modifications)" if not id_clean(hi) else "")
  298. except OSError:
  299. pass
  300. returncode = cmd_test()
  301. else:
  302. parser.error("Don't recognize command %s" % arg)
  303. else:
  304. parser.error("Too many commands? %s" % cmds)
  305. sys.exit(returncode)