diff options
Diffstat (limited to 'BuildTools/scons2ninja.rb')
-rwxr-xr-x | BuildTools/scons2ninja.rb | 562 |
1 files changed, 0 insertions, 562 deletions
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 } |