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