def gen_for_module(self, module): # attach VS2010-specific data to the model module.solution = self.Solution(self, module) for t in module.targets.itervalues(): with error_context(t): prj = self.get_project_object(t) if prj is None: # TODO: see the TODO in get_project_object() continue if prj.name != t.name: # TODO: This is only for the solution file; we should remap the name instead of # failure. Note that we don't always control prj.name, it may come from external # project file. raise Error("project name (\"%s\") differs from target name (\"%s\"), they must be the same" % (prj.name, t.name)) if prj.version and prj.version not in self.proj_versions: if prj.version > self.proj_versions[-1]: raise Error("project %s is for Visual Studio %.1f and will not work with %.1f" % (prj.projectfile, prj.version, self.version)) else: warning("project %s is for Visual Studio %.1f, not %.1f, will be converted when built", prj.projectfile, prj.version, self.version) if self.is_natively_supported(t): self.gen_for_target(t, prj) module.solution.add_project(prj)
def get_project_object(self, target): if self.is_natively_supported(target): proj = self.Project(target.name, target["%s.guid" % self.name].as_py(), target["%s.projectfile" % self.name], target["deps"].as_py(), [x.config for x in target.configurations], self.get_platforms(target), target.source_pos) else: # Not natively supported; try if the TargetType has an implementation try: proj = target.type.vs_project(self, target) except NotImplementedError: # TODO: handle this as generic action target warning("target type \"%s\" is not supported by the %s toolset, ignoring", target.type.name, self.name) return None # See which configurations this target is explicitly disabled in. # Notice that we must check _all_ configurations visible in the solution, # not just the ones used by this target. all_global_configs = (ConfigurationProxy(target, x) for x in target.project.configurations.itervalues()) proj.disabled_configurations = [x.config for x in all_global_configs if not x.should_build()] return proj
def detect_unused_vars(model): """ Warns about unused variables -- they may indicate typos. """ # First of all, iterate over all variables and mark their usage of other # variables. Notice that it's possible that some code explicitly marked # variables as used with mark_variables_as_used() before this step. for var in model.all_variables(): usage_tracker.visit(var.value) # Not emit warnings for unused variables. import re regex_vs_option = re.compile(r"vs[0-9]+\.option\.") for var in model.all_variables(): if ( not var.is_property and not usage_tracker.is_used(var) and # FIXME: Handle these cases properly. Have a properties group # declaration similar to Property, with type checking and # automated docs and all. Then test for it here as other # properties are tested for. not regex_vs_option.match(var.name) and # FIXME: Handle this case properly. var.name != "configurations" ): warning('variable "%s" is never used', var.name, pos=var.value.pos)
def detect_missing_generated_outputs(model): """ Warns about generated source files not included in sources/headers. """ for t in model.all_targets(): for srcfile in t.all_source_files(): with error_context(srcfile): if not srcfile["compile-commands"]: continue sources = set(ch.name for ch in t.child_parts()) outputs = set(i for c,i in bkl.expr.enum_possible_values(srcfile["outputs"])) for item in outputs: partname = bkl.expr.get_model_name_from_path(item) if partname not in sources: warning("file %s generated from %s is not among sources or headers of target \"%s\"", item, srcfile.filename, t.name, pos=item.pos)
def generate(self): """ Generates output files. """ # collect all requested toolsets: toolsets = set() for module in self.model.modules: module_toolsets = module.get_variable("toolsets") if module_toolsets: toolsets.update(module_toolsets.value.as_py()) if self.toolsets_to_use: for t in self.toolsets_to_use: if t not in toolsets: try: bkl.api.Toolset.get(t) except KeyError: raise Error( "unknown toolset \"%s\" given on command line" % t) warning( "toolset \"%s\" is not supported by the project, there may be issues", t) # Add the forced toolset to all submodules: for module in self.model.modules: module_toolsets = module.get_variable("toolsets") if module_toolsets: module_toolsets.value.items.append( bkl.expr.LiteralExpr(t)) toolsets = self.toolsets_to_use toolsets = list(toolsets) logger.debug("toolsets to generate for: %s", toolsets) if not toolsets: raise Error("nothing to generate, \"toolsets\" property is empty") # call any custom steps first: self._call_custom_steps(self.model, "generate") # and generate the outputs (notice that we can avoid making a # (expensive!) deepcopy of the model for one of the toolsets and can # reuse the current model): for toolset in toolsets[:-1]: self.generate_for_toolset(toolset) self.generate_for_toolset(toolsets[-1], skip_making_copy=True)
def unescape(self, token, text): """Removes \\ escapes from the text.""" out = "" start = 0 while True: pos = text.find('\\', start) if pos == -1: out += text[start:] break else: out += text[start:pos] c = text[pos+1] out += c start = pos+2 if c != '"' and c != '\\' and c != '$': source_pos = self._get_position(token) source_pos.column += pos+1 warning("unnecessary escape sequence '\\%s' (did you mean '\\\\%s'?)" % (c, c), pos=source_pos) return out
def generate(self): """ Generates output files. """ # collect all requested toolsets: toolsets = set() for module in self.model.modules: module_toolsets = module.get_variable("toolsets") if module_toolsets: toolsets.update(module_toolsets.value.as_py()) if self.toolsets_to_use: for t in self.toolsets_to_use: if t not in toolsets: try: bkl.api.Toolset.get(t) except KeyError: raise Error("unknown toolset \"%s\" given on command line" % t) warning("toolset \"%s\" is not supported by the project, there may be issues", t) # Add the forced toolset to all submodules: for module in self.model.modules: module_toolsets = module.get_variable("toolsets") if module_toolsets: module_toolsets.value.items.append(bkl.expr.LiteralExpr(t)) toolsets = self.toolsets_to_use toolsets = list(toolsets) logger.debug("toolsets to generate for: %s", toolsets) if not toolsets: raise Error("nothing to generate, \"toolsets\" property is empty") # call any custom steps first: self._call_custom_steps(self.model, "generate") # and generate the outputs (notice that we can avoid making a # (expensive!) deepcopy of the model for one of the toolsets and can # reuse the current model): for toolset in toolsets[:-1]: self.generate_for_toolset(toolset) self.generate_for_toolset(toolsets[-1], skip_making_copy=True)
def detect_missing_generated_outputs(model): """ Warns about generated source files not included in sources/headers. """ for t in model.all_targets(): for srcfile in t.all_source_files(): with error_context(srcfile): if not srcfile["compile-commands"]: continue sources = set(ch.name for ch in t.child_parts()) outputs = set(i for c, i in bkl.expr.enum_possible_values( srcfile["outputs"])) for item in outputs: partname = bkl.expr.get_model_name_from_path(item) if partname not in sources: warning( "file %s generated from %s is not among sources or headers of target \"%s\"", item, srcfile.filename, t.name, pos=item.pos)
def _get_matching_project_config(cfg, prj): """ Returns best match project configuration for given solution configuration. """ with error_context(prj): if cfg in prj.configurations: return cfg # else: try to find a configuration closest to the given one, i.e. # the one from which it inherits via the minimal number of # intermediate configurations: compatibles = [] for pc in prj.configurations: degree = cfg.derived_from(pc) if degree: compatibles.append((degree, pc)) if not compatibles: # if we don't have any project configurations from which this # one inherits, check if we have any which inherit from this # one themselves as they should be a reasonably good fallback: for pc in prj.configurations: degree = pc.derived_from(cfg) if degree: compatibles.append((degree, pc)) if compatibles: if len(compatibles) > 1: compatibles.sort() # It can happen that we have 2 project configurations # inheriting from the solution configuration with the same # degree. In this case there we can't really make the # right choice automatically, so we must warn the user. degree = compatibles[0][0] if compatibles[1][0] == degree: good_ones = [x[1].name for x in compatibles if x[0] == degree] warning("project %s: no unambiguous choice of project configuration to use for the solution configuration \"%s\", equally good candidates are: \"%s\"", prj.projectfile, cfg.name, '", "'.join(good_ones)) degree, ret = compatibles[0] logger.debug("solution config \"%s\" -> project %s config \"%s\" (dg %d)", cfg.name, prj.projectfile, ret.name, degree) return ret # if all failed, just pick the first config, but at least try to match # debug/release setting: compatibles = [x for x in prj.configurations if x.is_debug == cfg.is_debug] if compatibles: ret = compatibles[0] warning("project %s: using unrelated project configuration \"%s\" for solution configuration \"%s\"", prj.projectfile, ret.name, cfg.name) return ret else: ret = prj.configurations[0] warning("project %s: using incompatible project configuration \"%s\" for solution configuration \"%s\"", prj.projectfile, ret.name, cfg.name) return ret
def unescape(self, token, text): """Removes \\ escapes from the text.""" out = "" start = 0 while True: pos = text.find('\\', start) if pos == -1: out += text[start:] break else: out += text[start:pos] c = text[pos + 1] out += c start = pos + 2 if c != '"' and c != '\\' and c != '$': source_pos = self._get_position(token) source_pos.column += pos + 1 warning( "unnecessary escape sequence '\\%s' (did you mean '\\\\%s'?)" % (c, c), pos=source_pos) return out
def detect_unused_vars(model): """ Warns about unused variables -- they may indicate typos. """ # First of all, iterate over all variables and mark their usage of other # variables. Notice that it's possible that some code explicitly marked # variables as used with mark_variables_in_expr_as_used() before this step. for var in model.all_variables(): usage_tracker.visit(var.value) # Not emit warnings for unused variables. import re regex_vs_option = re.compile(r'vs[0-9]+\.option\.') for var in model.all_variables(): if (not var.is_property and not usage_tracker.is_used(var) and # FIXME: Handle these cases properly. Have a properties group # declaration similar to Property, with type checking and # automated docs and all. Then test for it here as other # properties are tested for. not regex_vs_option.match(var.name) and # FIXME: Handle this case properly. var.name != "configurations"): warning('variable "%s" is never used', var.name, pos=var.value.pos)
def _get_matching_project_config(cfg, prj): """ Returns best match project configuration for given solution configuration. """ with error_context(prj): # If the project doesn't have any configurations, it means that we # failed to parse it properly, presumably because it defines its # configurations (and platforms, see _get_matching_project_platform() # too) in a separately imported file. Ideal would be to follow the # import chain, but this is not trivial, e.g. we would need to parse # and evaluate MSBuild functions to find the full path of the file # being imported, so for now we just optimistically assume that the # project supports all solution configurations because it's the only # thing we can do, the only alternative would be to refuse to use it # completely. if cfg in prj.configurations or not prj.configurations: return cfg # else: try to find a configuration closest to the given one, i.e. # the one from which it inherits via the minimal number of # intermediate configurations: compatibles = [] for pc in prj.configurations: degree = cfg.derived_from(pc) if degree: compatibles.append((degree, pc)) if not compatibles: # if we don't have any project configurations from which this # one inherits, check if we have any which inherit from this # one themselves as they should be a reasonably good fallback: for pc in prj.configurations: degree = pc.derived_from(cfg) if degree: compatibles.append((degree, pc)) if compatibles: if len(compatibles) > 1: compatibles.sort() # It can happen that we have 2 project configurations # inheriting from the solution configuration with the same # degree. In this case there we can't really make the # right choice automatically, so we must warn the user. degree = compatibles[0][0] if compatibles[1][0] == degree: good_ones = [x[1].name for x in compatibles if x[0] == degree] warning("project %s: no unambiguous choice of project configuration to use for the solution configuration \"%s\", equally good candidates are: \"%s\"", prj.projectfile, cfg.name, '", "'.join(good_ones)) degree, ret = compatibles[0] logger.debug("solution config \"%s\" -> project %s config \"%s\" (dg %d)", cfg.name, prj.projectfile, ret.name, degree) return ret # if all failed, just pick the first config, but at least try to match # debug/release setting: compatibles = [x for x in prj.configurations if x.is_debug == cfg.is_debug] if compatibles: ret = compatibles[0] warning("project %s: using unrelated project configuration \"%s\" for solution configuration \"%s\"", prj.projectfile, ret.name, cfg.name) return ret else: ret = prj.configurations[0] warning("project %s: using incompatible project configuration \"%s\" for solution configuration \"%s\"", prj.projectfile, ret.name, cfg.name) return ret