| #!/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() |