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 _do_format_node(self, n, indent): attrs = self._get_quoted_nonempty_attrs(n) if n.children: children_markup = [] assert not n.text, "nodes with both text and children not implemented" subindent = indent + self.indent_step for key, value in n.children: if isinstance(value, Node): assert key == value.name children_markup.append(self._do_format_node(value, subindent)) else: try: v = escape(self.format_value(value)) if v: children_markup.append("%s<%s>%s</%s>\n" % (subindent, key, v, key)) # else: empty value, don't write that except CannotDetermineError as e: with error_context(value): raise Error("cannot set property \"%s\" to non-constant expression \"%s\" (%s)" % (key, value, e.msg)) children_markup = "".join(children_markup) else: children_markup = None text = self.format_value(n.text) if n.text else None return self.format_node(n.name, attrs, text, children_markup, indent)
def add_module(self, ast, parent): """ Adds parsed AST to the model, without doing any optimizations. May be called more than once, with different parsed files. :param ast: AST of the input file, as returned by :func:`bkl.parser.parse_file`. """ logger.info("processing %s", ast.filename) submodules = [] b = Builder(on_submodule=lambda fn, pos: submodules.append((fn, pos))) module = b.create_model(ast, parent) while submodules: sub_filename, sub_pos = submodules[0] submodules.pop(0) try: sub_ast = parse_file(sub_filename) except IOError as e: if e.filename: msg = "%s: %s" % (e.strerror, e.filename) else: msg = e.strerror raise Error(msg, pos=sub_pos) self.add_module(sub_ast, module)
def path(self, e): if e.anchor == bkl.expr.ANCHOR_BUILDDIR and self.toolset is not None: if self.target is None: raise Error("@builddir references are not allowed outside of targets", pos=e.pos) bdir = self._builddir(self.target) e = bkl.expr.PathExpr(bdir.components + e.components, bdir.anchor, bdir.anchor_file, pos=e.pos) if e.anchor == bkl.expr.ANCHOR_SRCDIR: assert self.module is not None if e.anchor_file: source_file = e.anchor_file elif e.pos and e.pos.filename: source_file = e.pos.filename else: source_file = self.module.source_file prefix = self._src_prefix(source_file) components = e.components if prefix is not None: # Don't mess the path if it starts with user setting and so # should be treated as absolute. if not e.is_external_absolute(): components = prefix + components e = bkl.expr.PathExpr(components, bkl.expr.ANCHOR_TOP_SRCDIR, None, pos=e.pos) return e
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 version(self): v = self.xml.get("Version") if v and "," in v: # vcproj files written under some locales (French, Czech) may use # ',' as decimal point character. v = v.replace(",", ".") if v == "7.10": return 7.1 elif v == "8.00": return 8 elif v == "9.00": return 9 else: raise Error("unrecognized version of Visual Studio project %s: Version=\"%s\"" % ( self.projectfile, v))
def _format_dep(t): g = build_graphs[t].main if len(g.outputs) == 0: assert g.name if t.parent is not module: raise Error("cross-module dependencies on phony targets (\"%s\") not supported yet" % t.name) # TODO out = g.name else: # FIXME: handle multi-output nodes too assert len(g.outputs) == 1 out = g.outputs[0] return expr_fmt.format(out)
def load_from_file(filename): """ Load a Bakefile plugin from given file. """ import os.path import imp from bkl.error import Error basename = os.path.splitext(os.path.basename(filename))[0] if basename.startswith("bkl.plugins."): modname = basename else: modname = "bkl.plugins.%s" % basename.replace(".", "_") if modname in sys.modules: prev_file = sys.modules[modname].__file__ if filename == prev_file or filename == prev_file[:-1]: #.pyc->.py # plugin already loaded from this file, skip it __logger.debug( "plugin %s from %s is already loaded, nothing to do", modname, filename) return else: raise Error( "cannot load plugin %s from %s: plugin with the same name already loaded from %s" % (modname, filename, prev_file)) try: global __all__ __logger.debug("loading plugin %s from %s", modname, filename) globals()[basename] = imp.load_source(modname, filename) __all__.append(basename) except Error: raise except IOError as e: raise Error("failed to load plugin %s:\n%s" % (filename, e)) except Exception: import traceback raise Error("failed to load plugin %s:\n%s" % (filename, traceback.format_exc()))
def reference(self, e): var = e.get_variable() if var is None: # reference to default value of a property return if var in self.stack: # TODO: include complete stack of messages+positions raise Error( 'variable "%s" is defined recursively, references itself' % var.name, pos=e.pos) else: self.check(var)
def version(self): v = self.xml.get("ToolsVersion") if v == "14.0": return 14 elif v != "4.0": raise Error("unrecognized version of Visual Studio project %s: ToolsVersion=\"%s\"" %( self.projectfile, v)) # TODO-PY26: use "PropertyGroup[@Label='Configuration']" t = self.xml.findtext("{%(ms)s}PropertyGroup/{%(ms)s}PlatformToolset" % XMLNS) if t == "v110": return 11 else: return 10
def multifile_target(self, outputs, outfiles, deps, commands): """ Returns string with target definition for targets that produce multiple files. A typical example is Bison parser generator, which produces both .c and .h files. :param outputs: List of output files of the rule, as objects. :param outfiles: List of output files of the rule, as strings. :param deps: See target() :param commands: See target() """ # TODO: Implement these. Could you pattern rules with GNU make, # or stamp files. raise Error("rules with multiple output files not implemented yet (%s from %s)" % (outfiles, deps))
def multifile_target(self, outputs, outfiles, deps, commands): # Use a helper intermediate target to handle multiple outputs of a rule, # because we can't easily use GNU Make's pattern rules matching. The # absence of an intermediate file is not a problem and does not cause # spurious builds. See for details: # http://www.gnu.org/software/make/manual/html_node/Chained-Rules.html # http://stackoverflow.com/a/10609434/237188 for c in commands: if '$@' in c: raise Error("The use of $@ or %%(out) not supported with multiple outputs (in \"%s\")" % c) inter_name = ".dummy_" + "_".join("_".join(c.as_py() for c in f.components) for f in outputs) return "\n".join([ "%s: %s" % (" ".join(outfiles), inter_name), ".INTERMEDIATE: %s" % inter_name, self.target(inter_name, deps, commands) ])
def configurations(self): known = self._project.configurations lst = [] for name in filter_duplicates(self._extract_configurations_names()): try: lst.append(known[name]) except KeyError: if "Debug" in name: base = known["Debug"] elif "Release" in name: base = known["Release"] else: raise Error("don't know whether the \"%s\" configuration from external %s is debug or release; please define it in your bakefile explicitly" % (name, self.projectfile), pos=self.source_pos) cfg = base.create_derived(name) self._project.add_configuration(cfg) lst.append(cfg) return lst
def __init__(self, filename, eol, charset="utf-8", creator=None, create_for=None): """ Creates output file. :param filename: Name of the output file. Should be either relative to CWD or absolute; the latter is recommended. :param eol: Line endings to use. One of EOL_WINDOWS and EOL_UNIX. :param charset: Charset to use if Unicode string is passed to write(). :param creator: Who is creating the file; typically toolset object. :param create_for: Object the file is created for, e.g. a module or a target. """ if filename in _all_written_files: creator1, create_for1 = _all_written_files[filename] from bkl.error import Error raise Error("conflict in file %(filename)s, generated both by %(creator1)s for %(create_for1)s and %(creator)s for %(create_for)s" % locals()) _all_written_files[filename] = (creator, create_for) self.filename = filename self.eol = eol self.charset = charset self.text = ""
def placeholder(self, e): name = e.var if name == "arch": raise Error("multi-arch builds are not supported by makefiles ($(arch) referenced)", pos=e.pos) return "$(%s)" % name