def run(self, project, name, ps, sources): # TODO: Replace this with the use of a target-os property. no_static_link = False if bjam.variable('UNIX'): no_static_link = True; ##FIXME: what does this mean? ## { ## switch [ modules.peek : JAMUNAME ] ## { ## case * : no-static-link = true ; ## } ## } reason = None if no_static_link and ps.get('runtime-link') == 'static': if ps.get('link') == 'shared': reason = "On gcc, DLL can't be build with '<runtime-link>static'." elif type.is_derived(self.target_types[0], 'EXE'): for s in sources: source_type = s.type() if source_type and type.is_derived(source_type, 'SHARED_LIB'): reason = "On gcc, using DLLS together with the " +\ "<runtime-link>static options is not possible " if reason: print 'warning:', reason print 'warning:',\ "It is suggested to use '<runtime-link>static' together",\ "with '<link>static'." ; return else: generated_targets = unix.UnixLinkingGenerator.run(self, project, name, ps, sources) return generated_targets
def run(self, project, name, prop_set, sources): assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) # create a copy since sources is being modified sources = list(sources) sources.extend(prop_set.get('<library>')) # Add <library-path> properties for all searched libraries extra = [] for s in sources: if s.type() == 'SEARCHED_LIB': search = s.search() extra.extend( property.Property('<library-path>', sp) for sp in search) # It's possible that we have libraries in sources which did not came # from 'lib' target. For example, libraries which are specified # just as filenames as sources. We don't have xdll-path properties # for such target, but still need to add proper dll-path properties. extra_xdll_path = [] for s in sources: if type.is_derived(s.type(), 'SHARED_LIB') and not s.action(): # Unfortunately, we don't have a good way to find the path # to a file, so use this nasty approach. p = s.project() location = path.root(s.name(), p.get('source-location')[0]) extra_xdll_path.append(os.path.dirname(location)) # Hardcode DLL paths only when linking executables. # Pros: do not need to relink libraries when installing. # Cons: "standalone" libraries (plugins, python extensions) can not # hardcode paths to dependent libraries. if prop_set.get('<hardcode-dll-paths>') == ['true'] \ and type.is_derived(self.target_types_ [0], 'EXE'): xdll_path = prop_set.get('<xdll-path>') extra.extend(property.Property('<dll-path>', sp) \ for sp in extra_xdll_path) extra.extend(property.Property('<dll-path>', sp) \ for sp in xdll_path) if extra: prop_set = prop_set.add_raw(extra) result = generators.Generator.run(self, project, name, prop_set, sources) if result: ur = self.extra_usage_requirements(result, prop_set) ur = ur.add( property_set.create( ['<xdll-path>' + p for p in extra_xdll_path])) else: return None return (ur, result)
def run (self, project, name, prop_set, sources): assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) # create a copy since sources is being modified sources = list(sources) sources.extend(prop_set.get('<library>')) # Add <library-path> properties for all searched libraries extra = [] for s in sources: if s.type () == 'SEARCHED_LIB': search = s.search() extra.extend(property.Property('<library-path>', sp) for sp in search) # It's possible that we have libraries in sources which did not came # from 'lib' target. For example, libraries which are specified # just as filenames as sources. We don't have xdll-path properties # for such target, but still need to add proper dll-path properties. extra_xdll_path = [] for s in sources: if type.is_derived (s.type (), 'SHARED_LIB') and not s.action (): # Unfortunately, we don't have a good way to find the path # to a file, so use this nasty approach. p = s.project() location = path.root(s.name(), p.get('source-location')[0]) extra_xdll_path.append(os.path.dirname(location)) # Hardcode DLL paths only when linking executables. # Pros: do not need to relink libraries when installing. # Cons: "standalone" libraries (plugins, python extensions) can not # hardcode paths to dependent libraries. if prop_set.get('<hardcode-dll-paths>') == ['true'] \ and type.is_derived(self.target_types_ [0], 'EXE'): xdll_path = prop_set.get('<xdll-path>') extra.extend(property.Property('<dll-path>', sp) \ for sp in extra_xdll_path) extra.extend(property.Property('<dll-path>', sp) \ for sp in xdll_path) if extra: prop_set = prop_set.add_raw (extra) result = generators.Generator.run(self, project, name, prop_set, sources) if result: ur = self.extra_usage_requirements(result, prop_set) ur = ur.add(property_set.create(['<xdll-path>' + p for p in extra_xdll_path])) else: return None return (ur, result)
def run(self, project, name, prop_set, sources): lib_sources = prop_set.get('<library>') sources.extend(lib_sources) # Add <library-path> properties for all searched libraries extra = [] for s in sources: if s.type() == 'SEARCHED_LIB': search = s.search() extra.extend( property.Property('<library-path>', sp) for sp in search) orig_xdll_path = [] if prop_set.get('<hardcode-dll-paths>') == ['true'] \ and type.is_derived(self.target_types_ [0], 'EXE'): xdll_path = prop_set.get('<xdll-path>') orig_xdll_path = [ replace_grist(x, '<dll-path>') for x in xdll_path ] # It's possible that we have libraries in sources which did not came # from 'lib' target. For example, libraries which are specified # just as filenames as sources. We don't have xdll-path properties # for such target, but still need to add proper dll-path properties. for s in sources: if type.is_derived(s.type(), 'SHARED_LIB') and not s.action(): # Unfortunately, we don't have a good way to find the path # to a file, so use this nasty approach. p = s.project() location = path.root(s.name(), p.get('source-location')) xdll_path.append(path.parent(location)) extra.extend( property.Property('<dll-path>', sp) for sp in xdll_path) if extra: prop_set = prop_set.add_raw(extra) result = generators.Generator.run(self, project, name, prop_set, sources) if result: ur = self.extra_usage_requirements(result, prop_set) ur = ur.add(property_set.create(orig_xdll_path)) else: return None return (ur, result)
def extra_usage_requirements (self, created_targets, prop_set): result = property_set.empty () extra = [] # Add appropriate <xdll-path> usage requirements. raw = prop_set.raw () if '<link>shared' in raw: paths = [] # TODO: is it safe to use the current directory? I think we should use # another mechanism to allow this to be run from anywhere. pwd = os.getcwd() for t in created_targets: if type.is_derived(t.type(), 'SHARED_LIB'): paths.append(path.root(path.make(t.path()), pwd)) extra += replace_grist(paths, '<xdll-path>') # We need to pass <xdll-path> features that we've got from sources, # because if shared library is built, exe which uses it must know paths # to other shared libraries this one depends on, to be able to find them # all at runtime. # Just pass all features in property_set, it's theorically possible # that we'll propagate <xdll-path> features explicitly specified by # the user, but then the user's to blaim for using internal feature. values = prop_set.get('<xdll-path>') extra += replace_grist(values, '<xdll-path>') if extra: result = property_set.create(extra) return result
def generated_targets (self, sources, prop_set, project, name): # sources to pass to inherited rule sources2 = [] # sources which are libraries libraries = [] # Searched libraries are not passed as argument to linker # but via some option. So, we pass them to the action # via property. fsa = [] fst = [] for s in sources: if type.is_derived(s.type(), 'SEARCHED_LIB'): n = s.name() if s.shared(): fsa.append(n) else: fst.append(n) else: sources2.append(s) add = [] if fsa: add.append("<find-shared-library>" + '&&'.join(fsa)) if fst: add.append("<find-static-library>" + '&&'.join(fst)) spawn = generators.Generator.generated_targets(self, sources2, prop_set.add_raw(add), project, name) return spawn
def extra_usage_requirements(self, created_targets, prop_set): result = property_set.empty() extra = [] # Add appropriate <xdll-path> usage requirements. raw = prop_set.raw() if '<link>shared' in raw: paths = [] # TODO: is it safe to use the current directory? I think we should use # another mechanism to allow this to be run from anywhere. pwd = os.getcwd() for t in created_targets: if type.is_derived(t.type(), 'SHARED_LIB'): paths.append(path.root(path.make(t.path()), pwd)) extra += replace_grist(paths, '<xdll-path>') # We need to pass <xdll-path> features that we've got from sources, # because if shared library is built, exe which uses it must know paths # to other shared libraries this one depends on, to be able to find them # all at runtime. # Just pass all features in property_set, it's theorically possible # that we'll propagate <xdll-path> features explicitly specified by # the user, but then the user's to blaim for using internal feature. values = prop_set.get('<xdll-path>') extra += replace_grist(values, '<xdll-path>') if extra: result = property_set.create(extra) return result
def generated_targets(self, sources, prop_set, project, name): # sources to pass to inherited rule sources2 = [] # sources which are libraries libraries = [] # Searched libraries are not passed as argument to linker # but via some option. So, we pass them to the action # via property. fsa = [] fst = [] for s in sources: if type.is_derived(s.type(), 'SEARCHED_LIB'): n = s.real_name() if s.shared(): fsa.append(n) else: fst.append(n) else: sources2.append(s) add = [] if fsa: add.append("<find-shared-library>" + '&&'.join(fsa)) if fst: add.append("<find-static-library>" + '&&'.join(fst)) spawn = generators.Generator.generated_targets(self, sources2, prop_set.add_raw(add), project, name) return spawn
def generated_targets(self, sources, prop_set, project, name): assert is_iterable_typed(sources, virtual_target.VirtualTarget) assert isinstance(prop_set, property_set.PropertySet) assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) # sources to pass to inherited rule sources2 = [] # sources which are libraries libraries = [] # Searched libraries are not passed as argument to linker # but via some option. So, we pass them to the action # via property. fsa = [] fst = [] for s in sources: if type.is_derived(s.type(), "SEARCHED_LIB"): n = s.name() if s.shared(): fsa.append(n) else: fst.append(n) else: sources2.append(s) add = [] if fsa: add.append("<find-shared-library>" + "&&".join(fsa)) if fst: add.append("<find-static-library>" + "&&".join(fst)) spawn = generators.Generator.generated_targets(self, sources2, prop_set.add_raw(add), project, name) return spawn
def generated_targets (self, sources, prop_set, project, name): # sources to pass to inherited rule sources2 = [] # properties to pass to inherited rule properties2 = [] # sources which are libraries libraries = [] # Searched libraries are not passed as argument to linker # but via some option. So, we pass them to the action # via property. properties2 = prop_set.raw() fsa = [] fst = [] for s in sources: if type.is_derived(s.type(), 'SEARCHED_LIB'): name = s.real_name() if s.shared(): fsa.append(name) else: fst.append(name) else: sources2.append(s) if fsa: properties2 += [replace_grist('&&'.join(fsa), '<find-shared-library>')] if fst: properties2 += [replace_grist('&&'.join(fst), '<find-static-library>')] spawn = generators.Generator.generated_targets(self, sources2, property_set.create(properties2), project, name) return spawn
def run_pch(self, project, name, prop_set, sources): # Find the header in sources. Ignore any CPP sources. header = None for s in sources: if type.is_derived(s.type(), 'H'): header = s # Error handling: Base header file name should be the same as the base # precompiled header name. header_name = header.name() header_basename = os.path.basename(header_name).rsplit('.', 1)[0] if header_basename != name: location = project.project_module ###FIXME: raise Exception() ### errors.user-error "in" $(location)": pch target name `"$(name)"' should be the same as the base name of header file `"$(header-name)"'" ; pch_file = Generator.run(self, project, name, prop_set, [header]) # return result of base class and pch-file property as usage-requirements # FIXME: what about multiple results from generator.run? return (property_set.create([ Property('pch-file', pch_file[0]), Property('cflags', '-Winvalid-pch') ]), pch_file)
def set_library_order(manager, sources, prop_set, result): used_libraries = [] deps = prop_set.dependency() sources.extend(d.value() for d in deps) sources = sequence.unique(sources) for l in sources: if l.type() and type.is_derived(l.type(), 'LIB'): used_libraries.append(l) created_libraries = [] for l in result: if l.type() and type.is_derived(l.type(), 'LIB'): created_libraries.append(l) created_libraries = set.difference(created_libraries, used_libraries) set_library_order_aux(created_libraries, used_libraries)
def set_library_order (manager, sources, prop_set, result): used_libraries = [] deps = prop_set.dependency () sources.extend(d.value() for d in deps) sources = sequence.unique(sources) for l in sources: if l.type () and type.is_derived (l.type (), 'LIB'): used_libraries.append (l) created_libraries = [] for l in result: if l.type () and type.is_derived (l.type (), 'LIB'): created_libraries.append (l) created_libraries = set.difference (created_libraries, used_libraries) set_library_order_aux (created_libraries, used_libraries)
def run (self, project, name, prop_set, sources): lib_sources = prop_set.get('<library>') sources.extend(lib_sources) # Add <library-path> properties for all searched libraries extra = [] for s in sources: if s.type () == 'SEARCHED_LIB': search = s.search() extra.extend(property.Property('<library-path>', sp) for sp in search) orig_xdll_path = [] if prop_set.get('<hardcode-dll-paths>') == ['true'] \ and type.is_derived(self.target_types_ [0], 'EXE'): xdll_path = prop_set.get('<xdll-path>') orig_xdll_path = [ replace_grist(x, '<dll-path>') for x in xdll_path ] # It's possible that we have libraries in sources which did not came # from 'lib' target. For example, libraries which are specified # just as filenames as sources. We don't have xdll-path properties # for such target, but still need to add proper dll-path properties. for s in sources: if type.is_derived (s.type (), 'SHARED_LIB') and not s.action (): # Unfortunately, we don't have a good way to find the path # to a file, so use this nasty approach. p = s.project() location = path.root(s.name(), p.get('source-location')) xdll_path.append(path.parent(location)) extra.extend(property.Property('<dll-path>', sp) for sp in xdll_path) if extra: prop_set = prop_set.add_raw (extra) result = generators.Generator.run(self, project, name, prop_set, sources) if result: ur = self.extra_usage_requirements(result, prop_set) ur = ur.add(property_set.create(orig_xdll_path)) else: return None return(ur, result)
def run(self, project, name, prop_set, sources): sources.extend(prop_set.get("<library>")) # Add <library-path> properties for all searched libraries extra = [] for s in sources: if s.type() == "SEARCHED_LIB": search = s.search() extra.extend(property.Property("<library-path>", sp) for sp in search) # It's possible that we have libraries in sources which did not came # from 'lib' target. For example, libraries which are specified # just as filenames as sources. We don't have xdll-path properties # for such target, but still need to add proper dll-path properties. extra_xdll_path = [] for s in sources: if type.is_derived(s.type(), "SHARED_LIB") and not s.action(): # Unfortunately, we don't have a good way to find the path # to a file, so use this nasty approach. p = s.project() location = path.root(s.name(), p.get("source-location")[0]) extra_xdll_path.append(os.path.dirname(location)) # Hardcode DLL paths only when linking executables. # Pros: do not need to relink libraries when installing. # Cons: "standalone" libraries (plugins, python extensions) can not # hardcode paths to dependent libraries. if prop_set.get("<hardcode-dll-paths>") == ["true"] and type.is_derived(self.target_types_[0], "EXE"): xdll_path = prop_set.get("<xdll-path>") extra.extend(property.Property("<dll-path>", sp) for sp in extra_xdll_path) extra.extend(property.Property("<dll-path>", sp) for sp in xdll_path) if extra: prop_set = prop_set.add_raw(extra) result = generators.Generator.run(self, project, name, prop_set, sources) if result: ur = self.extra_usage_requirements(result, prop_set) ur = ur.add(property_set.create(["<xdll-path>" + p for p in extra_xdll_path])) else: return None return (ur, result)
def compute_target_directories(self, target_type=None): result = [] for t in self.created_targets(): if not target_type or type.is_derived(t.type(), target_type): result.append(t.path()) for d in self.other_dg_: result.extend(d.all_target_directories(target_type)) result = unique(result) return result
def generated_targets (self, sources, prop_set, project, name): sources2 = [] libraries = [] for l in sources: if type.is_derived (l.type (), 'LIB'): libraries.append (l) else: sources2.append (l) sources = sources2 + order_libraries (libraries) return builtin.LinkingGenerator.generated_targets (self, sources, prop_set, project, name)
def consume_directly(self, source): real_source_type = source.type() # If there are no source types, we can consume anything source_types = self.source_types() if not source_types: source_types = [real_source_type] consumed = [] missing_types = [] for st in source_types: # The 'source' if of right type already) if real_source_type == st or type.is_derived(real_source_type, st): consumed.append(source) else: missing_types.append(st) return (consumed, missing_types)
def consume_directly (self, source): assert isinstance(source, virtual_target.VirtualTarget) real_source_type = source.type () # If there are no source types, we can consume anything source_types = self.source_types() if not source_types: source_types = [real_source_type] consumed = [] missing_types = [] for st in source_types: # The 'source' if of right type already) if real_source_type == st or type.is_derived (real_source_type, st): consumed.append (source) else: missing_types.append (st) return (consumed, missing_types)
def generated_targets(self, sources, prop_set, project, name): # sources to pass to inherited rule sources2 = [] # properties to pass to inherited rule properties2 = [] # sources which are libraries libraries = [] # Searched libraries are not passed as argument to linker # but via some option. So, we pass them to the action # via property. properties2 = prop_set.raw() fsa = [] fst = [] for s in sources: if type.is_derived(s.type(), 'SEARCHED_LIB'): name = s.real_name() if s.shared(): fsa.append(name) else: fst.append(name) else: sources2.append(s) if fsa: properties2 += [ replace_grist('&&'.join(fsa), '<find-shared-library>') ] if fst: properties2 += [ replace_grist('&&'.join(fst), '<find-static-library>') ] spawn = generators.Generator.generated_targets( self, sources2, property_set.create(properties2), project, name) return spawn
def run (self, project, name, prop_set, sources): assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) # create a copy since this modifies the sources list sources = list(sources) sources.extend(prop_set.get('<library>')) result = generators.Generator.run (self, project, name, prop_set, sources) usage_requirements = [] link = prop_set.get('<link>') if 'static' in link: for t in sources: if type.is_derived(t.type(), 'LIB'): usage_requirements.append(property.Property('<library>', t)) usage_requirements = property_set.create(usage_requirements) return usage_requirements, result
def run(self, project, name, prop_set, sources): assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) # create a copy since this modifies the sources list sources = list(sources) sources.extend(prop_set.get("<library>")) result = generators.Generator.run(self, project, name, prop_set, sources) usage_requirements = [] link = prop_set.get("<link>") if "static" in link: for t in sources: if type.is_derived(t.type(), "LIB"): usage_requirements.append(property.Property("<library>", t)) usage_requirements = property_set.create(usage_requirements) return usage_requirements, result
def run_pch(self, project, name, prop_set, sources): # Find the header in sources. Ignore any CPP sources. header = None for s in sources: if type.is_derived(s.type, 'H'): header = s # Error handling: Base header file name should be the same as the base # precompiled header name. header_name = header.name header_basename = os.path.basename(header_name).rsplit('.', 1)[0] if header_basename != name: location = project.project_module ###FIXME: raise Exception() ### errors.user-error "in" $(location)": pch target name `"$(name)"' should be the same as the base name of header file `"$(header-name)"'" ; pch_file = Generator.run(self, project, name, prop_set, [header]) # return result of base class and pch-file property as usage-requirements # FIXME: what about multiple results from generator.run? return (property_set.create('<pch-file>' + pch_file[0], '<cflags>-Winvalid-pch'), pch_file)
def format_name(format, name, target_type, prop_set): """ Given a target, as given to a custom tag rule, returns a string formatted according to the passed format. Format is a list of properties that is represented in the result. For each element of format the corresponding target information is obtained and added to the result string. For all, but the literal, the format value is taken as the as string to prepend to the output to join the item to the rest of the result. If not given "-" is used as a joiner. The format options can be: <base>[joiner] :: The basename of the target name. <toolset>[joiner] :: The abbreviated toolset tag being used to build the target. <threading>[joiner] :: Indication of a multi-threaded build. <runtime>[joiner] :: Collective tag of the build runtime. <version:/version-feature | X.Y[.Z]/>[joiner] :: Short version tag taken from the given "version-feature" in the build properties. Or if not present, the literal value as the version number. <property:/property-name/>[joiner] :: Direct lookup of the given property-name value in the build properties. /property-name/ is a regular expression. e.g. <property:toolset-.*:flavor> will match every toolset. /otherwise/ :: The literal value of the format argument. For example this format: boost_ <base> <toolset> <threading> <runtime> <version:boost-version> Might return: boost_thread-vc80-mt-gd-1_33.dll, or boost_regex-vc80-gd-1_33.dll The returned name also has the target type specific prefix and suffix which puts it in a ready form to use as the value from a custom tag rule. """ assert (isinstance(format, list)) assert (isinstance(name, str)) assert (isinstance(target_type, str) or not type) # assert(isinstance(prop_set, property_set.PropertySet)) if type.is_derived(target_type, 'LIB'): result = "" for f in format: grist = get_grist(f) if grist == '<base>': result += os.path.basename(name) elif grist == '<toolset>': result += join_tag(get_value(f), toolset_tag(name, target_type, prop_set)) elif grist == '<threading>': result += join_tag(get_value(f), threading_tag(name, target_type, prop_set)) elif grist == '<runtime>': result += join_tag(get_value(f), runtime_tag(name, target_type, prop_set)) elif grist.startswith('<version:'): key = grist[len('<version:'):-1] version = prop_set.get('<' + key + '>') if not version: version = key version = __re_version.match(version) result += join_tag(get_value(f), version[1] + '_' + version[2]) elif grist.startswith('<property:'): key = grist[len('<property:'):-1] property_re = re.compile('<(' + key + ')>') p0 = None for prop in prop_set.raw(): match = property_re.match(prop) if match: p0 = match[1] break if p0: p = prop_set.get('<' + p0 + '>') if p: assert (len(p) == 1) result += join_tag(ungrist(f), p) else: result += f result = b2.build.virtual_target.add_prefix_and_suffix( ''.join(result), target_type, prop_set) return result
def format_name(format, name, target_type, prop_set): """ Given a target, as given to a custom tag rule, returns a string formatted according to the passed format. Format is a list of properties that is represented in the result. For each element of format the corresponding target information is obtained and added to the result string. For all, but the literal, the format value is taken as the as string to prepend to the output to join the item to the rest of the result. If not given "-" is used as a joiner. The format options can be: <base>[joiner] :: The basename of the target name. <toolset>[joiner] :: The abbreviated toolset tag being used to build the target. <threading>[joiner] :: Indication of a multi-threaded build. <runtime>[joiner] :: Collective tag of the build runtime. <version:/version-feature | X.Y[.Z]/>[joiner] :: Short version tag taken from the given "version-feature" in the build properties. Or if not present, the literal value as the version number. <property:/property-name/>[joiner] :: Direct lookup of the given property-name value in the build properties. /property-name/ is a regular expression. e.g. <property:toolset-.*:flavor> will match every toolset. /otherwise/ :: The literal value of the format argument. For example this format: boost_ <base> <toolset> <threading> <runtime> <version:boost-version> Might return: boost_thread-vc80-mt-gd-1_33.dll, or boost_regex-vc80-gd-1_33.dll The returned name also has the target type specific prefix and suffix which puts it in a ready form to use as the value from a custom tag rule. """ assert(isinstance(format, list)) assert(isinstance(name, str)) assert(isinstance(target_type, str) or not type) # assert(isinstance(prop_set, property_set.PropertySet)) if type.is_derived(target_type, 'LIB'): result = "" ; for f in format: grist = get_grist(f) if grist == '<base>': result += os.path.basename(name) elif grist == '<toolset>': result += join_tag(get_value(f), toolset_tag(name, target_type, prop_set)) elif grist == '<threading>': result += join_tag(get_value(f), threading_tag(name, target_type, prop_set)) elif grist == '<runtime>': result += join_tag(get_value(f), runtime_tag(name, target_type, prop_set)) elif grist.startswith('<version:'): key = grist[len('<version:'):-1] version = prop_set.get('<' + key + '>') if not version: version = key version = __re_version.match(version) result += join_tag(get_value(f), version[1] + '_' + version[2]) elif grist.startswith('<property:'): key = grist[len('<property:'):-1] property_re = re.compile('<(' + key + ')>') p0 = None for prop in prop_set.raw(): match = property_re.match(prop) if match: p0 = match[1] break if p0: p = prop_set.get('<' + p0 + '>') if p: assert(len(p) == 1) result += join_tag(ungrist(f), p) else: result += f result = b2.build.virtual_target.add_prefix_and_suffix( ''.join(result), target_type, prop_set) return result