acprep 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. #!/usr/bin/env python
  2. # acprep, version 3.1
  3. #
  4. # This script simply sets up the compiler and linker flags for all the various
  5. # build permutations I use for testing and profiling.
  6. import inspect
  7. import logging
  8. import logging.handlers
  9. import optparse
  10. import os
  11. import re
  12. import shutil
  13. import string
  14. import sys
  15. import time
  16. import tempfile
  17. import datetime
  18. try:
  19. import hashlib
  20. except:
  21. import md5
  22. from os.path import *
  23. from stat import *
  24. from subprocess import Popen, PIPE, call
  25. LEVELS = {'DEBUG': logging.DEBUG,
  26. 'INFO': logging.INFO,
  27. 'WARNING': logging.WARNING,
  28. 'ERROR': logging.ERROR,
  29. 'CRITICAL': logging.CRITICAL}
  30. def which(program):
  31. def is_exe(fpath):
  32. return os.path.exists(fpath) and os.access(fpath, os.X_OK)
  33. def ext_candidates(fpath):
  34. yield fpath
  35. for ext in os.environ.get("PATHEXT", "").split(os.pathsep):
  36. yield fpath + ext
  37. fpath, fname = os.path.split(program)
  38. if fpath:
  39. if is_exe(program):
  40. return program
  41. else:
  42. for path in os.environ["PATH"].split(os.pathsep):
  43. exe_file = os.path.join(path, program)
  44. for candidate in ext_candidates(exe_file):
  45. if is_exe(candidate):
  46. return candidate
  47. return None
  48. class BoostInfo(object):
  49. def dependencies(self, system):
  50. if system == 'darwin-homebrew':
  51. return [ 'boost' ]
  52. if system == 'darwin-macports':
  53. return [ 'boost-jam', 'boost', '+python27+universal' ]
  54. if system == 'centos':
  55. return [ 'boost-devel' ]
  56. elif system == 'ubuntu-trusty':
  57. return [ 'libboost-dev',
  58. 'libboost-date-time-dev',
  59. 'libboost-filesystem-dev',
  60. 'libboost-iostreams-dev',
  61. 'libboost-python-dev',
  62. 'libboost-regex-dev',
  63. 'libboost-system-dev',
  64. 'libboost-test-dev' ]
  65. elif system == 'ubuntu-saucy' or system == 'ubuntu-precise':
  66. return [ 'autopoint',
  67. 'libboost-dev',
  68. 'libboost-test-dev',
  69. 'libboost-regex-dev',
  70. 'libboost-date-time-dev',
  71. 'libboost-filesystem-dev',
  72. 'libboost-iostreams-dev',
  73. 'libboost-python-dev' ]
  74. elif system == 'ubuntu-lucid':
  75. return [ 'bjam', 'autopoint',
  76. 'libboost-dev',
  77. 'libboost-regex-dev',
  78. 'libboost-date-time-dev',
  79. 'libboost-filesystem-dev',
  80. 'libboost-iostreams-dev',
  81. 'libboost-python-dev' ]
  82. class CommandLineApp(object):
  83. "Base class for building command line applications."
  84. force_exit = True # If true, always ends run() with sys.exit()
  85. log_handler = None
  86. boost_major = "1_52"
  87. def __init__(self):
  88. "Initialize CommandLineApp."
  89. # Create the logger
  90. self.log = logging.getLogger(os.path.basename(sys.argv[0]))
  91. ch = logging.StreamHandler()
  92. formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
  93. ch.setFormatter(formatter)
  94. self.log.addHandler(ch)
  95. self.log_handler = ch
  96. # Setup the options parser
  97. usage = 'usage: %prog [OPTIONS...] [ARGS...]'
  98. op = self.option_parser = optparse.OptionParser(usage = usage,
  99. conflict_handler = 'resolve')
  100. op.add_option('', '--debug',
  101. action='store_true', dest='debug',
  102. default=False, help='show debug messages and pass exceptions')
  103. op.add_option('-v', '--verbose',
  104. action='store_true', dest='verbose',
  105. default=False, help='show informational messages')
  106. op.add_option('-q', '--quiet',
  107. action='store_true', dest='quiet',
  108. default=False, help='do not show log messages on console')
  109. op.add_option('', '--log', metavar='FILE',
  110. type='string', action='store', dest='logfile',
  111. default=False, help='append logging data to FILE')
  112. op.add_option('', '--loglevel', metavar='LEVEL',
  113. type='string', action='store', dest='loglevel',
  114. default=False, help='set log level: DEBUG, INFO, WARNING, ERROR, CRITICAL')
  115. self.options = op.get_default_values()
  116. def main(self, *args):
  117. """Main body of your application.
  118. This is the main portion of the app, and is run after all of the
  119. arguments are processed. Override this method to implment the primary
  120. processing section of your application."""
  121. pass
  122. def handleInterrupt(self):
  123. """Called when the program is interrupted via Control-C or SIGINT.
  124. Returns exit code."""
  125. self.log.error('Canceled by user.')
  126. return 1
  127. def handleMainException(self):
  128. "Invoked when there is an error in the main() method."
  129. if not self.options.debug:
  130. self.log.exception('Caught exception')
  131. return 1
  132. ## INTERNALS (Subclasses should not need to override these methods)
  133. def run(self):
  134. """Entry point.
  135. Process options and execute callback functions as needed. This method
  136. should not need to be overridden, if the main() method is defined."""
  137. # Process the options supported and given
  138. self.options, main_args = self.option_parser.parse_args(values=self.options)
  139. if self.options.logfile:
  140. fh = logging.handlers.RotatingFileHandler(self.options.logfile,
  141. maxBytes = (1024 * 1024),
  142. backupCount = 5)
  143. formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")
  144. fh.setFormatter(formatter)
  145. self.log.addHandler(fh)
  146. if self.options.quiet:
  147. self.log.removeHandler(self.log_handler)
  148. ch = logging.handlers.SysLogHandler()
  149. formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
  150. ch.setFormatter(formatter)
  151. self.log.addHandler(ch)
  152. self.log_handler = ch
  153. if self.options.loglevel:
  154. self.log.setLevel(LEVELS[self.options.loglevel])
  155. elif self.options.debug:
  156. self.log.setLevel(logging.DEBUG)
  157. elif self.options.verbose:
  158. self.log.setLevel(logging.INFO)
  159. exit_code = 0
  160. try:
  161. # We could just call main() and catch a TypeError, but that would
  162. # not let us differentiate between application errors and a case
  163. # where the user has not passed us enough arguments. So, we check
  164. # the argument count ourself.
  165. argspec = inspect.getargspec(self.main)
  166. expected_arg_count = len(argspec[0]) - 1
  167. if len(main_args) >= expected_arg_count:
  168. exit_code = self.main(*main_args)
  169. else:
  170. self.log.debug('Incorrect argument count (expected %d, got %d)' %
  171. (expected_arg_count, len(main_args)))
  172. self.option_parser.print_help()
  173. exit_code = 1
  174. except KeyboardInterrupt:
  175. exit_code = self.handleInterrupt()
  176. except SystemExit as msg:
  177. exit_code = msg.args[0]
  178. except Exception:
  179. exit_code = self.handleMainException()
  180. if self.options.debug:
  181. raise
  182. if self.force_exit:
  183. sys.exit(exit_code)
  184. return exit_code
  185. class PrepareBuild(CommandLineApp):
  186. #########################################################################
  187. # Initialization routines #
  188. #########################################################################
  189. def initialize(self):
  190. self.log.debug('Initializing all state variables')
  191. self.should_clean = False
  192. self.configured = False
  193. self.current_ver = None
  194. #self.current_flavor = 'default'
  195. self.current_flavor = 'debug'
  196. self.products_dir = None
  197. self.configure_args = []
  198. self.CXXFLAGS = []
  199. self.LDFLAGS = []
  200. self.envvars = {
  201. 'CXX': 'g++',
  202. 'CXXFLAGS': '',
  203. 'LDFLAGS': '',
  204. }
  205. for varname in self.envvars.keys():
  206. if varname in os.environ:
  207. self.envvars[varname] = os.environ[varname]
  208. if varname.endswith('FLAGS'):
  209. self.__dict__[varname] = str.split(os.environ[varname])
  210. self.envvars[varname] = ''
  211. # If ~/Products/ or build/ exists, use them instead of the source tree
  212. # for building
  213. products = self.default_products_directory()
  214. if (exists(products) and isdir(products)) or \
  215. (exists('build') and isdir('build')):
  216. self.options.build_dir = None
  217. def __init__(self):
  218. CommandLineApp.__init__(self)
  219. self.log.setLevel(logging.INFO)
  220. self.source_dir = os.getcwd()
  221. op = self.option_parser
  222. op.add_option('', '--help', action="callback",
  223. callback=self.option_help,
  224. help='Show this help text')
  225. op.add_option('-j', '--jobs', metavar='N',
  226. type='int', action='store', dest='jobs',
  227. default=1, help='Allow N make jobs at once')
  228. op.add_option('', '--boost', metavar='BOOST_ROOT',
  229. action="store", dest="boost_root",
  230. help='Set Boost library root (ex: "--boost=/usr/local")')
  231. op.add_option('', '--boost-suffix', metavar='BOOST_SUFFIX',
  232. action="store", dest="boost_suffix",
  233. help='Set Boost library suffix (ex: "--boost-suffix=-mt")')
  234. op.add_option('', '--boost-include', metavar='BOOST_INCLUDE',
  235. action="store", dest="boost_include",
  236. help='Set Boost include path (ex: "--boost-include=DIR")')
  237. op.add_option('', '--compiler', metavar='COMPILER',
  238. action="store", dest="compiler",
  239. help='Use the Clang C++ compiler')
  240. op.add_option('', '--cxx', metavar='COMPILER',
  241. action="store", dest="compiler",
  242. help='Use the Clang C++ compiler')
  243. op.add_option('-N', '--ninja', action='store_true', dest='use_ninja',
  244. default=False,
  245. help='Use ninja to build, rather than make')
  246. op.add_option('', '--no-git', action='store_true', dest='no_git',
  247. default=False,
  248. help='Do not call out to Git; useful for offline builds')
  249. op.add_option('', '--doxygen', action='store_true',
  250. dest='enable_doxygen', default=False,
  251. help='Enable use of Doxygen to build ref manual ("make docs")')
  252. op.add_option('', '--python', action='store_true', dest='python',
  253. default=False,
  254. help='Enable Python support')
  255. op.add_option('', '--no-python', action='store_false', dest='python',
  256. help='Disable python support (default)')
  257. op.add_option('', '--prefix', metavar='DIR', action="store",
  258. dest="prefix_dir", help='Use custom installation prefix')
  259. op.add_option('', '--products', metavar='DIR', action="store",
  260. dest="option_products",
  261. help='Collect all build products in this directory')
  262. op.add_option('', '--output', metavar='DIR', action="store",
  263. default=self.source_dir,
  264. dest="build_dir", help='Build in the specified directory')
  265. op.add_option('', '--local', action="callback",
  266. callback=self.option_local,
  267. help='Build directly within the source tree (default)')
  268. self.options = op.get_default_values()
  269. self.initialize()
  270. def main(self, *args):
  271. if args and args[0] in ['default', 'debug', 'opt', 'gcov', 'gprof']:
  272. self.current_flavor = args[0]
  273. args = args[1:]
  274. if args:
  275. cmd = args[0]
  276. if 'phase_' + cmd not in PrepareBuild.__dict__:
  277. self.log.error("Unknown build phase: " + cmd + "\n")
  278. sys.exit(1)
  279. else:
  280. args = args[1:]
  281. else:
  282. cmd = 'config'
  283. self.log.info('Invoking primary phase: ' + cmd)
  284. PrepareBuild.__dict__['phase_' + cmd](self, *args)
  285. #########################################################################
  286. # General utility code #
  287. #########################################################################
  288. def execute(self, *args):
  289. try:
  290. self.log.debug('Executing command: ' + ' '.join(args))
  291. retcode = call(args, shell=False)
  292. if retcode < 0:
  293. self.log.error("Child was terminated by signal", -retcode)
  294. sys.exit(1)
  295. elif retcode != 0:
  296. self.log.error("Execution failed: " + ' '.join(args))
  297. sys.exit(1)
  298. except OSError as e:
  299. self.log.error("Execution failed: " + e)
  300. sys.exit(1)
  301. def get_stdout(self, *args):
  302. try:
  303. self.log.debug('Executing command: ' + ' '.join(args))
  304. proc = Popen(args, shell=False, stdout=PIPE)
  305. stdout = proc.stdout.read()
  306. retcode = proc.wait()
  307. if retcode < 0:
  308. self.log.error("Child was terminated by signal",
  309. -retcode)
  310. sys.exit(1)
  311. elif retcode != 0:
  312. self.log.error("Execution failed: " + ' '.join(args))
  313. sys.exit(1)
  314. return stdout[:-1]
  315. except OSError as e:
  316. self.log.error("Execution failed:" + e)
  317. sys.exit(1)
  318. def isnewer(self, file1, file2):
  319. "Check if file1 is newer than file2."
  320. if not exists(file2):
  321. return True
  322. return os.stat(file1)[ST_MTIME] > os.stat(file2)[ST_MTIME]
  323. #########################################################################
  324. # Determine information about the surroundings #
  325. #########################################################################
  326. def prefix_directory(self):
  327. if self.options.prefix_dir:
  328. return self.options.prefix_dir
  329. else:
  330. return None
  331. def default_products_directory(self):
  332. return join(os.environ['HOME'], "Products")
  333. def products_directory(self):
  334. if not self.products_dir:
  335. products = self.default_products_directory()
  336. if not exists(products) or not isdir(products):
  337. products = join(self.source_dir, 'build')
  338. products = join(products, basename(self.source_dir))
  339. self.products_dir = products
  340. return self.products_dir
  341. def build_directory(self):
  342. if not self.options.build_dir:
  343. self.options.build_dir = join(self.products_directory(),
  344. self.current_flavor)
  345. return self.options.build_dir
  346. def ensure(self, dirname):
  347. if not exists(dirname):
  348. self.log.info('Making directory: ' + dirname)
  349. os.makedirs(dirname)
  350. elif not isdir(dirname):
  351. self.log.error('Directory is not a directory: ' + dirname)
  352. sys.exit(1)
  353. return dirname
  354. def git_working_tree(self):
  355. return exists('.git') and isdir('.git') and not self.options.no_git
  356. def current_version(self):
  357. if not self.current_ver:
  358. major, minor, patch, date = None, None, None, None
  359. version_m4 = open('CMakeLists.txt', 'r')
  360. for line in version_m4.readlines():
  361. match = re.match('^set\(Ledger_VERSION_MAJOR ([0-9]+)\)', line)
  362. if match:
  363. major = match.group(1)
  364. match = re.match('^set\(Ledger_VERSION_MINOR ([0-9]+)\)', line)
  365. if match:
  366. minor = match.group(1)
  367. match = re.match('^set\(Ledger_VERSION_PATCH ([0-9]+)\)', line)
  368. if match:
  369. patch = match.group(1)
  370. match = re.match('^set\(Ledger_VERSION_DATE ([0-9]+)\)', line)
  371. if match:
  372. date = match.group(1)
  373. break
  374. self.current_ver = "%s.%s.%s%s" % (major, minor, patch,
  375. "-%s" % date if date else "")
  376. version_m4.close()
  377. return self.current_ver
  378. def phase_products(self, *args):
  379. self.log.info('Executing phase: products')
  380. print(self.products_directory())
  381. def phase_info(self, *args):
  382. self.log.info('Executing phase: info')
  383. environ, conf_args = self.configure_environment()
  384. self.log.info("Current version => " + self.current_version())
  385. self.log.info("Current flavor => " + self.current_flavor)
  386. self.log.info("Source directory => " + self.source_dir)
  387. if self.prefix_directory():
  388. self.log.info("Installation prefix => " + self.prefix_directory())
  389. self.log.info("Products directory => " + self.products_directory())
  390. self.log.info("Build directory => " + self.build_directory())
  391. self.log.debug('CMake environment =>')
  392. keys = environ.keys()
  393. keys.sort()
  394. for key in keys:
  395. if key in ['PATH', 'CXX'] or key.endswith('FLAGS'):
  396. self.log.debug(' %s=%s' % (key, environ[key]))
  397. self.log.debug('CMake arguments =>')
  398. for arg in conf_args + list(args):
  399. self.log.debug(' %s' % arg)
  400. def phase_sloc(self, *args):
  401. self.log.info('Executing phase: sloc')
  402. self.execute('sloccount', 'src', 'python', 'lisp', 'test')
  403. #########################################################################
  404. # Update local files with the latest information #
  405. #########################################################################
  406. def phase_pull(self, *args):
  407. self.log.info('Executing phase: pull')
  408. if self.git_working_tree():
  409. self.execute('git', 'pull')
  410. #########################################################################
  411. # Automatic installation of build dependencies #
  412. #########################################################################
  413. def phase_dependencies(self, *args):
  414. self.log.info('Executing phase: dependencies')
  415. self.log.info("Installing Ledger's build dependencies ...")
  416. system = self.get_stdout('uname', '-s')
  417. if system == 'Darwin':
  418. if exists('/opt/local/bin/port'):
  419. self.log.info('Looks like you are using MacPorts on OS X')
  420. packages = [
  421. 'sudo', 'port', 'install', '-f',
  422. 'automake', 'autoconf', 'libtool',
  423. 'python27', '+universal',
  424. 'libiconv', '+universal',
  425. 'zlib', '+universal',
  426. 'gmp' ,'+universal', 'mpfr', '+universal',
  427. 'ncurses', '+universal', 'ncursesw', '+universal',
  428. 'gettext' ,'+universal',
  429. 'libedit' ,'+universal',
  430. 'texlive-xetex', 'doxygen', 'graphviz', 'texinfo',
  431. 'lcov', 'sloccount'
  432. ] + BoostInfo().dependencies('darwin-macports')
  433. self.log.info('Executing: ' + ' '.join(packages))
  434. self.execute(*packages)
  435. elif exists('/usr/local/bin/brew') or exists('/opt/local/bin/brew'):
  436. self.log.info('Looks like you are using Homebrew on OS X')
  437. packages = [
  438. 'brew', 'install',
  439. 'cmake', 'ninja',
  440. 'mpfr', 'gmp',
  441. ] + BoostInfo().dependencies('darwin-homebrew')
  442. self.log.info('Executing: ' + ' '.join(packages))
  443. self.execute(*packages)
  444. elif exists('/sw/bin/fink'):
  445. self.log.info('Looks like you are using Fink on OS X')
  446. self.log.error("I don't know the package names for Fink yet!")
  447. sys.exit(1)
  448. elif system == 'Linux':
  449. if exists('/etc/issue'):
  450. issue = open('/etc/issue')
  451. if issue.readline().startswith('Ubuntu'):
  452. release = open('/etc/lsb-release')
  453. info = release.read()
  454. release.close()
  455. if re.search('trusty', info):
  456. self.log.info('Looks like you are using APT on Ubuntu Trusty')
  457. packages = [
  458. 'sudo', 'apt-get', 'install',
  459. 'build-essential',
  460. 'doxygen',
  461. 'cmake',
  462. 'ninja-build',
  463. 'zlib1g-dev',
  464. 'libbz2-dev',
  465. 'python-dev',
  466. 'libgmp3-dev',
  467. 'libmpfr-dev',
  468. 'gettext',
  469. 'libedit-dev',
  470. 'texinfo',
  471. 'lcov',
  472. 'libutfcpp-dev',
  473. 'sloccount'
  474. ] + BoostInfo().dependencies('ubuntu-trusty')
  475. elif re.search('saucy', info):
  476. self.log.info('Looks like you are using APT on Ubuntu Saucy')
  477. packages = [
  478. 'sudo', 'apt-get', 'install',
  479. 'build-essential',
  480. 'libtool',
  481. 'cmake',
  482. 'ninja-build',
  483. 'zlib1g-dev',
  484. 'libbz2-dev',
  485. 'python-dev',
  486. 'libgmp-dev',
  487. 'libmpfr-dev',
  488. 'gettext',
  489. 'libedit-dev',
  490. 'texinfo',
  491. 'lcov',
  492. 'sloccount'
  493. ] + BoostInfo().dependencies('ubuntu-saucy')
  494. elif re.search('precise', info):
  495. self.log.info('Looks like you are using APT on Ubuntu Precise')
  496. packages = [
  497. 'sudo', 'apt-get', 'install',
  498. 'build-essential',
  499. 'libtool',
  500. 'cmake',
  501. 'zlib1g-dev',
  502. 'libbz2-dev',
  503. 'python-dev',
  504. 'libgmp-dev',
  505. 'libmpfr-dev',
  506. 'gettext',
  507. 'libedit-dev',
  508. 'texinfo',
  509. 'lcov',
  510. 'libutfcpp-dev',
  511. 'sloccount'
  512. ] + BoostInfo().dependencies('ubuntu-precise')
  513. else:
  514. self.log.info('I do not recognize your version of Ubuntu!')
  515. packages = None
  516. if packages:
  517. self.log.info('Executing: ' + ' '.join(packages))
  518. self.execute(*packages)
  519. if exists('/etc/redhat-release'):
  520. release = open('/etc/redhat-release').readline()
  521. if release.startswith('CentOS'):
  522. self.log.info('Looks like you are using YUM on CentOS')
  523. packages = [
  524. 'sudo', 'yum', 'install',
  525. 'gcc',
  526. 'gcc-c++',
  527. 'compat-gcc-*',
  528. 'make',
  529. 'libtool',
  530. 'autoconf',
  531. 'automake',
  532. 'zlib-devel',
  533. 'bzip2-devel',
  534. 'python-devel',
  535. 'gmp-devel',
  536. 'gettext-devel',
  537. #'mpfr-devel'
  538. 'libedit-devel',
  539. #'texlive-full',
  540. #'doxygen',
  541. #'graphviz',
  542. 'texinfo',
  543. #'lcov',
  544. #'sloccount'
  545. ]
  546. self.log.info('Executing: ' + ' '.join(packages))
  547. self.execute(*packages)
  548. elif release.startswith('Fedora release 20'):
  549. self.log.info('Looks like you are using YUM on Fedora 20')
  550. packages = [
  551. 'sudo', 'yum', 'install',
  552. 'boost-devel',
  553. 'bzip2-devel',
  554. 'cmake',
  555. 'doxygen',
  556. 'gcc',
  557. 'gcc-c++',
  558. 'gettext',
  559. 'gettext-devel',
  560. 'gmp-devel',
  561. 'lcov',
  562. 'libedit-devel',
  563. 'mpfr-devel',
  564. 'ninja-build',
  565. 'python-devel',
  566. 'sloccount',
  567. 'texinfo',
  568. 'zlib-devel'
  569. ]
  570. self.log.info('Executing: ' + ' '.join(packages))
  571. self.execute(*packages)
  572. elif system.startswith('CYGWIN'):
  573. self.log.info('Looks like you are using Cygwin')
  574. self.log.info('Please install the dependencies manually.')
  575. #########################################################################
  576. # Determine the system's basic configuration #
  577. #########################################################################
  578. def setup_for_johnw(self):
  579. self.configure_args.append('-DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON')
  580. if not self.options.compiler:
  581. self.configure_args.append('-DCMAKE_CXX_COMPILER:PATH=/usr/local/bin/clang++')
  582. if self.current_flavor == 'opt':
  583. self.configure_args.append('-DCMAKE_CXX_FLAGS_RELEASE:STRING=-O3')
  584. self.configure_args.append('-DCMAKE_EXE_LINKER_FLAGS:STRING=-O3')
  585. self.configure_args.append('-DCMAKE_SHARED_LINKER_FLAGS:STRING=-O3')
  586. self.configure_args.append('-DCMAKE_MODULE_LINKER_FLAGS:STRING=-O3')
  587. #else:
  588. # self.CXXFLAGS.append('-g -O1 -faddress-sanitizer')
  589. # self.LDFLAGS.append('-g -O1 -faddress-sanitizer')
  590. self.configure_args.append(self.source_dir)
  591. else:
  592. self.configure_args.append('-DCMAKE_CXX_COMPILER:PATH=' + self.options.compiler)
  593. self.configure_args.append('-DCMAKE_INCLUDE_PATH:STRING=/usr/local/include')
  594. self.configure_args.append('-DCMAKE_LIBRARY_PATH:STRING=/usr/local/lib')
  595. self.configure_args.append('-DBOOST_ROOT=/usr/local')
  596. self.configure_args.append(self.source_dir)
  597. def setup_for_system(self):
  598. system = str(self.get_stdout('uname', '-s'))
  599. self.log.info('System type is => ' + system)
  600. if self.options.enable_doxygen:
  601. self.configure_args.append('-DUSE_DOXYGEN=1')
  602. if self.options.python:
  603. self.configure_args.append('-DUSE_PYTHON=1')
  604. if system.startswith('CYGWIN'):
  605. self.configure_args.append('-G')
  606. self.configure_args.append('Unix Makefiles')
  607. elif self.options.use_ninja:
  608. self.configure_args.append('-GNinja')
  609. if exists('/Users/johnw/Projects/ledger/plan/TODO'):
  610. self.setup_for_johnw()
  611. def setup_flavor(self):
  612. self.setup_for_system()
  613. if 'setup_flavor_' + self.current_flavor not in PrepareBuild.__dict__:
  614. self.log.error('Unknown build flavor "%s"' % self.current_flavor)
  615. sys.exit(1)
  616. self.log.info('Setting up build flavor => ' + self.current_flavor)
  617. PrepareBuild.__dict__['setup_flavor_' + self.current_flavor](self)
  618. def escape_string(self, data):
  619. return re.sub('(["\\\\])', '\\\\\\1', data)
  620. def finalize_config(self):
  621. self.setup_flavor()
  622. for var in ('CXXFLAGS', 'LDFLAGS'):
  623. value = self.__dict__[var]
  624. if value:
  625. first = not self.envvars[var]
  626. for member in value:
  627. #escaped = self.escape_string(member)
  628. #if member != escaped:
  629. # member = escaped
  630. if first:
  631. first = False
  632. else:
  633. self.envvars[var] += ' '
  634. self.envvars[var] += member
  635. self.log.debug('Final value of %s: %s' %
  636. (var, self.envvars[var]))
  637. elif var in self.envvars:
  638. del self.envvars[var]
  639. #########################################################################
  640. # Options that can modify any build flavor #
  641. #########################################################################
  642. def option_local(self, option=None, opt_str=None, value=None, parser=None):
  643. self.log.debug('Saw option --local')
  644. self.options.build_dir = self.source_dir
  645. def option_help(self, option=None, opt_str=None, value=None, parser=None):
  646. self.phase_help()
  647. #########################################################################
  648. # The various build flavors #
  649. #########################################################################
  650. def setup_flavor_default(self):
  651. pass
  652. def setup_flavor_debug(self):
  653. self.configure_args.append('-DBUILD_DEBUG=1')
  654. def setup_flavor_opt(self):
  655. self.configure_args.append('-DNO_ASSERTS=1')
  656. def setup_flavor_gcov(self):
  657. # NO_ASSERTS is set so that branch coverage ignores the never-taken
  658. # else branch inside assert statements.
  659. self.configure_args.append('-DBUILD_DEBUG=1')
  660. self.configure_args.append('-DNO_ASSERTS=1')
  661. self.configure_args.append('-DCLANG_GCOV=1')
  662. self.CXXFLAGS.append('-fprofile-arcs')
  663. self.CXXFLAGS.append('-ftest-coverage')
  664. self.LDFLAGS.append('-fprofile-arcs')
  665. self.LDFLAGS.append('-ftest-coverage')
  666. if not self.options.compiler or self.options.compiler == "clang-3.1":
  667. self.LDFLAGS.append('-lgcov')
  668. def setup_flavor_gprof(self):
  669. self.configure_args.append('-DBUILD_DEBUG=1')
  670. self.CXXFLAGS.append('-pg')
  671. self.LDFLAGS.append('-pg')
  672. #########################################################################
  673. # Configure build tree using CMake #
  674. #########################################################################
  675. def configure_environment(self):
  676. self.finalize_config()
  677. environ = dict(os.environ)
  678. for key, value in self.envvars.items():
  679. if value:
  680. environ[key] = value
  681. if self.build_directory() == self.source_dir:
  682. conf_args = ['cmake']
  683. else:
  684. conf_args = ['cmake', self.source_dir]
  685. if not which('cmake'):
  686. self.log.error("Cannot find CMake, please check your PATH")
  687. sys.exit(1)
  688. for var in ('CXX', 'CXXFLAGS', 'LDFLAGS'):
  689. if self.envvars.get(var) and (var.endswith('FLAGS')
  690. or exists(self.envvars[var])):
  691. if var == 'CXX':
  692. conf_args.append('-DCMAKE_CXX_COMPILER=%s' %
  693. self.envvars[var])
  694. elif var == 'CXXFLAGS':
  695. conf_args.append('-DCMAKE_CXX_FLAGS=%s' %
  696. self.envvars[var])
  697. elif var == 'LDFLAGS':
  698. conf_args.append('-DCMAKE_EXE_LINKER_FLAGS=%s' %
  699. self.envvars[var])
  700. if self.options.boost_root:
  701. conf_args.append('-DBOOST_ROOT=%s' %
  702. self.options.boost_root)
  703. conf_args.append('-DBoost_NO_SYSTEM_PATHS=TRUE')
  704. if self.options.boost_suffix:
  705. conf_args.append('-DBoost_COMPILER=%s' %
  706. self.options.boost_suffix)
  707. if self.options.boost_include:
  708. conf_args.append('-DBOOST_INCLUDEDIR=%s' %
  709. self.options.boost_include)
  710. if self.prefix_directory():
  711. conf_args.append('-DCMAKE_INSTALL_PREFIX=%s' % self.prefix_directory())
  712. return (environ, conf_args + self.configure_args)
  713. def phase_configure(self, *args):
  714. self.log.info('Executing phase: configure')
  715. self.configured = True
  716. environ, conf_args = self.configure_environment()
  717. for arg in args:
  718. if arg: conf_args.append(arg)
  719. build_dir = self.ensure(self.build_directory())
  720. try:
  721. os.chdir(build_dir)
  722. need_to_config = not isfile('rules.ninja' if self.options.use_ninja else 'Makefile')
  723. if need_to_config:
  724. self.log.debug('Source => ' + self.source_dir)
  725. self.log.debug('Build => ' + build_dir)
  726. self.log.debug('configure env => ' + str(environ))
  727. self.log.debug('configure args => ' + str(conf_args))
  728. configure = Popen(conf_args, shell=False, env=environ)
  729. retcode = configure.wait()
  730. if retcode < 0:
  731. self.log.error("Child was terminated by signal", -retcode)
  732. sys.exit(1)
  733. elif retcode != 0:
  734. self.log.error("Execution failed: " + ' '.join(conf_args))
  735. sys.exit(1)
  736. else:
  737. self.log.debug('configure does not need to be run')
  738. finally:
  739. os.chdir(self.source_dir)
  740. def phase_config(self, *args):
  741. self.log.info('Executing phase: config')
  742. self.phase_configure(*args)
  743. if self.should_clean:
  744. self.phase_clean()
  745. #########################################################################
  746. # Builds products from the sources #
  747. #########################################################################
  748. def phase_make(self, *args):
  749. self.log.info('Executing phase: make')
  750. config_args = []
  751. make_args = []
  752. for arg in args:
  753. if arg.startswith('--') or arg.startswith('-D'):
  754. config_args.append(arg)
  755. else:
  756. make_args.append(arg)
  757. if self.options.jobs > 1 and self.current_flavor != 'gcov':
  758. make_args.append('-j%d' % self.options.jobs)
  759. if self.options.verbose:
  760. make_args.append('-v' if self.options.use_ninja else 'VERBOSE=1')
  761. self.log.debug('Configure arguments => ' + str(config_args))
  762. self.log.debug('Makefile arguments => ' + str(make_args))
  763. if not self.configured:
  764. self.phase_config(*config_args)
  765. build_dir = self.ensure(self.build_directory())
  766. try:
  767. self.log.debug('Changing directory to ' + build_dir)
  768. os.chdir(build_dir)
  769. self.execute(*(['ninja' if self.options.use_ninja else 'make'] +
  770. make_args))
  771. finally:
  772. os.chdir(self.source_dir)
  773. def phase_check(self, *args):
  774. self.log.info('Executing phase: check')
  775. build_dir = self.ensure(self.build_directory())
  776. try:
  777. self.log.debug('Changing directory to ' + build_dir)
  778. os.chdir(build_dir)
  779. make_args = list(args)
  780. if self.options.jobs > 1:
  781. make_args.append('-j%d' % self.options.jobs)
  782. self.execute(*(['ctest'] + list(make_args)))
  783. finally:
  784. os.chdir(self.source_dir)
  785. def phase_update(self, *args):
  786. self.log.info('Executing phase: update')
  787. self.phase_pull()
  788. self.phase_make(*args)
  789. #########################################################################
  790. # Build directory cleaning phases #
  791. #########################################################################
  792. def phase_clean(self, *args):
  793. self.log.info('Executing phase: clean')
  794. self.phase_make('clean')
  795. def phase_gitclean(self, *args):
  796. self.log.info('Executing phase: gitclean')
  797. if self.git_working_tree():
  798. self.execute('git', 'clean', '-dfx')
  799. #########################################################################
  800. # Other build phases #
  801. #########################################################################
  802. def configure_flavor(self, flavor, reset=True):
  803. self.initialize() # reset everything
  804. self.current_flavor = flavor
  805. self.options.build_dir = None # use the build/ tree
  806. self.options.prefix_dir = None
  807. if reset and exists(self.build_directory()) and \
  808. isdir(self.build_directory()):
  809. self.log.info('=== Wiping build directory %s ===' %
  810. self.build_directory())
  811. try:
  812. shutil.rmtree(self.build_directory())
  813. except:
  814. self.execute('chmod', '-R', 'u+w', self.build_directory())
  815. self.execute('rm', '-fr', self.build_directory())
  816. def phase_rsync(self, *args):
  817. self.log.info('Executing phase: rsync')
  818. proof_dir = 'ledger-proof'
  819. if self.options.python:
  820. proof_dir += "-python"
  821. if self.options.compiler:
  822. proof_dir += "-" + basename(self.options.compiler)
  823. source_copy_dir = join(self.ensure(self.products_directory()), proof_dir)
  824. self.execute('rsync', '-a', '--delete', '--exclude=/dist/',
  825. '--exclude=.git/', '--exclude=b/',
  826. '--exclude=/lib/boost-release/',
  827. '--exclude=/archive/', '--exclude=/build/',
  828. '%s/' % self.source_dir, '%s/' % source_copy_dir)
  829. self.source_dir = source_copy_dir
  830. def phase_proof(self, *args):
  831. self.log.info('Executing phase: proof')
  832. self.log.info('=== Copying source tree ===')
  833. self.phase_rsync()
  834. self.phase_makeall(reset=True, *args)
  835. self.configure_flavor('opt', reset=False)
  836. self.log.info('=== Testing opt ===')
  837. # jww (2012-05-20): Can't use fullcheck yet
  838. #self.phase_make('fullcheck')
  839. self.phase_make('test')
  840. self.configure_flavor('gcov', reset=False)
  841. self.log.info('=== Testing gcov ===')
  842. #self.phase_make('check')
  843. self.phase_make('test')
  844. self.configure_flavor('default', reset=False)
  845. self.log.info('=== Testing default ===')
  846. #self.phase_make('fullcheck')
  847. self.phase_make('test')
  848. # jww (2012-05-20): docs are not working yet
  849. #self.phase_make('docs')
  850. self.configure_flavor('debug', reset=False)
  851. self.log.info('=== Testing debug ===')
  852. #self.phase_make('fullcheck')
  853. self.phase_make('test')
  854. def phase_makeall(self, reset=False, *args):
  855. self.log.info('Executing phase: makeall')
  856. self.configure_flavor('opt', reset)
  857. self.log.info('=== Building opt ===')
  858. self.phase_make(*args)
  859. self.configure_flavor('gcov', reset)
  860. self.log.info('=== Building gcov ===')
  861. self.phase_make(*args)
  862. self.configure_flavor('default', reset)
  863. self.log.info('=== Building default ===')
  864. self.phase_make(*args)
  865. self.configure_flavor('debug', reset)
  866. self.log.info('=== Building debug ===')
  867. self.phase_make(*args)
  868. #########################################################################
  869. # Help #
  870. #########################################################################
  871. def phase_help(self, *args):
  872. self.option_parser.print_help()
  873. print("""
  874. Of the optional ARGS, the first is an optional build FLAVOR, with the default
  875. being 'debug':
  876. default Regular autoconf settings
  877. debug Debugging and --verify support (default)
  878. opt Full optimizations
  879. gcov Coverage analysis
  880. gprof Code profiling (for OS X, just use: 'shark -i ledger ...')
  881. Next is the optional build PHASE, with 'config' being the default:
  882. clean Runs 'make clean' in the build directory
  883. config Configure the environment for building
  884. dependencies Automatically install all necessary build dependencies
  885. gitclean Runs 'git clean -dfx', which *really* cleans things
  886. help Displays this help text
  887. info Show information about the build environment
  888. make Do a make in the build directory
  889. proof Proves Ledger by building and testing every flavor
  890. pull Pulls the latest, and updates local config if need be
  891. update Does it all, updates your environment and re-make's
  892. There are many other build phases, though most are not of interest to the
  893. typical user:
  894. configure Runs just cmake
  895. do_all Runs makeall followed by proof
  896. gettext Initialize gettext support
  897. makeall Build every flavor there is
  898. products Report the products directory path
  899. rsync Rsync a copy of the source tree into Products
  900. sloc Report total Source Lines Of Code
  901. version Output current HEAD version to version.m4
  902. NOTE: If you wish to pass options to CMake or make, add "--" followed by
  903. your options. Those starting with "-D" or "--" will be passed on to CMake,
  904. positional arguments and other options will be passed to make.
  905. For the 'config' and 'configure' phase everything will be passed to CMake.
  906. Here are some real-world examples:
  907. ./acprep
  908. ./acprep --python
  909. ./acprep opt make
  910. ./acprep make doc -- -DBUILD_WEB_DOCS=1""")
  911. sys.exit(0)
  912. PrepareBuild().run()