00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 def _basis_init_sys_path():
00020 import os
00021 import sys
00022 module_dir = os.path.dirname(os.path.realpath(__file__))
00023 sitelib_dir = os.path.normpath(os.path.join(module_dir, '../lib/python'))
00024 if sitelib_dir not in sys.path:
00025 sys.path.insert(0, sitelib_dir)
00026 sitelib_dir = os.path.normpath(os.path.join(module_dir, '../lib/python'))
00027 if sitelib_dir not in sys.path:
00028 sys.path.insert(0, sitelib_dir)
00029
00030 _basis_init_sys_path()
00031 from sbia.basis import basis
00032
00033
00034 import os
00035 import sys
00036 import re
00037 import datetime
00038 import shutil
00039 from sbia.basis import argparse
00040 from sbia.basis import diff3
00041
00042
00043
00044
00045
00046
00047 _EXEC_DIR = basis.get_executable_directory()
00048 _EXEC_NAME = basis.get_executable_name()
00049
00050
00051 DEL = -1
00052 ADD = 1
00053
00054
00055 _template = os.path.abspath(os.path.join(_EXEC_DIR, '../share/template'))
00056
00057
00058
00059
00060
00061
00062 def get_template_options(op=None):
00063 return {'config-settings' : op,
00064 'config-components' : op,
00065 'config-package' : op,
00066 'config-find' : op,
00067 'config-find_version' : op,
00068 'config-script' : op,
00069 'config-test' : op,
00070 'config-use' : op,
00071 'data' : op,
00072 'doc' : op,
00073 'example' : op,
00074 'include' : op,
00075 'src' : op,
00076 'modules' : op,
00077 'test' : op}
00078
00079
00080 def get_standard_template_options(opts, overwrite=False):
00081 """Select standard project template."""
00082 if overwrite or opts.get('config-settings') == None: opts['config-settings'] = DEL
00083 if overwrite or opts.get('config-components') == None: opts['config-components'] = DEL
00084 if overwrite or opts.get('config-package') == None: opts['config-package'] = DEL
00085 if overwrite or opts.get('config-find') == None: opts['config-find'] = DEL
00086 if overwrite or opts.get('config-find_version') == None: opts['config-find_version'] = DEL
00087 if overwrite or opts.get('config-script') == None: opts['config-script'] = DEL
00088 if overwrite or opts.get('config-test') == None: opts['config-test'] = DEL
00089 if overwrite or opts.get('config-use') == None: opts['config-use'] = DEL
00090 if overwrite or opts.get('data') == None: opts['data'] = DEL
00091 if overwrite or opts.get('doc') == None: opts['doc'] = ADD
00092 if overwrite or opts.get('example') == None: opts['example'] = ADD
00093 if overwrite or opts.get('include') == None: opts['include'] = DEL
00094 if overwrite or opts.get('test') == None: opts['test'] = ADD
00095 if (overwrite
00096 or (opts.get('src') == None and opts.get('modules') == None)
00097 or (opts.get('src') == None and opts.get('modules') == DEL)
00098 or (opts.get('src') == DEL and opts.get('modules') == None)):
00099 if overwrite or opts.get('src') != DEL:
00100 opts['src'] = ADD
00101 opts['modules'] = DEL
00102 else:
00103 opts['src'] = DEL
00104 opts['modules'] = ADD
00105 return opts
00106
00107
00108 def _update_dflt_opt(root, opts, opt, paths):
00109 if not type(paths) is list:
00110 paths = [paths]
00111 for path in paths:
00112 if opt not in opts:
00113 opts[opt] = None
00114 if opts[opt] == None and os.path.exists(os.path.join(root, path)):
00115 opts[opt] = ADD
00116 return opts
00117
00118
00119 def get_default_template_options(root, opts, create):
00120 """Select default options."""
00121 if create:
00122 opts = get_standard_template_options(opts, overwrite=False)
00123 else:
00124 if os.path.isdir(os.path.join(root, 'config')):
00125 opts = _update_dflt_opt(root, opts, 'config-components', 'config/Components.cmake')
00126 opts = _update_dflt_opt(root, opts, 'config-find', 'config/Config.cmake.in')
00127 opts = _update_dflt_opt(root, opts, 'config-find_version', 'config/ConfigVersion.cmake.in')
00128 opts = _update_dflt_opt(root, opts, 'config-test', 'config/CTestCustom.cmake.in')
00129 opts = _update_dflt_opt(root, opts, 'config-package', 'config/Package.cmake')
00130 opts = _update_dflt_opt(root, opts, 'config-script', 'config/ScriptConfig.cmake.in')
00131 opts = _update_dflt_opt(root, opts, 'config-settings', 'config/Settings.cmake')
00132 opts = _update_dflt_opt(root, opts, 'config-use', 'config/ConfigUse.cmake.in')
00133 opts = _update_dflt_opt(root, opts, 'data', 'data')
00134 opts = _update_dflt_opt(root, opts, 'doc', 'doc')
00135 opts = _update_dflt_opt(root, opts, 'example', 'example')
00136 opts = _update_dflt_opt(root, opts, 'include', 'include')
00137 opts = _update_dflt_opt(root, opts, 'src', 'src')
00138 opts = _update_dflt_opt(root, opts, 'modules', 'modules')
00139 opts = _update_dflt_opt(root, opts, 'test', 'test')
00140 return opts
00141
00142
00143 def get_project_name(cmake_file):
00144 """Extract project name from BasisProject.cmake file of existing project."""
00145 fp = open(cmake_file, 'rt')
00146 txt = fp.read()
00147 fp.close()
00148 m = re.search(r'\s*basis_project\s*\(\s*NAME\s+\"?(?P<name>\w+)\"?', txt)
00149 if m: return m.group('name')
00150 return None
00151
00152
00153 def get_template_version(cmake_file):
00154 """Extract template version from root CMakeLists.cmake file of existing project."""
00155 fp = open(cmake_file, 'rt')
00156 txt = fp.read()
00157 fp.close()
00158 m = re.search(r'\s*set\s*\(\s*TEMPLATE_VERSION\s+\"?(?P<version>[0-9.]+)\"?', txt)
00159 if m: return m.group('version')
00160 return None
00161
00162
00163
00164
00165
00166
00167 def add_dependencies(txt, deps, required=True, test=False):
00168 """Add dependencies to basis_project() command in BasisProject.cmake file."""
00169 if test:
00170 if required:
00171 placeholder = '#<test-dependency>'
00172 else:
00173 placeholder = '#<optional-test-dependency>'
00174 else:
00175 if required:
00176 placeholder = '#<dependency>'
00177 else:
00178 placeholder = '#<optional-dependency>'
00179 for dep in deps:
00180 txt = txt.replace(placeholder, dep + '\n ' + placeholder, 1)
00181 return txt
00182
00183
00184 def alter(filename, args, backup=True, update=False):
00185 """Alter project file, i.e., substitute placeholders.
00186
00187 @param [in] filename Path of text file to alter.
00188 @param [in] args Command-line arguments.
00189 @param [in] backup Whether to backup file before overwriting it.
00190 @param [in] update Whether template file is generated for the update
00191 of an existing project file. In this case, certain
00192 modifications should not be applied to avoid conflicts.
00193
00194 @retval -1 on error
00195 @retval 0 if file was not modified
00196 @retval 1 if file has been modified
00197
00198 """
00199
00200 fp = open(filename, 'rt')
00201 txt = fp.read()
00202 fp.close()
00203
00204 out = txt
00205 out = out.replace('<template-version>', '1.3')
00206
00207 if hasattr(args, 'name') and args.name:
00208 out = out.replace('<project>', args.name)
00209
00210 if hasattr(args, 'description') and args.description:
00211 out = out.replace('<description>', args.description)
00212
00213 if hasattr(args, 'author') and args.author:
00214 start = out.find('<author>')
00215 if start != -1:
00216 linestart = out.rfind('\n', 0, start)
00217 if linestart == -1: linestart = 0
00218 else: linestart += 1
00219 authors = [a.strip() for a in args.author.split(',')]
00220 authors = ('\n' + out[linestart:start]).join(authors)
00221 out = out.replace('<author>', authors)
00222
00223 year = None
00224 if hasattr(args, 'year') and args.year:
00225 year = args.year
00226 elif not update:
00227 year = datetime.datetime.now().year
00228 if year:
00229 out = out.replace('<year>', str(year))
00230
00231 if filename.endswith('BasisProject.cmake'):
00232 if hasattr(args, 'use') and args.use:
00233 out = add_dependencies(out, args.use, required=True, test=False)
00234 if hasattr(args, 'useopt') and args.useopt:
00235 out = add_dependencies(out, args.useopt, required=False, test=False)
00236 if hasattr(args, 'usetest') and args.usetest:
00237 out = add_dependencies(out, args.usetest, required=True, test=True)
00238 if hasattr(args, 'useopttest') and args.useopttest:
00239 out = add_dependencies(out, args.useopttest, required=False, test=True)
00240
00241 if out == txt: return 0
00242
00243 if backup:
00244 try:
00245 shutil.copy(filename, filename + '~')
00246 except Exception, e:
00247 sys.stderr.write("E %s - failed to backup file before altering it" % filename)
00248 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00249 sys.stderr.write('\n')
00250 return -1
00251
00252 fp = open(filename, 'wt')
00253 fp.write(out)
00254 fp.close()
00255 return 1
00256
00257
00258 def add(path, args, isdir=False):
00259 """Add or modify project directory or file.
00260
00261 @param [in] path The path of the directory or file relative to the
00262 template or project root, respectively.
00263 @param [in] args Namespace with command-line arguments.
00264 @param [in] isdir Whether @p path is a directory that may not have
00265 a corresponding template.
00266
00267 @retval -1 on error
00268 @retval 0 if existing file is up to date
00269 @retval 1 if project file has been added/updated
00270 @retval 2 if project file has been updated, but with conflicts
00271
00272 """
00273 retval = 0
00274
00275 template_path = os.path.join(args.template, path)
00276 original_template_path = os.path.join(args.original_template, path)
00277 project_path = os.path.join(args.root, path)
00278
00279
00280 if not isdir and not os.path.exists(template_path):
00281 sys.stderr.write("E %s - template missing\n" % project_path)
00282 return -1
00283
00284
00285 if os.path.exists(project_path):
00286 if isdir or os.path.isdir(template_path):
00287 if not os.path.isdir(project_path):
00288
00289 sys.stderr.write("E %s - not a directory\n" % project_path)
00290 return -1
00291 else:
00292
00293 return 0
00294 elif os.path.isfile(template_path):
00295 if not os.path.isfile(project_path):
00296
00297 sys.stderr.write("E %s - not a file\n" % project_path)
00298 return -1
00299
00300
00301 if not args.update:
00302 rv = alter(project_path, args, backup=args.backup)
00303 if rv == 1: print "M %s" % project_path
00304 return rv
00305
00306
00307 if isdir or os.path.isdir(template_path):
00308 path_dir = path
00309 else:
00310 path_dir = os.path.dirname(path)
00311 project_dir = os.path.join(args.root, path_dir)
00312 if not os.path.isdir(project_dir):
00313 try:
00314 os.makedirs(project_dir)
00315 print "A %s" % project_dir
00316 except Exception, e:
00317 sys.stderr.write("E %s - failed to make directory" % project_dir)
00318 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00319 sys.stderr.write('\n')
00320 return -1
00321
00322
00323 if os.path.isfile(template_path):
00324
00325 if not os.path.isfile(project_path):
00326
00327 try:
00328 shutil.copyfile(template_path, project_path)
00329 except Exception, e:
00330 sys.stderr.write("E %s - failed to add file" % project_path)
00331 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00332 sys.stderr.write('\n')
00333 return -1
00334
00335 try:
00336 if alter(project_path, args, backup=False) == -1:
00337 retval = -1
00338 except Exception, e:
00339 retval = -1
00340 if retval != -1:
00341 print "A %s" % project_path
00342 retval = 1
00343 else:
00344 sys.stderr.write("E %s - failed to alter file, may require manual edits" % project_path)
00345 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00346 sys.stderr.write('\n')
00347
00348 else:
00349
00350 if os.path.isfile(original_template_path):
00351
00352 try:
00353 shutil.copyfile(template_path, project_path + '.template')
00354 if alter(project_path + '.template', args, backup=False, update=True) == -1:
00355 raise Exception('failed to alter template file')
00356 except Exception, e:
00357 sys.stderr.write("E %s - failed to create temporary template" % project_path)
00358 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00359 sys.stderr.write('\n')
00360 return -1
00361
00362 fp = open(project_path, 'rt')
00363 current = fp.readlines()
00364 fp.close()
00365 fp = open(project_path + '.template', 'rt')
00366 template = fp.readlines()
00367 fp.close()
00368 fp = open(original_template_path, 'rt')
00369 initial = fp.readlines()
00370 fp.close()
00371 merge = diff3.merge(current, initial, template)
00372
00373 if merge['body'] != current:
00374
00375 if merge['conflict']:
00376 try:
00377 shutil.copy(project_path, project_path + '.mine')
00378 except Exception, e:
00379 sys.stderr.write("E %s - failed to backup file" % project_path)
00380 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00381 sys.stderr.write('\n')
00382 return -1
00383
00384 elif args.backup:
00385 try:
00386 shutil.copyfile(project_path, project_path + '~')
00387 except Exception, e:
00388 sys.stderr.write("E %s - failed to backup file" % project_path)
00389 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00390 sys.stderr.write('\n')
00391 return -1
00392
00393 try:
00394 fp = open(project_path, 'wt')
00395 fp.writelines(merge['body'])
00396 fp.close()
00397 except Exception, e:
00398 sys.stderr.write("E %s - failed to update file" % project_path)
00399 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00400 sys.stderr.write('\n')
00401 return -1
00402 if retval != -1:
00403 if merge['conflict']: retval = 2
00404 else: retval = 1
00405
00406 if not merge['conflict']:
00407 try:
00408 os.remove(project_path + '.template')
00409 except Exception, e:
00410 sys.stderr.write("E %s - failed to remove temporary file" % project_path)
00411 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00412 sys.stderr.write('\n')
00413 retval = -1
00414
00415 else:
00416 try:
00417
00418
00419 shutil.copyfile(template_path, project_path + '.template')
00420 except:
00421 pass
00422 sys.stderr.write("S %s - cannot update file: missing original template\n" % project_path)
00423 retval = -1
00424
00425 if retval > 0:
00426 if merge['conflict']: print "C %s" % project_path
00427 else: print "G %s" % project_path
00428
00429 return retval
00430
00431
00432 def remove_directory(path):
00433 isempty = True
00434 for filename in os.listdir(path):
00435 if filename != '.svn' and filename != '.git' and filename != '.hg':
00436 isempty = False
00437 break
00438 if isempty:
00439 shutil.rmtree(path)
00440 return True
00441 return False
00442
00443
00444 def delete(path, args, isdir=False):
00445 """Delete file or empty directory.
00446
00447 @param [in] path Path relative to template or project root, respectively.
00448 @param [in] args Namespace with command-line arguments.
00449 @param [in] isdir Whether @p path is a directory path that may not have
00450 a corresponding template.
00451
00452 @retval -1 on error
00453 @retval 0 if file/directory did not exist
00454 @retval 1 if file/directory was removed
00455
00456 """
00457 retval = 0
00458
00459 original_template_path = os.path.join(args.original_template, path)
00460 project_path = os.path.join(args.root, path)
00461
00462
00463 if os.path.isdir(project_path):
00464 if args.force:
00465 try:
00466 shutil.rmtree(project_path)
00467 except Exception, e:
00468 sys.stderr.write("E %s - failed to remove directory" % project_path)
00469 if verbose > 0: sys.stderr.write(': ' + str(e))
00470 sys.stderr.write('\n')
00471 retval = -1
00472 else:
00473 try:
00474 os.rmdir(project_path)
00475 except Exception, e:
00476 sys.stderr.write("E %s - failed to remove directory;"
00477 " use --force to force deletion of non-empty directory" % project_path)
00478 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00479 sys.stderr.write('\n')
00480 retval = -1
00481 if retval != -1:
00482 print "D %s" % project_path
00483 retval = 1
00484
00485 elif os.path.isfile(project_path):
00486
00487 if not args.force:
00488 if not os.path.isfile(original_template_path):
00489 sys.stderr.write("E %s - original template missing, use --force to force deletion\n" % project_path)
00490 return -1
00491
00492 try:
00493 shutil.copyfile(original_template_path, project_path + '.template')
00494 if alter(project_path + '.template', args, backup=False) == -1:
00495 raise Exception('failed to alter template file')
00496 except Exception, e:
00497 sys.stderr.write("E %s - failed to create temporary template,"
00498 " use --force to force deletion")
00499 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00500 sys.stderr.write('\n')
00501 return -1
00502
00503 fp = open(project_path, 'rt')
00504 current = fp.read()
00505 fp.close()
00506 fp = open(project_path + '.template', 'rt')
00507 template = fp.read()
00508 fp.close()
00509
00510 try:
00511 os.remove(project_path + '.template')
00512 except Exception, e:
00513 sys.stderr.write("E %s - failed to remove temporary file" % project_path)
00514 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00515 sys.stderr.write('\n')
00516 retval = -1
00517
00518 if current != template:
00519 if retval != -1:
00520 sys.stderr.write("S %s - file was modified, use --force to force deletion\n" % project_path)
00521 return -1
00522
00523 try:
00524 os.remove(project_path)
00525 except Exception, e:
00526 sys.stderr.write("E %s - failed to remove file" % project_path)
00527 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00528 sys.stderr.write('\n')
00529 return -1
00530
00531 if retval != -1:
00532 print "D %s" % project_path
00533 retval = 1
00534
00535 subdir = os.path.dirname(project_path)
00536 while subdir != args.root:
00537 try:
00538 if remove_directory(subdir):
00539 print "D %s" % subdir
00540 except Exception, e:
00541 sys.stderr.write("W %s - failed to remove directory" % subdir)
00542 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00543 sys.stderr.write('\n')
00544 retval = -1
00545 break
00546 subdir = os.path.dirname(subdir)
00547
00548 return retval
00549
00550
00551 def addordel(op, path, args, isdir=False):
00552 """Add/update or delete file or directory depending on operation specified.
00553
00554 @param [in] op Operations. Either one of ADD, None, or DEL.
00555 @param [in] path File path relative to tempate or project root, respectively.
00556 @param [in] args Namespace with command-line arguments.
00557 @param [in] isdir Whether @p path is a directory path that may not have
00558 a corresponding template.
00559
00560 @retval -1 on error
00561 @retval 0 if nothing was done
00562 @retval 1 if operation was successful
00563
00564 """
00565
00566 if op == ADD:
00567 return add(path, args, isdir=isdir)
00568
00569 elif op == DEL:
00570 return delete(path, args, isdir=isdir)
00571
00572 return 0
00573
00574
00575 class AccumulatorForNumbersOfChangesAndErrors(object):
00576 """Helper class used to accumulate number of changes, conflicts, and
00577 errors when calling either one of the add(), delete(), or addordel()
00578 functions."""
00579
00580 changes = 0
00581 conflicts = 0
00582 errors = 0
00583
00584 def call(self, func, *args, **kwargs):
00585 """Call the specified function with the given arguments."""
00586 rt = func(*args, **kwargs)
00587 if rt == -1: self.errors += 1
00588 else:
00589 if rt > 0: self.changes += 1
00590 if rt == 2: self.conflicts += 1
00591
00592
00593
00594
00595
00596
00597 if __name__ == '__main__':
00598 ok = True
00599
00600
00601
00602 parser = argparse.ArgumentParser(prog='basisproject', description="""
00603 This command-line tool, also referred to as project tool, can be used to
00604 create a new project from the BASIS project template version 1.3 or to modify a
00605 previously created BASIS project.
00606
00607 Depending on the grade of customization or optional inclusion of template
00608 components, different subsets of the fully featured project template can be
00609 selected. Additional template files and directories can be added to an existing
00610 project at any time. Further, if the --no* options are given explicitly,
00611 project files which were previously copied from the template are deleted.
00612 Files are, however, only deleted if they were not modified by the project
00613 developer since their creation and hence do not contain project related changes.
00614 Similarly are directories deleted by this tool only if empty. The deletion of
00615 modified files can be forced by supplying the --force option.
00616
00617 Besides the name of the new project and a brief description, names of external
00618 packages required or optionally used by this project can be specified. For each
00619 such package, an entry in the list of dependencies given as argument to either
00620 one of the DEPENDS* options of the basis_project() command is added.
00621
00622 An additional feature of this tool is, that it can upgrade an existing project
00623 to a newer project template version, given that the existing directory structure
00624 and file names were preserved. User changes to previously added template files
00625 are preserved and merged with the changes of the template using a so-called
00626 three-way diff similar to the Subversion tool svn. If the automatic file merge
00627 is not successful, a copy of the original project file (*.mine) as well as the
00628 new template file (*.template) are written to the project directory next to the
00629 project file which has been overwritten with the merged content which includes
00630 markers to indicate where the conflicts occurred. The project file has to be
00631 edited manually in this case to resolve any conflicts. Once the conflicts have
00632 been resolved, the *.mine and *.template files must be removed before this
00633 tool can be used for another update of the project files. This can be done
00634 either manually or by running this program with the --cleanup option.""",
00635 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
00636
00637
00638
00639
00640 parser.set_defaults(opts=get_template_options())
00641
00642
00643 parser.add_argument('--version', action='version',
00644 help="Show version information and exit.",
00645 version="""%(prog)s (BASIS) version 1.2.3 (revision 2104)
00646 Copyright (c) 2011, 2012 University of Pennsylvania. All rights reserved.
00647 See https://www.cbica.upenn.edu/sbia/software/license.html or COPYING file.""")
00648
00649
00650 parser.add_argument('-v', '--verbose', action='count', default=0,
00651 help="Increase verbosity of output messages.")
00652
00653
00654 parser.add_argument('--name', metavar='<name>',
00655 help="Name of new project.")
00656
00657 parser.add_argument('--description', metavar='<text>',
00658 help="Brief project description.")
00659
00660 parser.add_argument('--author', metavar='<author>',
00661 help="Name of original author of the software.")
00662
00663
00664 parser.add_argument('--root', metavar='<dir>', type=os.path.abspath,
00665 help="""Project root directory. Defaults to a subdirectory named after the
00666 project created in the current working directory. In order to update
00667 an existing project, specify the root directory of this project
00668 using this option.""")
00669
00670 parser.add_argument('--template', metavar='<dir>',
00671 type=os.path.abspath, default=_template + '-1.3',
00672 help="""Root directory of project template. Defaults to the template
00673 corresponding to this BASIS installation.""")
00674
00675 parser.add_argument('--original', metavar='<dir>',
00676 type=os.path.abspath, dest='original_template',
00677 help="""Root directory of project template which the already existing project
00678 was created from or last updated to. By default, the version of the original template
00679 is extracted from the root CMakeLists.txt file of the project. Given this version,
00680 this tool determines the path of the corresponding project template which is part of
00681 the BASIS installation. If this previous template is not available, an automatic
00682 update of project files to a newer template version is not feasible.""")
00683
00684
00685 parser.add_argument('--use', metavar='<pkg>', action='append', default=[],
00686 help="""Name of external package used by this project.
00687 Note that the package name is case sensitive.""")
00688
00689 parser.add_argument('--useopt', metavar='<pkg>', action='append', default=[],
00690 help="""Name of external package optionally used by this project.
00691 Note that the package name is case sensitive.""")
00692
00693
00694 parser.add_argument('--usetest', metavar='<pkg>', action='append', default=[],
00695 help="""Name of external package required by tests of this project.
00696 Note that the package name is case sensitive.""")
00697
00698 parser.add_argument('--useopttest', metavar='<pkg>', action='append', default=[],
00699 help="""Name of external package optionally used by tests of this project.
00700 Note that the package name is case sensitive.""")
00701
00702
00703 class MinimalTemplateAction(argparse.Action):
00704 def __call__(self, parser, namespace, value, option_string):
00705 setattr(namespace, 'opts', get_template_options(DEL))
00706
00707 parser.add_argument('--minimal', nargs=0, action=MinimalTemplateAction,
00708 help="""Choose minimal project template. Corresponds to not
00709 selecting any of the additional template files.""")
00710
00711
00712 class StandardTemplateAction(argparse.Action):
00713 def __call__(self, parser, namespace, value, option_string):
00714 setattr(namespace, 'opts', get_standard_template_options({}))
00715
00716 parser.add_argument('--standard', nargs=0, action=StandardTemplateAction,
00717 help="""Choose standard project template. This is the default
00718 project template if no template was selected explicitly.
00719 Corresponds to:
00720 --doc
00721 --example
00722 --noconfig
00723 --nodata
00724 --test""")
00725
00726
00727 class FullTemplateAction(argparse.Action):
00728 def __call__(self, parser, namespace, value, option_string):
00729 setattr(namespace, 'opts', get_template_options(ADD))
00730
00731 parser.add_argument('--full', nargs=0, action=FullTemplateAction,
00732 help="""Choose full-featured project template.""")
00733
00734
00735 class TopLevelTemplateAction(argparse.Action):
00736 def __call__(self, parser, namespace, value, option_string):
00737 opts = get_standard_template_options({})
00738 opts['modules'] = ADD
00739 opts['src'] = DEL
00740 opts['test'] = DEL
00741 setattr(namespace, 'opts', opts)
00742
00743 parser.add_argument('--toplevel', nargs=0, action=TopLevelTemplateAction,
00744 help="""Choose top-level project template.""")
00745
00746
00747 class ModuleTemplateAction(argparse.Action):
00748 def __call__(self, parser, namespace, value, option_string):
00749 opts = get_template_options(DEL)
00750 opts['include'] = ADD
00751 opts['src'] = ADD
00752 opts['test'] = ADD
00753 setattr(namespace, 'opts', opts)
00754
00755 parser.add_argument('--module', nargs=0, action=ModuleTemplateAction,
00756 help="""Choose project module template.""")
00757
00758
00759 class ConfigAction(argparse.Action):
00760 def __call__(self, parser, namespace, values, option_string):
00761 if option_string == '--config': opts = self._get_options(ADD)
00762 else: opts = self._get_options(DEL)
00763 setattr(namespace, 'opts', opts)
00764
00765 def _get_options(self, op):
00766 return {'config-settings': op,
00767 'config-components': op,
00768 'config-package': op,
00769 'config-find': op,
00770 'config-find_version': op,
00771 'config-script': op,
00772 'config-test': op,
00773 'config-use': op}
00774
00775 parser.add_argument('--config', '--noconfig',
00776 nargs=0, action=ConfigAction,
00777 help="""Include/exclude all custom configuration files.""")
00778
00779
00780 class OptAction(argparse.Action):
00781 def __call__(self, parser, namespace, values, option_string):
00782 if not hasattr(namespace, 'opts'):
00783 setattr(namespace, 'opts', {})
00784 if option_string.startswith('--no'):
00785 op = DEL
00786 opt = option_string[4:]
00787 else:
00788 op = ADD
00789 opt = option_string[2:]
00790 namespace.opts[opt] = op
00791
00792 parser.add_argument('--config-components', '--noconfig-components',
00793 nargs=0, action=OptAction,
00794 help="Include/exclude custom Components.cmake file.")
00795
00796 parser.add_argument('--config-find', '--noconfig-find',
00797 nargs=0, action=OptAction,
00798 help="Include/exclude custom Config.cmake.in file.")
00799
00800 parser.add_argument('--config-find-version', '--noconfig-find-version',
00801 nargs=0, action=OptAction,
00802 help="Include/exclude custom ConfigVersion.cmake.in file.")
00803
00804 parser.add_argument('--config-package', '--noconfig-package',
00805 nargs=0, action=OptAction,
00806 help="Include/exclude custom Package.cmake file.")
00807
00808 parser.add_argument('--config-script', '--noconfig-script',
00809 nargs=0, action=OptAction,
00810 help="Include/exclude custom ScriptConfig.cmake.in file.")
00811
00812 parser.add_argument('--config-settings', '--noconfig-settings',
00813 nargs=0, action=OptAction,
00814 help="Include/exclude custom Settings.cmake file.")
00815
00816 parser.add_argument('--config-test', '--noconfig-test',
00817 nargs=0, action=OptAction,
00818 help="Include/exclude custom CTestCustom.cmake.in file.")
00819
00820 parser.add_argument('--config-use', '--noconfig-use',
00821 nargs=0, action=OptAction,
00822 help="Include/exclude custom ConfigUse.cmake.in file.")
00823
00824 parser.add_argument('--data', '--nodata',
00825 nargs=0, action=OptAction,
00826 help="Add/remove directory for auxiliary data files.")
00827
00828 parser.add_argument('--doc', '--nodoc',
00829 nargs=0, action=OptAction,
00830 help="Add/remove directory for documentation files.")
00831
00832 parser.add_argument('--example', '--noexample',
00833 nargs=0, action=OptAction,
00834 help="Add/remove directory for example files.")
00835
00836 parser.add_argument('--include', '--noinclude',
00837 nargs=0, action=OptAction,
00838 help="Add/remove directory for public header files.")
00839
00840 parser.add_argument('--modules', '--nomodules',
00841 nargs=0, action=OptAction,
00842 help="Add/remove support for modularization.")
00843
00844 parser.add_argument('--src', '--nosrc',
00845 nargs=0, action=OptAction,
00846 help="Add/remove directory for project source files.")
00847
00848 parser.add_argument('--test', '--notest',
00849 nargs=0, action=OptAction,
00850 help="Add/remove support for testing.")
00851
00852
00853 parser.add_argument('-u', '--update', action='store_true', default=False,
00854 help="""Enable update of existing project files. If this option is given,
00855 changes of the project file are merged with those of the
00856 corresponding template file if the template has been modified,
00857 for example, as part of a new release of BASIS.""")
00858
00859
00860 parser.add_argument('-c', '--cleanup', action='store_true', default=False,
00861 help="""Remove files resulting from merge conflicts of a previous update
00862 of existing project files and backups. The conflicts should have
00863 been manually resolved before using this option.""")
00864
00865
00866 parser.add_argument('-f', '--force', action='store_true', default=False,
00867 help="""Enable removal of non-empty directories and modified project files.
00868 By default, only empty directories and project files which have not
00869 been edited since their creation are removed.""")
00870
00871
00872 parser.add_argument('--nobackup', action='store_false', default=True, dest='backup',
00873 help="""Disable backup of existing project files. By default, whenever an
00874 existing project file is modified, a backup of this file is made and saved under the
00875 same filename, but with the ~ character as suffix. If this option is given, such backup
00876 files are not made. In case of a merge conflict, however, existing project files are
00877 always backed-up, using .mine as suffix for the file name of the backup in this case.""")
00878
00879
00880
00881 if len(sys.argv) == 1:
00882 parser.print_help()
00883 sys.exit(1)
00884 else:
00885 args = parser.parse_args()
00886
00887
00888
00889
00890 if args.name:
00891 create = True
00892
00893 if not args.root:
00894 args.root = os.path.join(os.getcwd(), args.name)
00895
00896 if os.path.exists(args.root) and len(os.listdir(args.root)) != 0:
00897 sys.stderr.write("Directory " + args.root + " exists already and is not empty.\n")
00898 sys.stderr.write("Please choose another project name or a different root directory using the --root option.\n")
00899 sys.stderr.write('\n')
00900 sys.stderr.write("If you want to modify an existing project, please specify the root directory\n")
00901 sys.stderr.write("of the existing project using the --root option and remove the --name option.\n")
00902 sys.exit(1)
00903
00904 if not args.description:
00905 sys.stderr.write("No project description given!\n")
00906 sys.exit(1)
00907
00908 args.original_template = args.template
00909
00910 if not re.match(r'^[a-zA-Z0-9]+$', args.name):
00911 sys.stderr.write("Invalid project name: " + args.name + "\n")
00912 sys.stderr.write("Project name may only consist of alphanumeric characters!\n")
00913 sys.stderr.write("If you are attempting to modify an existent project, check whether the\n")
00914 sys.stderr.write("project name is correctly extracted from the root CMakeLists.txt file.\n")
00915 sys.exit(1)
00916
00917 else:
00918 create = False
00919
00920 if not args.root:
00921 args.root = os.getcwd()
00922
00923 if args.root[-1] == '/' or args.root[-1] == '\\':
00924 args.root = args.root[0:-1]
00925
00926 if not os.path.isdir(args.root):
00927 sys.stderr.write("Project directory " + args.root + " does not exist!\n")
00928 sys.stderr.write("If you want to create a new project, please specify a project name.\n")
00929 sys.exit(1)
00930
00931 if not os.path.isfile(os.path.join(args.root, 'BasisProject.cmake')):
00932 sys.stderr.write("Could not find a BasisProject.cmake file in " + args.root +
00933 "! Are you sure this is a BASIS project?\n")
00934 sys.exit(1)
00935
00936 if args.description:
00937 sys.stderr.write("Cannot modify description of existing project. Please edit file BasisProject.cmake.\n")
00938 sys.stderr.write("Do not use option --description when attempting to modify an existing project.\n")
00939 sys.exit(1)
00940
00941 if not args.name:
00942 args.name = get_project_name(os.path.join(args.root, 'BasisProject.cmake'))
00943 if not args.name:
00944 sys.stderr.write("Failed to determine project name!\n")
00945 sys.exit(1)
00946
00947 if not args.original_template:
00948 original_template_version = get_template_version(os.path.join(args.root, 'CMakeLists.txt'))
00949 if not original_template_version:
00950 sys.stderr.write("Failed to determine original template version!\n")
00951 sys.exit(1)
00952 args.original_template = _template + '-' + original_template_version
00953
00954
00955 if args.verbose > 1:
00956 print "Project: " + args.root
00957 print "Template: " + args.template
00958 print "Original: " + args.original_template
00959 print
00960
00961
00962
00963 if not hasattr(args, 'opts'):
00964 setattr(args, 'opts', {})
00965 opts = get_default_template_options(args.root, args.opts, create)
00966
00967
00968
00969 if not create:
00970 for root, dirs, files in os.walk(args.root):
00971 for filename in files:
00972 if (filename.endswith('.mine')
00973 or filename.endswith('.template')
00974 or filename.endswith('~')):
00975 if args.cleanup:
00976 file_path = os.path.join(root, filename)
00977 try:
00978 os.remove(file_path)
00979 print "D %s" % file_path
00980 except Exception, e:
00981 sys.stderr.write("E %s - failed to remove file" % file_path)
00982 if args.verbose > 0: sys.stderr.write(': ' + str(e))
00983 sys.stderr.write('\n')
00984 ok = False
00985 elif args.update and not filename.endswith('~'):
00986 sys.stderr.write(
00987 """Their occurred conflicts when updating the project files before.
00988
00989 Please resolve these conflicts first by editing the conflicted files, then remove
00990 the *.mine and *.template files or run this program with the --cleanup option.
00991 Once these conflicts have been resolved, try updating again.
00992
00993 Note that if you want to revert the previous udpate entirely, you should use
00994 the revert functionality of the revision control system that you are hopefully
00995 using to manage your project files. This should also revert the copies of the
00996 template files in the .basis/ subdirectory.
00997 """)
00998 sys.exit(1)
00999 if args.cleanup and not ok:
01000 sys.stderr.write("\nFailed to cleanup backup files and temporary files\n")
01001 sys.exit(1)
01002
01003
01004
01005 acc = AccumulatorForNumbersOfChangesAndErrors()
01006
01007 try:
01008
01009
01010
01011
01012 acc.call(add, 'AUTHORS.txt', args)
01013 acc.call(add, 'README.txt', args)
01014 acc.call(add, 'INSTALL.txt', args)
01015 acc.call(add, 'COPYING.txt', args)
01016 acc.call(add, 'BasisProject.cmake', args)
01017 acc.call(add, 'CMakeLists.txt', args)
01018
01019 acc.call(addordel, opts.get('config-settings', None), 'config/Settings.cmake', args)
01020 acc.call(addordel, opts.get('config-components', None), 'config/Components.cmake', args)
01021 acc.call(addordel, opts.get('config-package', None), 'config/Package.cmake', args)
01022 acc.call(addordel, opts.get('config-find', None), 'config/Config.cmake.in', args)
01023 acc.call(addordel, opts.get('config-find', None), 'config/ConfigSettings.cmake', args)
01024 acc.call(addordel, opts.get('config-find_version', None), 'config/ConfigVersion.cmake.in', args)
01025 acc.call(addordel, opts.get('config-script', None), 'config/ScriptConfig.cmake.in', args)
01026 acc.call(addordel, opts.get('config-test', None), 'config/CTestCustom.cmake.in', args)
01027 acc.call(addordel, opts.get('config-use', None), 'config/ConfigUse.cmake.in', args)
01028
01029 acc.call(addordel, opts.get('data', None), 'data/CMakeLists.txt', args)
01030
01031 acc.call(addordel, opts.get('doc', None), 'doc/CMakeLists.txt', args)
01032
01033 acc.call(addordel, opts.get('modules', None), 'modules', args, isdir=True)
01034
01035 acc.call(addordel, opts.get('include', None), 'include', args, isdir=True)
01036 acc.call(addordel, opts.get('src', None), 'src/CMakeLists.txt', args)
01037
01038 acc.call(addordel, opts.get('test', None), 'CTestConfig.cmake', args)
01039 acc.call(addordel, opts.get('test', None), 'test/CMakeLists.txt', args)
01040
01041 acc.call(addordel, opts.get('example', None), 'example/CMakeLists.txt', args)
01042
01043 except Exception, e:
01044 sys.stderr.write("Failed to ")
01045 if create: sys.stderr.write("create")
01046 else: sys.stderr.write("modify")
01047 sys.stderr.write(" project: " + str(e) + '\n')
01048 ok = False
01049
01050
01051
01052 if ok:
01053 if create:
01054 sys.stdout.write("\nCreated project\n")
01055 else:
01056 if acc.changes > 0:
01057 sys.stdout.write('\n' + str(acc.changes) + " file")
01058 if acc.changes > 1: sys.stdout.write('s')
01059 sys.stdout.write(" added (A), removed (D), or modified (G, C)\n")
01060 elif acc.errors == 0:
01061 if args.update:
01062 sys.stdout.write("Project is up to date\n")
01063 else:
01064 sys.stdout.write("No project files added, removed, or modified\n")
01065 if acc.errors > 0:
01066 if acc.changes == 0: sys.stderr.write('\n')
01067 sys.stderr.write(str(acc.errors) + " error")
01068 if acc.errors > 1: sys.stderr.write('s')
01069 sys.stderr.write(" encountered\n")
01070 if acc.conflicts > 0:
01071 sys.stderr.write(str(acc.conflicts) + " conflicting change")
01072 if acc.conflicts > 1: sys.stderr.write('s')
01073 sys.stderr.write(" (C) encountered\n")
01074 sys.stderr.write(
01075 """
01076 Resolve conflicts by editing the updated files manually, using the corresonding *.mine
01077 and *.template files as reference. Once a conflict is resolved, remove these files.
01078 """)