def run(): parser = ArgumentParser(description=""" Query an attribute for the current project. Supported attributes: subprojects-dir : base directory where the dependencies will be extracted. """) parser.add_argument( "attribute", action="store", help= "The attribute to query. At the moment only subprojects-dir is supported" ) parser.add_argument("--abspath", action="store_true", help="For path attributes print the absolute path.") args = parser.parse_args() source_dir = os.path.join(os.path.abspath("."), '') conf = load_conf(source_dir) if conf is None: print("This is not a quark project. Aborting.") sys.exit(1) attr = args.attribute if attr == "subprojects_dir": subprojects_dir = conf.get("subprojects_dir", 'lib') if args.abspath: print(os.path.join(source_dir, subprojects_dir)) else: print(subprojects_dir) else: print("Unsupported attribute '%s'." % attr) sys.exit(1)
def generate_cmake_script(source_dir, url=None, options=None, print_tree=False,update=True): root, modules = Subproject.create_dependency_tree(source_dir, url, options, update=update) if print_tree: print(json.dumps(root.toJSON(), indent=4)) conf = load_conf(source_dir) if update and conf is not None: subproject_dir = join(source_dir, conf.get("subprojects_dir", 'lib')) cmakelists_rows = [] processed = set() def dump_options(module): for key, value in sorted(module.options.items()): if value is None: cmakelists_rows.append('unset(%s CACHE)\n' % (key)) continue elif isinstance(value, bool): kind = "BOOL" value = 'ON' if value else 'OFF' else: kind = "STRING" cmakelists_rows.append('set(%s %s CACHE INTERNAL "" FORCE)\n' % (key, value)) def process_module(module): # notice: if a module is marked as excluded from cmake we also # exclude its dependencies; they are nonetheless included if they # are required by another module which is not excluded from cmake if module.name in processed or module.exclude_from_cmake: return processed.add(module.name) # first add the dependent modules # module.children is a set, whose iteration order changes from run to run # make this deterministic (we want to generate always the same CMakeLists.txt) for c in sorted(module.children, key = lambda x: x.name): process_module(c) # dump options and add to the generated CMakeLists.txt dump_options(module) if module is not root and exists(join(module.directory, "CMakeLists.txt")): cmakelists_rows.append('add_subdirectory(%s)\n' % (module.directory)) process_module(root) # write only if different cmakelists_data = ''.join(cmakelists_rows) try: with open(join(subproject_dir, 'CMakeLists.txt'), 'r') as f: if cmakelists_data == f.read(): # nothing to do, avoid touching the file (which often yields a full rebuild) return except IOError: pass # actually write the file with open(join(subproject_dir, 'CMakeLists.txt'), 'w') as f: f.write(cmakelists_data)
def create_dependency_tree(source_dir, url=None, options=None, update=False, clean=False, clobber=False): # make sure the separator is present source_dir_rp = os.path.join(os.path.abspath(source_dir), '') clobber_backup_path = os.path.join(source_dir_rp, 'clobbered.quark') if clobber and os.path.exists(clobber_backup_path): raise QuarkError( 'clobbered.quark already exists; remove it to proceed') root_url = url try: root_url = url_from_directory(source_dir) except QuarkError: pass root = Subproject.create("root", root_url, source_dir, {}, {}, toplevel=True) if url and update: root.checkout() conf = load_conf(source_dir) if conf is None: return root, {} subprojects_dir = conf.get("subprojects_dir", 'lib') subproject_dir = join(source_dir, subprojects_dir) stack = [root] modules = {} def get_option(key): try: return root.options[key] except KeyError as e: err = e for module in modules.values(): try: return module.options[key] except KeyError as e: err = e raise err def add_module(parent, name, uri, options, conf, **kwargs): if uri is None: # options add only, lookup from existing modules uri = modules[name].urlstring target_dir = join(subproject_dir, name) target_dir_rp = os.path.join(os.path.abspath(target_dir), '') if not target_dir_rp.startswith(source_dir_rp): raise QuarkError( """ Subproject `%s` (URI: %s) is trying to escape from the main project directory (`%s`) subproject abspath: %s main project abspath: %s""" % (name, uri, source_dir, target_dir_rp, source_dir_rp)) newmodule = Subproject.create(name, uri, target_dir, options, conf, **kwargs) mod = modules.setdefault(name, newmodule) if mod is newmodule: mod.parents.add(parent) if update: if clobber and os.path.exists(mod.directory): # if we have to clobber and a subproject directory # already exists, move it out of the way # ensure we have a backup root os.makedirs(clobber_backup_path, exist_ok=True) # find a name for the backup directory; we may have to # deal with collisions, as the .. trick allows for # duplicated basenames between subprojects backup_path_base = os.path.join( clobber_backup_path, os.path.basename(mod.directory)) backup_path = backup_path_base i = 0 while True: if not os.path.exists(backup_path): break i += 1 backup_path = '%s.%d' % (backup_path_base, i) print( '%s already present; moving it to %s before new checkout' % (name, backup_path)) shutil.move(mod.directory, backup_path) mod.update(clean) else: if newmodule.exclude_from_cmake != mod.exclude_from_cmake: children_conf = [ join(parent.directory, dependency_file) for parent in mod.parents ] parent_conf = join(parent.directory, dependency_file) raise ValueError( "Conflicting value of 'exclude_from_cmake'" " attribute for module '%s': %r required by %s and %r required by %s" % (name, mod.exclude_from_cmake, children_conf, newmodule.exclude_from_cmake, parent_conf)) if not newmodule.same_checkout(mod) and uri is not None: children = [ join(parent.directory, dependency_file) for parent in mod.parents ] parent = join(parent.directory, dependency_file) raise ValueError( "Conflicting URLs for module '%s': '%s' required by %s and '%s' required by '%s'" % (name, mod.urlstring, children, newmodule.urlstring, parent)) else: for key, value in options.items(): mod.options.setdefault(key, value) if mod.options[key] != value: raise ValueError( "Conflicting values option '%s' of module '%s'" % (key, mod.name)) stack.append(mod) parent.children.add(mod) freeze_conf = join(root.directory, freeze_file) if exists(freeze_conf): with open(freeze_conf, 'r') as f: freeze_dict = json.load(f) else: freeze_dict = {} if update: mkdir(subproject_dir) while len(stack): current_module = stack.pop() if current_module.external_project: generate_cmake_script(current_module.directory, update=update) continue conf = load_conf(current_module.directory) if conf: if current_module.toplevel: current_module.options = conf.get('toplevel_options', {}) if options: current_module.options.update(options) def do_add_module(name, depobject): external_project = depobject.get('external_project', False) add_module(current_module, name, freeze_dict.get(name, depobject.get('url', None)), depobject.get('options', {}), depobject, exclude_from_cmake=depobject.get( 'exclude_from_cmake', external_project), external_project=external_project) for name, depobject in conf.get('depends', {}).items(): do_add_module(name, depobject) for key, optobjects in conf.get('optdepends', {}).items(): if isinstance(optobjects, dict): optobjects = [optobjects] for optobject in optobjects: try: value = get_option(key) except KeyError: continue if value == optobject['value']: for name, depobject in optobject['depends'].items( ): do_add_module(name, depobject) root.set_local_ignores(subprojects_dir, modules.values()) return root, modules
def create_dependency_tree(source_dir, url=None, options=None, update=False): root = Subproject.create("root", url, source_dir, {}, toplevel = True) if url and update: root.checkout() conf = load_conf(source_dir) if conf is None: return root, {} subproject_dir = join(source_dir, conf.get("subprojects_dir", 'lib')) stack = [root] modules = {} def get_option(key): try: return root.options[key] except KeyError as e: err = e for module in modules.values(): try: return module.options[key] except KeyError as e: err = e raise err def add_module(parent, name, uri, options, **kwargs): if uri is None: # options add only, lookup from existing modules uri = modules[name].urlstring newmodule = Subproject.create(name, uri, join(subproject_dir, name), options, **kwargs) mod = modules.setdefault(name, newmodule) if mod is newmodule: mod.parents.add(parent) if update: mod.update() else: if newmodule.exclude_from_cmake != mod.exclude_from_cmake: children_conf = [join(parent.directory, dependency_file) for parent in mod.parents] parent_conf = join(parent.directory, dependency_file) raise ValueError("Conflicting value of 'exclude_from_cmake'" " attribute for module '%s': '%s' required by %s and %s required by %s" % (name, str(mod.exclude_from_cmake), children_conf, str(parent.exclude_from_cmake), parent_conf) ) if not newmodule.same_checkout(mod) and uri is not None: children = [join(parent.directory, dependency_file) for parent in mod.parents] parent = join(parent.directory, dependency_file) raise ValueError( "Conflicting URLs for module '%s': '%s' required by %s and '%s' required by '%s'" % (name, mod.urlstring, children, newmodule.urlstring, parent)) else: for key, value in options.items(): mod.options.setdefault(key, value) if mod.options[key] != value: raise ValueError( "Conflicting values option '%s' of module '%s'" % (key, mod.name) ) stack.append(mod) parent.children.add(mod) freeze_conf = join(root.directory, freeze_file) if exists(freeze_conf): with open(freeze_conf, 'r') as f: freeze_dict = json.load(f) else: freeze_dict = {} if update: mkdir(subproject_dir) while len(stack): current_module = stack.pop() if current_module.external_project: generate_cmake_script(current_module.directory, update = update) continue conf = load_conf(current_module.directory) if conf: if current_module.toplevel: current_module.options = conf.get('toplevel_options', {}) if options: current_module.options.update(options) for name, depobject in conf.get('depends', {}).items(): external_project = depobject.get('external_project', False) add_module(current_module, name, freeze_dict.get(name, depobject.get('url', None)), depobject.get('options', {}), exclude_from_cmake=depobject.get('exclude_from_cmake', external_project), external_project=external_project ) for key, optobjects in conf.get('optdepends', {}).items(): if isinstance(optobjects, dict): optobjects = [optobjects] for optobject in optobjects: try: value = get_option(key) except KeyError: continue if value == optobject['value']: for name, depobject in optobject['depends'].items(): add_module(current_module, name, freeze_dict.get(name, depobject.get('url', None)), depobject.get('options', {})) return root, modules
def create_dependency_tree(source_dir, url=None, options=None, update=False, clean=False): # make sure the separator is present source_dir_rp = os.path.join(os.path.abspath(source_dir), '') root_url = url try: root_url = url_from_directory(source_dir) except QuarkError: pass root = Subproject.create("root", root_url, source_dir, {}, {}, toplevel = True) if url and update: root.checkout() conf = load_conf(source_dir) if conf is None: return root, {} subprojects_dir = conf.get("subprojects_dir", 'lib') subproject_dir = join(source_dir, subprojects_dir) stack = [root] modules = {} def get_option(key): try: return root.options[key] except KeyError as e: err = e for module in modules.values(): try: return module.options[key] except KeyError as e: err = e raise err def add_module(parent, name, uri, options, conf, **kwargs): if uri is None: # options add only, lookup from existing modules uri = modules[name].urlstring target_dir = join(subproject_dir, name) target_dir_rp = os.path.join(os.path.abspath(target_dir), '') if not target_dir_rp.startswith(source_dir_rp): raise QuarkError(""" Subproject `%s` (URI: %s) is trying to escape from the main project directory (`%s`) subproject abspath: %s main project abspath: %s""" % (name, uri, source_dir, target_dir_rp, source_dir_rp)) newmodule = Subproject.create(name, uri, target_dir, options, conf, **kwargs) mod = modules.setdefault(name, newmodule) if mod is newmodule: mod.parents.add(parent) if update: mod.update(clean) else: if newmodule.exclude_from_cmake != mod.exclude_from_cmake: children_conf = [join(parent.directory, dependency_file) for parent in mod.parents] parent_conf = join(parent.directory, dependency_file) raise ValueError("Conflicting value of 'exclude_from_cmake'" " attribute for module '%s': %r required by %s and %r required by %s" % (name, mod.exclude_from_cmake, children_conf, newmodule.exclude_from_cmake, parent_conf) ) if not newmodule.same_checkout(mod) and uri is not None: children = [join(parent.directory, dependency_file) for parent in mod.parents] parent = join(parent.directory, dependency_file) raise ValueError( "Conflicting URLs for module '%s': '%s' required by %s and '%s' required by '%s'" % (name, mod.urlstring, children, newmodule.urlstring, parent)) else: for key, value in options.items(): mod.options.setdefault(key, value) if mod.options[key] != value: raise ValueError( "Conflicting values option '%s' of module '%s'" % (key, mod.name) ) stack.append(mod) parent.children.add(mod) freeze_conf = join(root.directory, freeze_file) if exists(freeze_conf): with open(freeze_conf, 'r') as f: freeze_dict = json.load(f) else: freeze_dict = {} if update: mkdir(subproject_dir) while len(stack): current_module = stack.pop() if current_module.external_project: generate_cmake_script(current_module.directory, update = update) continue conf = load_conf(current_module.directory) if conf: if current_module.toplevel: current_module.options = conf.get('toplevel_options', {}) if options: current_module.options.update(options) for name, depobject in conf.get('depends', {}).items(): external_project = depobject.get('external_project', False) add_module(current_module, name, freeze_dict.get(name, depobject.get('url', None)), depobject.get('options', {}), depobject, exclude_from_cmake=depobject.get('exclude_from_cmake', external_project), external_project=external_project, ) for key, optobjects in conf.get('optdepends', {}).items(): if isinstance(optobjects, dict): optobjects = [optobjects] for optobject in optobjects: try: value = get_option(key) except KeyError: continue if value == optobject['value']: for name, depobject in optobject['depends'].items(): add_module(current_module, name, freeze_dict.get(name, depobject.get('url', None)), depobject.get('options', {}), depobject) root.set_local_ignores(subprojects_dir, modules.values()) return root, modules