From ffb8c6b0cdcdfa133680d87a1cebe8fecff8426b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sun, 14 Jul 2013 19:32:27 +0200
Subject: Ported scons2ninja to python.

Change-Id: I0cf2c0123686d6cad487e423072f4fc5b28ea1ce

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 }
-- 
cgit v0.10.2-6-g49f6