#!/usr/bin/env python # # Kuroga, single python file meta-build system for ninja # https://github.com/lighttransport/kuroga # # Requirements: python 2.6 or 2.7 # # Usage: $ python kuroga.py input.py # import imp import re import textwrap import glob import os import sys # gcc preset def add_gnu_rule(ninja): ninja.rule('gnucxx', description='CXX $out', command='$gnucxx -MMD -MF $out.d $gnudefines $gnuincludes $gnucxxflags -c $in -o $out', depfile='$out.d', deps='gcc') ninja.rule('gnucc', description='CC $out', command='$gnucc -MMD -MF $out.d $gnudefines $gnuincludes $gnucflags -c $in -o $out', depfile='$out.d', deps='gcc') ninja.rule('gnulink', description='LINK $out', pool='link_pool', command='$gnuld -o $out $in $libs $gnuldflags') ninja.rule('gnuar', description='AR $out', pool='link_pool', command='$gnuar rsc $out $in') ninja.rule('gnustamp', description='STAMP $out', command='touch $out') ninja.newline() ninja.variable('gnucxx', 'g++') ninja.variable('gnucc', 'gcc') ninja.variable('gnuld', '$gnucxx') ninja.variable('gnuar', 'ar') ninja.newline() # clang preset def add_clang_rule(ninja): ninja.rule('clangcxx', description='CXX $out', command='$clangcxx -MMD -MF $out.d $clangdefines $clangincludes $clangcxxflags -c $in -o $out', depfile='$out.d', deps='gcc') ninja.rule('clangcc', description='CC $out', command='$clangcc -MMD -MF $out.d $clangdefines $clangincludes $clangcflags -c $in -o $out', depfile='$out.d', deps='gcc') ninja.rule('clanglink', description='LINK $out', pool='link_pool', command='$clangld -o $out $in $libs $clangldflags') ninja.rule('clangar', description='AR $out', pool='link_pool', command='$clangar rsc $out $in') ninja.rule('clangstamp', description='STAMP $out', command='touch $out') ninja.newline() ninja.variable('clangcxx', 'clang++') ninja.variable('clangcc', 'clang') ninja.variable('clangld', '$clangcxx') ninja.variable('clangar', 'ar') ninja.newline() # msvc preset def add_msvc_rule(ninja): ninja.rule('msvccxx', description='CXX $out', command='$msvccxx /TP /showIncludes $msvcdefines $msvcincludes $msvccxxflags -c $in /Fo$out', depfile='$out.d', deps='msvc') ninja.rule('msvccc', description='CC $out', command='$msvccc /TC /showIncludes $msvcdefines $msvcincludes $msvccflags -c $in /Fo$out', depfile='$out.d', deps='msvc') ninja.rule('msvclink', description='LINK $out', pool='link_pool', command='$msvcld $msvcldflags $in $libs /OUT:$out') ninja.rule('msvcar', description='AR $out', pool='link_pool', command='$msvcar $in /OUT:$out') #ninja.rule('msvcstamp', description='STAMP $out', command='touch $out') ninja.newline() ninja.variable('msvccxx', 'cl.exe') ninja.variable('msvccc', 'cl.exe') ninja.variable('msvcld', 'link.exe') ninja.variable('msvcar', 'lib.exe') ninja.newline() # -- from ninja_syntax.py -- def escape_path(word): return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') class Writer(object): def __init__(self, output, width=78): self.output = output self.width = width def newline(self): self.output.write('\n') def comment(self, text, has_path=False): for line in textwrap.wrap(text, self.width - 2, break_long_words=False, break_on_hyphens=False): self.output.write('# ' + line + '\n') def variable(self, key, value, indent=0): if value is None: return if isinstance(value, list): value = ' '.join(filter(None, value)) # Filter out empty strings. self._line('%s = %s' % (key, value), indent) def pool(self, name, depth): self._line('pool %s' % name) self.variable('depth', depth, indent=1) def rule(self, name, command, description=None, depfile=None, generator=False, pool=None, restat=False, rspfile=None, rspfile_content=None, deps=None): self._line('rule %s' % name) self.variable('command', command, indent=1) if description: self.variable('description', description, indent=1) if depfile: self.variable('depfile', depfile, indent=1) if generator: self.variable('generator', '1', indent=1) if pool: self.variable('pool', pool, indent=1) if restat: self.variable('restat', '1', indent=1) if rspfile: self.variable('rspfile', rspfile, indent=1) if rspfile_content: self.variable('rspfile_content', rspfile_content, indent=1) if deps: self.variable('deps', deps, indent=1) def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, variables=None): outputs = as_list(outputs) out_outputs = [escape_path(x) for x in outputs] all_inputs = [escape_path(x) for x in as_list(inputs)] if implicit: implicit = [escape_path(x) for x in as_list(implicit)] all_inputs.append('|') all_inputs.extend(implicit) if order_only: order_only = [escape_path(x) for x in as_list(order_only)] all_inputs.append('||') all_inputs.extend(order_only) self._line('build %s: %s' % (' '.join(out_outputs), ' '.join([rule] + all_inputs))) if variables: if isinstance(variables, dict): iterator = iter(variables.items()) else: iterator = iter(variables) for key, val in iterator: self.variable(key, val, indent=1) return outputs def include(self, path): self._line('include %s' % path) def subninja(self, path): self._line('subninja %s' % path) def default(self, paths): self._line('default %s' % ' '.join(as_list(paths))) def _count_dollars_before_index(self, s, i): """Returns the number of '$' characters right in front of s[i].""" dollar_count = 0 dollar_index = i - 1 while dollar_index > 0 and s[dollar_index] == '$': dollar_count += 1 dollar_index -= 1 return dollar_count def _line(self, text, indent=0): """Write 'text' word-wrapped at self.width characters.""" leading_space = ' ' * indent while len(leading_space) + len(text) > self.width: # The text is too wide; wrap if possible. # Find the rightmost space that would obey our width constraint and # that's not an escaped space. available_space = self.width - len(leading_space) - len(' $') space = available_space while True: space = text.rfind(' ', 0, space) if (space < 0 or self._count_dollars_before_index(text, space) % 2 == 0): break if space < 0: # No such space; just use the first unescaped space we can find. space = available_space - 1 while True: space = text.find(' ', space + 1) if (space < 0 or self._count_dollars_before_index(text, space) % 2 == 0): break if space < 0: # Give up on breaking. break self.output.write(leading_space + text[0:space] + ' $\n') text = text[space+1:] # Subsequent lines are continuations, so indent them. leading_space = ' ' * (indent+2) self.output.write(leading_space + text + '\n') def close(self): self.output.close() def as_list(input): if input is None: return [] if isinstance(input, list): return input return [input] # -- end from ninja_syntax.py -- def gen(ninja, toolchain, config): ninja.variable('ninja_required_version', '1.4') ninja.newline() if hasattr(config, "builddir"): builddir = config.builddir[toolchain] ninja.variable(toolchain + 'builddir', builddir) else: builddir = '' ninja.variable(toolchain + 'defines', config.defines[toolchain] or []) ninja.variable(toolchain + 'includes', config.includes[toolchain] or []) ninja.variable(toolchain + 'cflags', config.cflags[toolchain] or []) ninja.variable(toolchain + 'cxxflags', config.cxxflags[toolchain] or []) ninja.variable(toolchain + 'ldflags', config.ldflags[toolchain] or []) ninja.newline() if hasattr(config, "link_pool_depth"): ninja.pool('link_pool', depth=config.link_pool_depth) else: ninja.pool('link_pool', depth=4) ninja.newline() # Add default toolchain(gnu, clang and msvc) add_gnu_rule(ninja) add_clang_rule(ninja) add_msvc_rule(ninja) obj_files = [] cc = toolchain + 'cc' cxx = toolchain + 'cxx' link = toolchain + 'link' ar = toolchain + 'ar' if hasattr(config, "cxx_files"): for src in config.cxx_files: srcfile = src obj = os.path.splitext(srcfile)[0] + '.o' obj = os.path.join(builddir, obj); obj_files.append(obj) ninja.build(obj, cxx, srcfile) ninja.newline() if hasattr(config, "c_files"): for src in config.c_files: srcfile = src obj = os.path.splitext(srcfile)[0] + '.o' obj = os.path.join(builddir, obj); obj_files.append(obj) ninja.build(obj, cc, srcfile) ninja.newline() targetlist = [] if hasattr(config, "exe"): ninja.build(config.exe, link, obj_files) targetlist.append(config.exe) if hasattr(config, "staticlib"): ninja.build(config.staticlib, ar, obj_files) targetlist.append(config.staticlib) ninja.build('all', 'phony', targetlist) ninja.newline() ninja.default('all') def main(): if len(sys.argv) < 2: print("Usage: python kuroga.py config.py") sys.exit(1) config = imp.load_source("config", sys.argv[1]) f = open('build.ninja', 'w') ninja = Writer(f) if hasattr(config, "register_toolchain"): config.register_toolchain(ninja) gen(ninja, config.toolchain, config) f.close() main()