diff options
| author | Remko Tronçon <git@el-tramo.be> | 2013-07-14 17:32:27 (GMT) | 
|---|---|---|
| committer | Remko Tronçon <git@el-tramo.be> | 2013-07-15 18:02:01 (GMT) | 
| commit | ffb8c6b0cdcdfa133680d87a1cebe8fecff8426b (patch) | |
| tree | e537fde8087cb6cead858db92b8bc2f4217380ea | |
| parent | a0f902844e7d83006a45c40158aa7d8256c87260 (diff) | |
| download | swift-ffb8c6b0cdcdfa133680d87a1cebe8fecff8426b.zip swift-ffb8c6b0cdcdfa133680d87a1cebe8fecff8426b.tar.bz2 | |
Ported scons2ninja to python.
Change-Id: I0cf2c0123686d6cad487e423072f4fc5b28ea1ce
| -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 } | 
 Swift
 Swift