diff options
-rw-r--r-- | .scons2ninja.conf | 24 | ||||
-rwxr-xr-x | BuildTools/scons2ninja.py | 589 | ||||
-rwxr-xr-x | BuildTools/scons2ninja.rb | 562 |
3 files changed, 601 insertions, 574 deletions
diff --git a/.scons2ninja.conf b/.scons2ninja.conf index 3770b06..883cccd 100644 --- a/.scons2ninja.conf +++ b/.scons2ninja.conf @@ -1,16 +1,16 @@ -$scons_cmd = "python 3rdParty/SCons/scons.py" -$scons_dependencies += Dir["BuildTools/SCons/**/*.py"] + Dir["BuildTools/SCons/SCons*"] +import glob -ninja_post do |ninja| +scons_cmd = "python 3rdParty/SCons/scons.py" +scons_dependencies += glob.glob("BuildTools/SCons/**/*.py") + glob.glob("BuildTools/SCons/SCons*") + +def ninja_post(ninja) : # Unit tests - ninja.build 'check', 'run', to_native_path("QA/UnitTest/checker#{EXE_SUFFIX}") + ninja.build('check', 'run', os.path.join('QA', 'UnitTest', 'checker' + EXE_SUFFIX)) # Swift binary - if RUBY_PLATFORM =~ /(win32|mingw32)/ - ninja.build ['Swift', 'swift'], 'phony', "Swift\\QtUI\\Swift.exe" - elsif RUBY_PLATFORM =~ /linux/ - ninja.build ['Swift', 'swift'], 'phony', 'Swift/QtUI/swift-im' - else - ninja.build ['Swift', 'swift'], 'phony', /Swift\/QtUI\/Swift.app\/(.*)/ - end -end + if sys.platform == 'win32' : + ninja.build(['Swift', 'swift'], 'phony', re.compile('Swift\\\\QtUI\\\\Swift\\\\(.*)')) + elif sys.platform == 'posix' : + ninja.build(['Swift', 'swift'], 'phony', 'Swift/QtUI/swift-im') + else : + ninja.build(['Swift', 'swift'], 'phony', re.compile('Swift/QtUI/Swift\.app/(.*)')) diff --git a/BuildTools/scons2ninja.py b/BuildTools/scons2ninja.py new file mode 100755 index 0000000..1af52d7 --- /dev/null +++ b/BuildTools/scons2ninja.py @@ -0,0 +1,589 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +################################################################################ +# +# scons2ninja: A script to create a Ninja build file from SCons. +# +# Copyright (c) 2013 Remko Tronçon +# Licensed under the simplified BSD license. +# See COPYING for details. +# +################################################################################ + +import re, os, os.path, subprocess, sys, fnmatch + +################################################################################ +# Helper methods & variables +################################################################################ + +SCRIPT = sys.argv[0] +SCONS_ARGS = ' '.join(sys.argv[1:]) + +# TODO: Make this a tool-specific map +BINARY_FLAGS = ["-framework", "-arch", "-x", "--output-format", "-isystem", "-include"] + +if sys.platform == 'win32' : + LIB_PREFIX = "" + LIB_SUFFIX = "" + EXE_SUFFIX = ".exe" +else : + LIB_PREFIX = "lib" + LIB_SUFFIX = ".a" + EXE_SUFFIX = "" + +def is_regexp(x) : + return 'match' in dir(x) + +def is_list(l) : + return type(l) is list + +def escape(s) : + return s.replace(' ', '$ ').replace(':', '$:') + +def to_list(l) : + if not l : + return [] + if is_list(l) : + return l + return [l] + +def partition(l, f) : + x = [] + y = [] + for v in l : + if f(v) : + x.append(v) + else : + y.append(v) + return (x, y) + +def get_unary_flags(prefix, flags) : + return [x[len(prefix):] for x in flags if x.lower().startswith(prefix.lower())] + +def extract_unary_flags(prefix, flags) : + f1, f2 = partition(flags, lambda x : x.lower().startswith(prefix.lower())) + return ([f[len(prefix):] for f in f1], f2) + +def extract_unary_flag(prefix, flags) : + flag, flags = extract_unary_flags(prefix, flags) + return (flag[0], flags) + +def extract_binary_flag(prefix, flags) : + i = flags.index(prefix) + flag = flags[i + 1] + del flags[i] + del flags[i] + return (flag, flags) + +def get_non_flags(flags) : + skip = False + result = [] + for f in flags : + if skip : + skip = False + elif f in BINARY_FLAGS : + skip = True + elif not f.startswith("/") and not f.startswith("-") : + result.append(f) + return result + +def extract_non_flags(flags) : + non_flags = get_non_flags(flags) + return (non_flags, filter(lambda x : x not in non_flags, flags)) + +def get_dependencies(target, build_targets) : + result = [] + queue = list(dependencies.get(target, [])) + while len(queue) > 0 : + n = queue.pop() + # Filter out Value() results + if n in build_targets or os.path.exists(n) : + result.append(n) + queue += list(dependencies.get(n, [])) + return result + +def get_built_libs(libs, libpaths, outputs) : + canonical_outputs = [os.path.abspath(p) for p in outputs] + result = [] + for libpath in libpaths : + for lib in libs : + lib_libpath = os.path.join(libpath, LIB_PREFIX + lib + LIB_SUFFIX) + if os.path.abspath(lib_libpath) in canonical_outputs : + result.append(lib_libpath) + return result + +def parse_tool_command(line) : + command = line.split(' ') + flags = command[1:] + tool = os.path.splitext(os.path.basename(command[0]))[0] + if tool.startswith('clang++') or tool.startswith('g++') : + tool = "cxx" + elif tool.startswith('clang') or tool.startswith('gcc') : + tool = "cc" + if tool in ["cc", "cxx"] and not "-c" in flags : + tool = "glink" + tool = tool.replace('-qt4', '') + return tool, command, flags + +def rglob(pattern, root = '.') : + return [os.path.join(path, f) for path, dirs, files in os.walk(root) for f in fnmatch.filter(files, pattern)] + +################################################################################ +# Helper for building Ninja files +################################################################################ + +class NinjaBuilder : + def __init__(self) : + self._header = "" + self.variables = "" + self.rules = "" + self._build = "" + self.pools = "" + self._flags = {} + self.targets = [] + + def header(self, text) : + self._header += text + "\n" + + def rule(self, name, **kwargs) : + self.rules += "rule " + name + "\n" + for k, v in kwargs.iteritems() : + self.rules += " " + str(k) + " = " + str(v) + "\n" + self.rules += "\n" + + def pool(self, name, **kwargs) : + self.pools += "pool " + name + "\n" + for k, v in kwargs.iteritems() : + self.pools += " " + str(k) + " = " + str(v) + "\n" + self.pools += "\n" + + def variable(self, name, value) : + self.variables += str(name) + " = " + str(value) + "\n" + + def build(self, target, rule, sources = None, **kwargs) : + self._build += "build " + self.to_string(target) + ": " + rule + if sources : + self._build += " " + self.to_string(sources) + if 'deps' in kwargs and kwargs['deps'] : + self._build += " | " + self.to_string(kwargs["deps"]) + if 'order_deps' in kwargs : + self._build += " || " + self.to_string(kwargs['order_deps']) + self._build += "\n" + for var, value in kwargs.iteritems() : + if var in ['deps', 'order_deps'] : + continue + value = self.to_string(value) + if var.endswith("flags") : + value = self.get_flags_variable(var, value) + self._build += " " + var + " = " + value + "\n" + self.targets += to_list(target) + + def header_targets(self) : + return [x for x in self.targets if x.endswith('.h') or x.endswith('.hh')] + + def serialize(self) : + result = "" + result += self._header + "\n" + result += self.variables + "\n" + for prefix in self._flags.values() : + for k, v in prefix.iteritems() : + result += v + " = " + k + "\n" + result += "\n" + result += self.pools + "\n" + result += self.rules + "\n" + result += self._build + "\n" + return result + + def to_string(self, lst) : + if is_list(lst) : + return ' '.join([escape(x) for x in lst]) + if is_regexp(lst) : + return ' '.join([escape(x) for x in self.targets if lst.match(x)]) + return escape(lst) + + def get_flags_variable(self, flags_type, flags) : + if len(flags) == 0 : + return '' + if flags_type not in self._flags : + self._flags[flags_type] = {} + type_flags = self._flags[flags_type] + if flags not in type_flags : + type_flags[flags] = flags_type + "_" + str(len(type_flags)) + return "$" + type_flags[flags] + + +################################################################################ +# Configuration +################################################################################ + +ninja_post = [] +scons_cmd = "scons" +scons_dependencies = ['SConstruct'] + rglob('SConscript') + +CONFIGURATION_FILE = '.scons2ninja.conf' +execfile(CONFIGURATION_FILE) + +scons_dependencies = [os.path.normpath(x) for x in scons_dependencies] + + +################################################################################ +# Rules +################################################################################ + +ninja = NinjaBuilder() + +ninja.pool('scons_pool', depth = 1) + +if sys.platform == 'win32' : + ninja.rule('cl', + deps = 'msvc', + command = '$cl /showIncludes $clflags -c $in /Fo$out', + description = 'CXX $out') + + ninja.rule('link', + command = '$link $in $linkflags $libs /out:$out', + description = 'LINK $out') + + ninja.rule('link_mt', + command = '$link $in $linkflags $libs /out:$out ; $mt $mtflags', + description = 'LINK $out') + + ninja.rule('lib', + command = '$lib $libflags /out:$out $in', + description = 'AR $out') + + ninja.rule('rc', + command = '$rc $rcflags /Fo$out $in', + description = 'RC $out') + + # SCons doesn't touch files if they didn't change, which makes + # ninja rebuild the file over and over again. There's no touch on Windows :( + # Could implement it with a script, but for now, delete the file if + # this problem occurs. I'll fix it if it occurs too much. + ninja.rule('scons', + command = scons_cmd + " ${scons_args} $out", + pool = 'scons_pool', + description = 'GEN $out') + + ninja.rule('install', command = 'cmd /c copy $in $out') + ninja.rule('run', command = '$in') +else : + ninja.rule('cxx', + deps = 'gcc', + depfile = '$out.d', + command = '$cxx -MMD -MF $out.d $cxxflags -c $in -o $out', + description = 'CXX $out') + + ninja.rule('cc', + deps = 'gcc', + depfile = '$out.d', + command = '$cc -MMD -MF $out.d $ccflags -c $in -o $out', + description = 'CC $out') + + ninja.rule('link', + command = '$glink -o $out $in $linkflags', + description = 'LINK $out') + + ninja.rule('ar', + command = 'ar $arflags $out $in && ranlib $out', + description = 'AR $out') + + # SCons doesn't touch files if they didn't change, which makes + # ninja rebuild the file over and over again. Touching solves this. + ninja.rule('scons', + command = scons_cmd + " $out && touch $out", + pool = 'scons_pool', + description = 'GEN $out') + + ninja.rule('install', command = 'install $in $out') + ninja.rule('run', command = './$in') + + +ninja.rule('moc', + command = '$moc $mocflags -o $out $in', + description = 'MOC $out') + +ninja.rule('rcc', + command = '$rcc $rccflags -name $name -o $out $in', + description = 'RCC $out') + +ninja.rule('uic', + command = '$uic $uicflags -o $out $in', + description = 'UIC $out') + +ninja.rule('lrelease', + command = '$lrelease $lreleaseflags $in -qm $out', + description = 'LRELEASE $out') + +ninja.rule('ibtool', + command = '$ibtool $ibtoolflags --compile $out $in', + description = 'IBTOOL $out') + +ninja.rule('generator', + command = "python " + SCRIPT + " ${scons_args}", + pool = 'scons_pool', + generator = '1', + description = 'Regenerating build.ninja') + + +################################################################################ +# Build Statements +################################################################################ + +scons_generate_cmd = scons_cmd + " " + SCONS_ARGS + " --tree=all,prune dump_trace=1" +#scons_generate_cmd = 'cmd /c type scons2ninja.in' +#scons_generate_cmd = 'cat scons2ninja.in' + +# Pass 1: Parse dependencies (and prefilter some build rules) +build_lines = [] +dependencies = {} +mtflags = {} +previous_file = None +f = subprocess.Popen(scons_generate_cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True) +stage = 'preamble' +skip_nth_line = -1 +stack = ['.'] +for line in f.stdout.readlines() : + line = line.rstrip() + + # Skip lines if requested from previous command + if skip_nth_line >= 0 : + skip_nth_line -= 1 + if skip_nth_line == 0 : + continue + + if line.startswith('scons: done building targets') : + break + + if stage == "preamble" : + # Pass all lines from the SCons configuration step to output + if re.match("^scons: Building targets ...", line) : + stage = "build" + else : + print line + + elif stage == "build" : + if line.startswith('+-') : + stage = "dependencies" + elif re.match("^Using tempfile", line) : + # Ignore response files from MSVS + skip_nth_line = 2 + else : + build_lines.append(line) + + # Already detect targets that will need 'mt' + tool, _, flags = parse_tool_command(line) + if tool == 'mt' : + target = get_unary_flags("-outputresource:", flags)[0] + target = target[0:target.index(';')] + mtflags[target] = flags + + elif stage == "dependencies" : + if not re.match('^[\s|]+\+\-', line) : + # Work around bug in SCons that splits output over multiple lines + continue + + level = line.index('+-') / 2 + filename = line[level*2+2:] + if filename.startswith('[') : + filename = filename[1:-1] + + # Check if we use the 'fixed' format which escapes filenamenames + if filename.startswith('\'') and filename.endswith('\'') : + filename = eval(filename) + + if level < len(stack) : + stack = stack[0:level] + elif level > len(stack) : + if level != len(stack) + 1 : + raise Exception("Internal Error" ) + stack.append(previous_filename) + + # Skip absolute paths + if not os.path.isabs(filename) : + target = stack[-1] + if target not in dependencies : + dependencies[target] = [] + dependencies[target].append(filename) + previous_filename = filename + +if f.wait() != 0 : + print "Error calling '" + scons_generate_cmd + "'" + print f.stderr.read() + exit(-1) + +# Pass 2: Parse build rules +tools = {} +for line in build_lines : + # Custom python function + m = re.match('^(\w+)\(\[([^\]]*)\]', line) + if m : + out = [x[1:-1] for x in m.group(2).split(',')] + for x in out : + # 'Note' = To be more correct, deps should also include $scons_dependencies, + # but this regenerates a bit too often, so leaving it out for now. + ninja.build(x, 'scons', None, deps = sorted(get_dependencies(x, ninja.targets))) + continue + + + # TextFile + m = re.match("^Creating '([^']+)'", line) + if m : + out = m.group(1) + # Note: To be more correct, deps should also include $scons_dependencies, + # but this regenerates a bit too often, so leaving it out for now. + ninja.build(out, 'scons', None, deps = sorted(get_dependencies(out, ninja.targets))) + continue + + # Install + m = re.match('^Install file: "(.*)" as "(.*)"', line) + if m : + ninja.build(m.group(2), 'install', m.group(1)) + continue + + m = re.match('^Install directory: "(.*)" as "(.*)"', line) + if m : + for source in rglob('*', m.group(1)) : + if os.path.isdir(source) : + continue + target = os.path.join(m.group(2), os.path.relpath(source, m.group(1))) + ninja.build(target, 'install', source) + continue + + # Tools + tool, command, flags = parse_tool_command(line) + tools[tool] = command[0] + + ############################################################ + # clang/gcc tools + ############################################################ + + if tool == 'cc': + out, flags = extract_binary_flag("-o", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'cc', files, order_deps = '_generated_headers', ccflags = flags) + + elif tool == 'cxx': + out, flags = extract_binary_flag("-o", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'cxx', files, order_deps = '_generated_headers', cxxflags = flags) + + elif tool == 'glink': + out, flags = extract_binary_flag("-o", flags) + files, flags = extract_non_flags(flags) + libs = get_unary_flags('-l', flags) + libpaths = get_unary_flags("-L", flags) + deps = get_built_libs(libs, libpaths, ninja.targets) + ninja.build(out, 'link', files, deps = sorted(deps), linkflags = flags) + + elif tool == 'ar': + objects, flags = partition(flags, lambda x: x.endswith('.o')) + libs, flags = partition(flags, lambda x: x.endswith('.a')) + out = libs[0] + ninja.build(out, 'ar', objects, arflags = flags) + + elif tool == 'ranlib': + pass + + + ############################################################ + # MSVC tools + ############################################################ + + elif tool == 'cl': + out, flags = extract_unary_flag("/Fo", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'cl', files, order_deps = '_generated_headers', clflags = flags) + + elif tool == 'lib': + out, flags = extract_unary_flag("/out:", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'lib', files, libflags = flags) + + elif tool == 'link': + objects, flags = partition(flags, lambda x: x.endswith('.obj') or x.endswith('.res')) + out, flags = extract_unary_flag("/out:", flags) + libs, flags = partition(flags, lambda x: not x.startswith("/") and x.endswith(".lib")) + libpaths = get_unary_flags("/libpath:", flags) + deps = get_built_libs(libs, libpaths, ninja.targets) + if out in mtflags : + ninja.build(out, 'link_mt', objects, deps = sorted(deps), + libs = libs, linkflags = flags, mtflags = mtflags[out]) + else : + ninja.build(out, 'link', objects, deps = sorted(deps), + libs = libs, linkflags = flags) + + elif tool == 'rc': + out, flags = extract_unary_flag("/fo", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'rc', files[0], order_deps = '_generated_headers', rcflags = flags) + + elif tool == 'mt': + # Already handled + pass + + ############################################################ + # Qt tools + ############################################################ + + elif tool == 'moc': + out, flags = extract_binary_flag("-o", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'moc', files, mocflags = flags) + + elif tool == 'uic': + out, flags = extract_binary_flag("-o", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'uic', files, uicflags = flags) + + elif tool == 'lrelease': + out, flags = extract_binary_flag("-qm", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'lrelease', files, lreleaseflags = flags) + + elif tool == 'rcc': + out, flags = extract_binary_flag("-o", flags) + name, flags = extract_binary_flag("-name", flags) + files, flags = extract_non_flags(flags) + deps = list(set(get_dependencies(out, ninja.targets)) - set(files)) + ninja.build(out, 'rcc', files, deps = sorted(deps), name = name, rccflags = flags) + + ############################################################ + # OS X tools + ############################################################ + + elif tool == 'ibtool': + out, flags = extract_binary_flag("--compile", flags) + files, flags = extract_non_flags(flags) + ninja.build(out, 'ibtool', files, ibtoolflags = flags) + + else : + raise Exception("Unknown tool: '" + line + "'") + + +# Phony target for all generated headers, used as an order-only depency from all C/C++ sources +ninja.build('_generated_headers', 'phony', ninja.header_targets()) + +# Regenerate build.ninja file +ninja.build('build.ninja', 'generator', [], deps = [SCRIPT, CONFIGURATION_FILE] + scons_dependencies) + +# Header & variables +ninja.header("# This file is generated by " + SCRIPT) +ninja.variable("ninja_required_version", "1.3") +ninja.variable("scons_args", SCONS_ARGS) +for k, v in tools.iteritems() : + ninja.variable(k, v) + +# Extra customizations +if 'ninja_post' in dir() : + ninja_post(ninja) + + +################################################################################ +# Result +################################################################################ + +f = open("build.ninja", "w") +f.write(ninja.serialize()) +f.close() diff --git a/BuildTools/scons2ninja.rb b/BuildTools/scons2ninja.rb deleted file mode 100755 index 6184f36..0000000 --- a/BuildTools/scons2ninja.rb +++ /dev/null @@ -1,562 +0,0 @@ -#!/usr/bin/env ruby - -################################################################################ -# -# scons2ninja: A script to create a Ninja build file from SCons. -# -# Copyright (c) 2013 Remko Tronçon -# Licensed under the simplified BSD license. -# See COPYING for details. -# -################################################################################ - -require 'pathname' -require 'open3' - - -################################################################################ -# Helper for building Ninja files -################################################################################ - -class NinjaBuilder - attr_reader :targets - - def initialize - @header = "" - @variables = "" - @rules = "" - @build = "" - @pools = "" - @flags = Hash.new{ |h,k| h[k] = Hash.new() } - @targets = [] - end - - def header(text) - @header << text << "\n" - end - - def rule(name, opts = {}) - @rules << "rule #{name}\n" - opts.each { |k, v| @rules << " " << k.to_s << " = " << v.to_s << "\n" } - @rules << "\n" - end - - def pool(name, opts = {}) - @pools << "pool #{name}\n" - opts.each { |k, v| @pools << " " << k.to_s << " = " << v.to_s << "\n" } - @pools << "\n" - end - - def variable(name, value) - @variables << "#{name} = #{value}\n" - end - - def build(target, rule, sources = nil, opts = {}) - @build << "build " << str(target) << ": " << rule - @build << " " << str(sources) if sources - @build << " | " << str(opts[:deps]) if opts[:deps] - @build << " || " << str(opts[:order_deps]) if opts[:order_deps] - @build << "\n" - opts.each do |var, value| - next if [:deps, :order_deps].include? var - var = var.to_s - value = str(value) - value = get_flags_variable(var, value) if var.end_with? "flags" - @build << " #{var} = #{value}\n" - end - @targets += list(target) - end - - def header_targets - @targets.select { |target| target.end_with? '.h' or target.end_with? '.hh' } - end - - def to_s - result = "" - result << @header << "\n" - result << @variables << "\n" - @flags.each { |_, prefix| prefix.each { |k, v| result << "#{v} = #{k}\n" } } - result << "\n" - result << @pools << "\n" - result << @rules << "\n" - result << @build << "\n" - result - end - - private - def str(list) - return list.map{ |x| escape(x) }.join(' ') if list.is_a? Enumerable - return @targets.select { |x| list.match(x) }.map { |x| escape(x) }.join(' ') if list.is_a? Regexp - list - end - - def escape(s) - s.gsub(/ /, '$ ') - end - - def get_flags_variable(type, flags) - return '' if flags.empty? - type_flags = @flags[type] - unless id = type_flags[flags] - id = "#{type}_#{type_flags.size()}" - type_flags[flags] = id - end - "$#{id}" - end -end - -################################################################################ -# Helper methods & variables -################################################################################ - -if RUBY_PLATFORM =~ /(win32|mingw32)/ - LIB_PREFIX = "" - LIB_SUFFIX = "" - EXE_SUFFIX = ".exe" -else - LIB_PREFIX = "lib" - LIB_SUFFIX = ".a" - EXE_SUFFIX = "" -end - -def list(l) - return [] if nil - return l if l.is_a? Enumerable - [l] -end - -def get_unary_flags(prefix, flags) - flags.select {|x| /^#{prefix}/i.match(x)}.map { |x| x[prefix.size .. -1] } -end - -def extract_unary_flags(prefix, flags) - flag, flags = flags.partition { |x| /^#{prefix}/i.match(x) } - [flag.map { |x| x[prefix.size .. -1] }, flags] -end - -def extract_unary_flag(prefix, flags) - flag, flags = extract_unary_flags(prefix, flags) - [flag[0], flags] -end - -def extract_binary_flag(prefix, flags) - i = flags.index(prefix) - flag = flags[i + 1] - flags.delete_at(i) - flags.delete_at(i) - [flag, flags] -end - -BINARY_FLAGS = ["-framework", "-arch", "-x", "--output-format", "-isystem", "-include"] - -def get_non_flags(flags) - skip = false - result = [] - flags.each do |f| - if skip - skip = false - elsif BINARY_FLAGS.include? f - skip = true - elsif not f.start_with? "/" and not f.start_with? "-" - result << f - end - end - result -end - -def extract_non_flags(flags) - non_flags = get_non_flags(flags) - [non_flags, flags - non_flags] -end - -def to_native_path(path) - path.gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR) -end - -def from_native_path(path) - path.gsub(File::ALT_SEPARATOR || File::SEPARATOR, File::SEPARATOR) -end - -def get_dependencies(target, build_targets) - result = [] - queue = $dependencies[target].dup - while queue.size > 0 - n = queue.pop - result << n - queue += $dependencies[n].dup - end - # Filter out Value() results - result.select {|x| build_targets.include? x or File.exists? x } -end - -def get_built_libs(libs, libpaths, outputs) - canonical_outputs = outputs.map {|p| File.expand_path(p) } - result = [] - libpaths.each do |libpath| - libs.each do |lib| - lib_libpath = Pathname.new(libpath) + "#{LIB_PREFIX}#{lib}#{LIB_SUFFIX}" - if canonical_outputs.include? lib_libpath.expand_path.to_s - result << to_native_path(lib_libpath.to_s) - end - end - end - result -end - -script = to_native_path($0) - -################################################################################ -# Configuration -################################################################################ - -$ninja_post = [] -$scons_cmd = "scons" -$scons_dependencies = Dir['SConstruct'] + Dir['**/SConscript'] - -def ninja_post (&block) - $ninja_post << block -end - - -CONFIGURATION_FILE = '.scons2ninja.conf' - -load CONFIGURATION_FILE - -$scons_dependencies = $scons_dependencies.map {|x| to_native_path(x) } - -################################################################################ -# Rules -################################################################################ - -ninja = NinjaBuilder.new - -ninja.pool 'scons_pool', depth: 1 - -if RUBY_PLATFORM =~ /(win32|mingw32)/ - ninja.rule 'cl', - deps: 'msvc', - command: '$cl /showIncludes $clflags -c $in /Fo$out', - description: 'CXX $out' - - ninja.rule 'link', - command: '$link $in $linkflags $libs /out:$out', - description: 'LINK $out' - - ninja.rule 'lib', - command: '$lib $libflags /out:$out $in', - description: 'AR $out' - - ninja.rule 'rc', - command: '$rc $rcflags /Fo$out $in', - description: 'RC $out' - - # SCons doesn't touch files if they didn't change, which makes - # ninja rebuild the file over and over again. There's no touch on Windows :( - # Could implement it with a script, but for now, delete the file if - # this problem occurs. I'll fix it if it occurs too much. - ninja.rule 'scons', - command: "#{$scons_cmd} $out", - pool: 'scons_pool', - description: 'GEN $out' - - ninja.rule 'install', command: 'cmd /c copy $in $out' - ninja.rule 'run', command: '$in' -else - ninja.rule 'cxx', - deps: 'gcc', - depfile: '$out.d', - command: '$cxx -MMD -MF $out.d $cxxflags -c $in -o $out', - description: 'CXX $out' - - ninja.rule 'cc', - deps: 'gcc', - depfile: '$out.d', - command: '$cc -MMD -MF $out.d $ccflags -c $in -o $out', - description: 'CC $out' - - ninja.rule 'link', - command: '$glink -o $out $in $linkflags', - description: 'LINK $out' - - ninja.rule 'ar', - command: 'ar $arflags $out $in && ranlib $out', - description: 'AR $out' - - # SCons doesn't touch files if they didn't change, which makes - # ninja rebuild the file over and over again. Touching solves this. - ninja.rule 'scons', - command: "#{$scons_cmd} $out && touch $out", - pool: 'scons_pool', - description: 'GEN $out' - - ninja.rule 'install', command: 'install $in $out' - ninja.rule 'run', command: './$in' -end - -ninja.rule 'moc', - command: '$moc $mocflags -o $out $in', - description: 'MOC $out' - -ninja.rule 'rcc', - command: '$rcc $rccflags -name $name -o $out $in', - description: 'RCC $out' - -ninja.rule 'uic', - command: '$uic $uicflags -o $out $in', - description: 'UIC $out' - -ninja.rule 'lrelease', - command: '$lrelease $lreleaseflags $in -qm $out', - description: 'LRELEASE $out' - -ninja.rule 'ibtool', - command: '$ibtool $ibtoolflags --compile $out $in', - description: 'IBTOOL $out' - -ninja.rule 'generator', - command: "ruby #{script} ${generator_args}", - pool: 'scons_pool', - generator: '1', - description: 'Regenerating build.ninja' - - -################################################################################ -# Build Statements -################################################################################ - -generator_args = ARGV.join(' ') -scons_generate_cmd = "#{$scons_cmd} #{generator_args} --tree=all,prune dump_trace=1" -#scons_generate_cmd = 'cmd /c type scons2ninja.in' -#scons_generate_cmd = 'cat scons2ninja.in' - -# Pass 1: Parse dependencies (and prefilter some build rules) -build_lines = [] -$dependencies = Hash.new {|h, k| h[k] = [] } -previous_file = nil -Open3.popen3(scons_generate_cmd) do |stdin, f, stderr, thread| - stage = :preamble - skip_nth_line = -1 - stack = ['.'] - f.each_line do |line| - # Skip lines if requested from previous command - skip_nth_line -= 1 if skip_nth_line >= 0 - next if skip_nth_line == 0 - - line.chop! - - break if line.start_with? 'scons: done building targets' - - case stage - # Pass all lines from the SCons configuration step to output - when :preamble - if /^scons: Building targets .../.match(line) - stage = :build - else - puts line - end - - when :build - if line.start_with? '+-' - stage = :dependencies - # Ignore response files from MSVS - elsif /^Using tempfile/.match(line) - skip_nth_line = 2 - else - build_lines << line - end - - when :dependencies - # Work around bug in SCons that splits output over multiple lines - next unless /^[\s|]+\+\-/.match(line) - - level = line.index('+-') / 2 - file = line[level*2+2..-1] - file = file[1..-2] if file.start_with? '[' - - # Check if we use the 'fixed' format which escapes filenames - file = eval('"' + file[1..-2].gsub('"', '\\"') + '"') if file.start_with? '\'' - - if level < stack.length - stack = stack[0..level-1] - elsif level > stack.length - raise "Internal Error" if level != stack.length + 1 - stack << previous_file - end - # Skip absolute paths - $dependencies[stack[-1]] << file unless Pathname.new(file).absolute? - previous_file = file - end - end - - unless thread.value.success? - print "Error calling '#{scons_generate_cmd}': " - print stderr.read - exit(-1) - end -end - -# Pass 2: Parse build rules -tools = {} -build_lines.each do |line| - # Custom python function - if m = /^(\w+)\(\[([^\]]*)\]/.match(line) - out = m[2].split(',').map { |x| x[1..-2] } - out.each do |x| - # Note: To be more correct, deps should also include $scons_dependencies, - # but this regenerates a bit too often, so leaving it out for now. - ninja.build x, 'scons', nil, deps: get_dependencies(x, ninja.targets) - end - - # TextFile - elsif m = /^Creating '([^']+)'/.match(line) - out = m[1] - # Note: To be more correct, deps should also include $scons_dependencies, - # but this regenerates a bit too often, so leaving it out for now. - ninja.build out, 'scons', nil, deps: get_dependencies(out, ninja.targets) - - # Install - elsif m = /^Install file: "(.*)" as "(.*)"/.match(line) - ninja.build m[2], 'install', m[1] - - elsif m = /^Install directory: "(.*)" as "(.*)"/.match(line) - Dir["#{m[1]}/**"].each do |file| - source = Pathname.new(file) - native_source = to_native_path(source.to_s) - target = Pathname.new(m[2]) + source.relative_path_from(Pathname.new(m[1])) - native_target = to_native_path(target.to_s) - ninja.build native_target, 'install', native_source - end - - # Tools - else - command = line.split - flags = command[1..-1] - tool = File.basename(command[0], File.extname(command[0])) - tool = "cxx" if ["clang++", "g++"].include? tool - tool = "cc" if ["clang", "gcc"].include? tool - tool = "glink" if ["cc", "cxx"].include? tool and not flags.include? "-c" - tool.gsub!(/-qt4$/, '') - tools[tool] = command[0] - - case tool - - ############################################################ - # clang/gcc tools - ############################################################ - - when 'cc' - out, flags = extract_binary_flag("-o", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'cc', files, order_deps: '_generated_headers', ccflags: flags - - when 'cxx' - out, flags = extract_binary_flag("-o", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'cxx', files, order_deps: '_generated_headers', cxxflags: flags - - when 'glink' - out, flags = extract_binary_flag("-o", flags) - files, flags = extract_non_flags(flags) - libs = get_unary_flags('-l', flags) - libpaths = get_unary_flags("-L", flags) - dependencies = get_built_libs(libs, libpaths, ninja.targets) - ninja.build out, 'link', files, deps: dependencies, linkflags: flags - - when 'ar' - objects, flags = flags.partition { |x| x.end_with? ".o" } - libs, flags = flags.partition { |x| x.end_with? ".a" } - out = libs[0] - ninja.build out, 'ar', objects, arflags: flags - - when 'ranlib' - - - ############################################################ - # MSVC tools - ############################################################ - - when 'cl' - out, flags = extract_unary_flag("/Fo", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'cl', files, order_deps: '_generated_headers', clflags: flags - - when 'lib' - out, flags = extract_unary_flag("/out:", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'lib', files, libflags: flags - - when 'link' - objects, flags = flags.partition { |x| x.end_with? ".obj" } - out, flags = extract_unary_flag("/out:", flags) - libs, flags = flags.partition { |x| not x.start_with? "/" and x.end_with? ".lib" } - libpaths = get_unary_flags("/libpath:", flags) - dependencies = get_built_libs(libs, libpaths, ninja.targets) - ninja.build out, 'link', objects, deps: dependencies, - libs: libs, linkflags: flags - - when 'rc' - out, flags = extract_unary_flag("/fo", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'rc', files[0], order_deps: '_generated_headers', rcflags: flags - - ############################################################ - # Qt tools - ############################################################ - - when 'moc' - out, flags = extract_binary_flag("-o", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'moc', files, mocflags: flags - - when 'uic' - out, flags = extract_binary_flag("-o", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'uic', files, uicflags: flags - - when 'lrelease' - out, flags = extract_binary_flag("-qm", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'lrelease', files, lreleaseflags: flags - - when 'rcc' - out, flags = extract_binary_flag("-o", flags) - name, flags = extract_binary_flag("-name", flags) - files, flags = extract_non_flags(flags) - deps = get_dependencies(out, ninja.targets) - files - ninja.build out, 'rcc', files, deps: deps, name: name, rccflags: flags - - ############################################################ - # OS X tools - ############################################################ - - when 'ibtool' - out, flags = extract_binary_flag("--compile", flags) - files, flags = extract_non_flags(flags) - ninja.build out, 'ibtool', files, ibtoolflags: flags - - else - raise "Unknown tool: '#{line}'" - end - end -end - -# Phony target for all generated headers, used as an order-only dependency from all C/C++ sources -ninja.build '_generated_headers', 'phony', ninja.header_targets - -# Regenerate build.ninja file -ninja.build 'build.ninja', 'generator', [], deps: [script, CONFIGURATION_FILE] + $scons_dependencies - -# Header & variables -ninja.header "# This file is generated by #{script}" -ninja.variable "ninja_required_version", "1.3" -ninja.variable "generator_args", generator_args -tools.each { |k, v| ninja.variable k, v } - -# Extra customizations -$ninja_post.each { |p| p.call(ninja) } - -################################################################################ -# Result -################################################################################ - -File.open('build.ninja', 'w') { |f| f.write ninja } |