#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -wKU # == Synopsis # # gen_build: create build.ninja based on target files # # == Usage # # --help: # show help. # # --output/-o «file»: # the «file» to write build info into. # # --build-directory/-C «directory»: # where build files should go. # # --define/-d «name»=«value»: # define a variable that ends up in build.ninja. require 'getoptlong' require 'rdoc/usage' require 'shellwords' require 'set' require 'pp' GLOB_KEYS = %w{ CP_Resources CP_SharedSupport CP_PlugIns EXPORT HTML_FOOTER HTML_HEADER MARKDOWN_HEADER MARKDOWN_FOOTER PRELUDE SOURCES TARGETS TEST_SOURCES } STRING_KEYS = %w{ TARGET_NAME BUNDLE_EXTENSION FLAGS C_FLAGS CXX_FLAGS OBJC_FLAGS OBJCXX_FLAGS LN_FLAGS PLIST_FLAGS BUILD } COMPILER_INFO = { 'c' => { :rule => 'build_c', :compiler => '$CC', :flags => '$C_FLAGS', }, 'm' => { :rule => 'build_m', :compiler => '$CC', :flags => '$OBJC_FLAGS', }, 'cc' => { :rule => 'build_cc', :compiler => '$CXX', :flags => '$CXX_FLAGS', }, 'mm' => { :rule => 'build_mm', :compiler => '$CXX', :flags => '$OBJCXX_FLAGS', }, } class Targets include Enumerable attr_reader :root def initialize(path) targets = all_targets(path) linked = Set.new(targets.collect { |target| target['LINK'].to_a }.flatten) @root = targets.first @all_targets = targets @all = [ ] targets.each do |target| next if target['SOURCES'].to_a.empty? @all << target if linked.include?(target[:name]) || target['BUILD'] == 'lib' target[:type] = :library else has_info_plist = target['CP_Resources'].to_a.find { |path| path =~ /\bInfo\.plist$/ } target[:type] = has_info_plist ? :bundle : :executable end end end def target_named(name) res = self.find { |target| target[:name] == name } return res unless res.nil? abort "*** no target named ‘#{name}’. Did you update submodules?\n" end def each(&block) @all.each(&block) end def dependencies paths = [ ] @all_targets.each do |target| paths << target[:full_path] << target[:depeends_on_directories].to_a target.each do |key, value| paths << value.reject { |path| path =~ /^@/ }.map do |path| File.dirname(File.join(target[:base], path)) end if GLOB_KEYS.include?(key) && key != 'PRELUDE' end end paths.flatten.sort.uniq end private def parse(path, root_dir, parent = { }) base = File.dirname(path) dict = parent.merge({ :name => File.basename(base), :base => base, :path => base.sub(/^#{Regexp.escape root_dir}\//, ''), :full_path => path }) dict.delete('TARGETS') data = File.read(path) assignments = data.scan(/^(\w+)\s*(\+=|-=|=)[ \t]*(.*)$/) assignments.each do |arr| key, op, value = *arr value = Shellwords.shellwords(value) unless STRING_KEYS.include? key if GLOB_KEYS.include?(key) globs = value.reject { |v| v =~ /^@/ } targets = value.reject { |v| v !~ /^@/ } Dir.chdir(File.dirname(path)) do value = Dir.glob(globs) value = value.concat(targets) end end unless op != '+=' || dict[key].nil? if STRING_KEYS.include? key value = "#{dict[key]} #{value}" else value = dict[key].dup.concat(value) end end dict[key] = value end if dict.has_key?('TARGET_NAME') dict[:name] = dict['TARGET_NAME'] dict.delete('TARGET_NAME') end dict end def recursive_parse(target, root_dir) res = [ target ] target["TARGETS"].to_a.each do |subtarget| res << recursive_parse(parse(File.join(target[:base], subtarget), root_dir, target), root_dir) end res end def all_targets(path) recursive_parse(parse(path, File.dirname(path)), File.dirname(path)).flatten end end def esc_path(path) if path.class == String return path if path =~ /^\$builddir/ path.gsub(/[ :$]/, '$\\&') else path.map { |obj| esc_path(obj) } end end def build_path(path) "$builddir/#{path.gsub(/[ :$]/, '$\\&')}" end def prepend(prefix, array, sep = ' ') array.map do |item| item =~ %r{/} ? item : esc_path("#{prefix}#{item}") end.join(sep) end def flags_for_target(target) res = "" target.each do |key, value| res << " #{key} = #{value}\n" unless key.class != String || key !~ /FLAGS$/ || value == TARGETS.root[key] end res end def compiler_for(source) COMPILER_INFO[source[/\b[a-z]+$/]][:rule] end def pch_for(src, target) srcExt = src[/\b[a-z]+$/] target['PRELUDE'].each do |pch| if srcExt == pch[/\b[a-z]+$/] res = build_path(pch) res << "-arc" if srcExt == 'm' && target['OBJ_FLAGS'] =~ /-fobjc-arc/ res << "-arc" if srcExt == 'mm' && target['OBJCXX_FLAGS'] =~ /-fobjc-arc/ return res end end abort "*** no pre-compiled header for #{src} (target: #{target[:name]})" end def all_values_for_key(target, key) all_targets = { } new_targets = [ target ] until new_targets.empty? target = new_targets.pop unless all_targets.include? target[:base] all_targets[target[:base]] = target target['LINK'].to_a.each do |name| TARGETS.each { |target| new_targets << target if name == target[:name] } end end end all_targets.map { |path, target| target[key].to_a }.flatten.sort.uniq end def headers(target) res = '' return res if target['EXPORT'].to_s.empty? headers = [ ] target['EXPORT'].to_a.each do |path| src = File.join(target[:path], path) dst = build_path("include/#{target[:name]}/#{File.basename(path)}") res << "build #{dst}: cp #{esc_path src}\n" headers << dst end headers = headers.concat(target['LINK'].map { |lib| "#{esc_path lib}/headers" }) unless target['LINK'].nil? res << "build #{target[:name]}/headers: phony | #{headers.join(' ')}\n" res end def sources(target) res = '' objects = [ ] headers = target['LINK'].to_a.map { |goal| "#{goal}/headers" } target['SOURCES'].each do |path| src = File.join(target[:path], path) case src when /^(.*)\.(c|cc|m|mm)$/ then base, ext = $1, $2 cc = compiler_for(src) dst = build_path("#{base}.o") res << "build #{dst}: #{cc} #{esc_path src} | #{pch_for(src, target)}.gch || #{esc_path(headers).join(' ')}\n" res << " depfile = #{dst}.d\n" res << " RAVE_FLAGS = -D#{target[:name].gsub(/[^A-Za-z_]/, '_')}_EXPORTS -include #{pch_for(src, target)}\n" res << flags_for_target(target) objects << dst when /^(.*)\.(rl)$/ then base, ext = $1, $2 dst = build_path("#{base}.cc") res << "build #{dst}: gen_ragel #{esc_path src} || #{esc_path(headers).join(' ')}\n" ext = 'cc' cc = compiler_for(dst) obj = build_path("#{base}.o") res << "build #{obj}: #{cc} #{dst} | #{pch_for(dst, target)}.gch || #{esc_path(headers).join(' ')}\n" res << " depfile = #{obj}.d\n" res << " RAVE_FLAGS = -D#{target[:name].gsub(/[^A-Za-z_]/, '_')}_EXPORTS -include #{pch_for(dst, target)} -iquote#{esc_path File.dirname(src)}\n" res << flags_for_target(target) objects << obj else STDERR << "*** unhandled source type ‘#{path}’\n" end end target[:objects] = objects res end def clean(target) res = '' dst = build_path(File.join(target[:path])) res << "build #{dst}/#{target[:name]}.clean: clean\n" res << " path = #{dst}\n" res << "build #{target[:name]}/clean: phony #{dst}/#{target[:name]}.clean\n" end def tests(target) res = '' unless target['TEST_SOURCES'].to_s.empty? headers = target['LINK'].to_a.map { |goal| "#{goal}/headers" } src = target['TEST_SOURCES'].map { |path| esc_path(File.join(target[:path], path)) }.join(' ') dst = build_path("#{target[:path]}/test_#{target[:name]}") ext = target['TEST_SOURCES'].any? { |path| path =~ /\.mm$/ } ? 'mm' : 'cc' res << "build #{dst}.#{ext}: gen_test #{src}\n" res << "build #{dst}.o: #{compiler_for("#{dst}.#{ext}")} #{dst}.#{ext} | #{pch_for(src, target)}.gch || #{target[:name]}/headers\n" res << " depfile = #{dst}.o.d\n" res << " RAVE_FLAGS = -Ibin/CxxTest -include #{pch_for(src, target)}\n" res << flags_for_target(target) res << "build #{dst}: link_test #{all_values_for_key(target, :objects).join(' ')} #{dst}.o\n" res << flags_for_target(target) fws = prepend('-framework ', all_values_for_key(target, 'FRAMEWORKS')) libs = prepend('-l', all_values_for_key(target, 'LIBS')) res << " RAVE_FLAGS = #{fws} #{libs}\n" res << "build #{dst}.run: run_test #{dst}\n" res << "build #{dst}.always-run: always_run_test #{dst}\n" res << "build #{dst}.coerce: skip_test #{dst}\n" res << " seal = #{dst}.run\n" res << "build #{target[:name]}: phony #{dst}.run\n" res << "build #{target[:name]}/test: phony #{dst}.always-run\n" res << "build #{target[:name]}/coerce: phony #{dst}.coerce\n" target[:test] = "#{dst}.run" end res end def resources_helper(target, key, symbol) res = '' rsrc = [ ] all_resources = [ ] depeends_on_directories = Array.new(target[:depeends_on_directories].to_a) target[key].to_a.each do |path| if path =~ /^@/ src = TARGETS.target_named($') abort "*** no resources in target: ‘#{path}’ (from target: #{target[:name]}).\n" unless src.has_key?(:rsrc_info) src[:rsrc_info].each do |info| rsrc << { :src => info[:src], :dst => esc_path(info[:install_name]) } end else src = File.join(target[:path], path) abs = File.join(target[:base], path) if File.directory?(abs) && !File.symlink?(abs) && abs !~ /\.xcdatamodeld$/ depeends_on_directories << abs Dir.chdir(abs) { Dir.glob("**/*") }.each do |file| if File.directory?(File.join(abs, file)) depeends_on_directories << File.join(abs, file) else all_resources << { :path => File.dirname(src), :file => File.join(File.basename(src), file) } end end else all_resources << { :path => File.dirname(src), :file => File.basename(src) } end end end target[:depeends_on_directories] = depeends_on_directories.sort.uniq all_resources.each do |dict| path, file = dict[:path], dict[:file] src = File.join(path, file) case file when /(.+)\.xib$/ then dst = build_path(File.join(path, "#$1.nib")) res << "build #{dst}: compile_xib #{esc_path src}\n" rsrc << { :src => dst, :dst => esc_path("#$1.nib") } when /(.+)\.xcdatamodeld$/ then dst = build_path(File.join(path, "#$1.mom")) res << "build #{dst}: compile_mom #{esc_path src}\n" rsrc << { :src => dst, :dst => esc_path("#$1.mom") } when /\bInfo\.plist$/ then dst = build_path(File.join(path, file)) res << "build #{dst}: process_plist #{esc_path src} | bin/process_plist\n" res << " RAVE_FLAGS = -dTARGET_NAME='#{target[:name]}'\n" res << flags_for_target(target) target[:info_plist] = dst when /(.+)\.md(own)?$/ then dst = build_path(File.join(path, "#$1.html")) prefix, suffix = target['MARKDOWN_HEADER'].to_a, target['MARKDOWN_FOOTER'].to_a res << "build #{dst}: markdown #{prefix.map { |path| esc_path(File.join(target[:path], path))}.join(' ')} #{esc_path src} #{suffix.map { |path| esc_path(File.join(target[:path], path))}.join(' ')} | bin/gen_html\n" header, footer = target['HTML_HEADER'], target['HTML_FOOTER'] flags = '' flags << " -h #{esc_path(File.join(target[:path], header.first))}" if header.to_a.size == 1 flags << " -f #{esc_path(File.join(target[:path], footer.first))}" if footer.to_a.size == 1 res << " md_flags =#{flags}\n" rsrc << { :src => dst, :dst => esc_path("#$1.html") } else rsrc << { :src => esc_path(src), :dst => esc_path(file) } end end help_files = rsrc.find_all { |dict| dict[:src] =~ %r{[^/]+ Help/.+\.html$} } unless help_files.empty? src = "#$&/#{target[:name]}.helpindex" if help_files.first[:src] =~ /.* Help(?=\/)/ dst = "#$&/#{target[:name]}.helpindex" if help_files.first[:dst] =~ /.* Help(?=\/)/ res << "build #{src}: index_help | #{help_files.map { |dict| dict[:src] }.join(' ')}\n" res << " HELP_BOOK = #{File.dirname(src)}\n" rsrc << { :src => src, :dst => dst } end target[symbol] = rsrc res end def resources(target) res = '' target.keys.each do |key| res << resources_helper(target, key, key.to_sym) if key.class == String && key =~ /^CP_/ end res end def object_archive(target) dst = build_path("#{target[:path]}/lib#{target[:name]}.a") target[:lib] = dst res = '' res << "build #{dst}: link_static #{target[:objects].join(' ')} | #{target[:test]}\n" res << flags_for_target(target) end def dynamic_lib(target) dst = target[:dylib] = build_path("#{target[:path]}/#{target[:name]}.dylib") if target[:CP_Resources].to_a.empty? install_name = "@executable_path/../Frameworks/#{target[:name]}.dylib" else install_name = "@executable_path/../Frameworks/#{target[:name]}.framework/Versions/A/#{target[:name]}" end link_with = all_values_for_key(target, 'LINK').map { |name| TARGETS.target_named(name)[:dylib] } res = '' res << "build #{dst}: link_dynamic #{target[:objects].join(' ')} | #{target[:test]} #{link_with.join(' ')}\n" res << flags_for_target(target) fws = prepend('-framework ', all_values_for_key(target, 'FRAMEWORKS')) libs = prepend('-l', all_values_for_key(target, 'LIBS')) res << " RAVE_FLAGS = -install_name #{esc_path install_name} #{link_with.join(' ')} #{fws} #{libs}\n" end def framework_bundle(target, dst_dir = nil) res = '' dst = File.join(dst_dir || build_path("#{target[:path]}"), "#{target[:name]}.framework") deps = [ ] target.each do |key, files| if key.class == Symbol && key.to_s =~ /^CP_(.+)$/ install_dir = $1 files.each do |rsrc| deps << "#{dst}/Versions/A/#{install_dir}/#{rsrc[:dst]}" res << "build #{deps.last}: cp #{rsrc[:src]}\n" end end end deps << "#{dst}/Versions/A/Resources/Info.plist" res << "build #{deps.last}: cp #{target[:info_plist]}\n" deps << "#{dst}/Versions/A/#{target[:name]}" res << "build #{deps.last}: cp #{target[:dylib]}\n" deps << "#{dst}/Versions/Current" res << "build #{deps.last}: ln\n link = A\n" deps << "#{dst}/Resources" res << "build #{deps.last}: ln\n link = Versions/Current/Resources\n" deps << "#{dst}/#{target[:name]}" res << "build #{deps.last}: ln\n link = Versions/Current/#{target[:name]}\n" res << "build #{dst}: phony | #{deps.join(' ')}\n" end def static_executable(target) objects = [ ] all_values_for_key(target, 'LINK').each do |name| objects << TARGETS.target_named(name)[:lib] end objects << target[:objects] res = '' dst = build_path("#{target[:path]}/#{target[:name]}") target[:static_executable] = dst target[:rsrc_info] = [ { :install_name => "#{target[:name]}", :src => dst } ] res << "build #{dst}: link_executable #{objects.flatten.join(' ')}\n" res << flags_for_target(target) fws = prepend('-framework ', all_values_for_key(target, 'FRAMEWORKS')) libs = prepend('-l', all_values_for_key(target, 'LIBS')) res << " RAVE_FLAGS = #{fws} #{libs}\n" res << "build #{dst}.run: run_executable #{dst}\n" res << "build #{dst}.debug: debug_executable #{dst}\n" res << "build #{target[:name]}: phony #{dst}\n" res << "build #{target[:name]}/run: phony #{dst}.run\n" res << "build #{target[:name]}/debug: phony #{dst}.debug\n" res end def dynamic_executable(target) res = '' link_with = all_values_for_key(target, 'LINK').map { |name| TARGETS.target_named(name)[:dylib] } dst = build_path("#{target[:path]}/#{target[:name]}.dyn") target[:dynamic_executable] = dst res << "build #{dst}: link_executable #{target[:objects].join(' ')} | #{link_with.join(' ')}\n" res << flags_for_target(target) fws = prepend('-framework ', all_values_for_key(target, 'FRAMEWORKS')) libs = prepend('-l', all_values_for_key(target, 'LIBS')) res << " RAVE_FLAGS = #{link_with.join(' ')} #{fws} #{libs}\n" res end def app_bundle(target) res = '' deps = [ ] all_files = [ ] ext = target['BUNDLE_EXTENSION'] || 'app' dst = build_path("#{target[:path]}/#{target[:name]}.#{ext}") target[:bundle] = dst fw_dst = File.join(dst, "Contents/Frameworks") all_values_for_key(target, 'LINK').each do |name| tmp = TARGETS.target_named(name) if tmp[:CP_Resources].to_a.empty? res << "build #{fw_dst}/#{tmp[:name]}.dylib: cp #{tmp[:dylib]}\n" deps << "#{fw_dst}/#{tmp[:name]}.dylib" else res << framework_bundle(tmp, fw_dst) deps << "#{fw_dst}/#{tmp[:name]}.framework" end end target.each do |key, files| if key.class == Symbol && key.to_s =~ /^CP_(.+)$/ install_dir = $1 files.each do |rsrc| deps << "#{dst}/Contents/#{install_dir}/#{rsrc[:dst]}" res << "build #{deps.last}: cp #{rsrc[:src]}\n" all_files << { :install_name => "#{target[:name]}.#{ext}/Contents/#{install_dir}/#{rsrc[:dst]}", :src => rsrc[:src] } end end end deps << "#{dst}/Contents/Info.plist" res << "build #{deps.last}: cp #{target[:info_plist]}\n" all_files << { :install_name => "#{target[:name]}.#{ext}/Contents/Info.plist", :src => target[:info_plist] } deps << "#{dst}/Contents/MacOS/#{target[:name]}" res << "build #{deps.last}: cp #{target[:dynamic_executable]}\n" all_files << { :install_name => "#{target[:name]}.#{ext}/Contents/MacOS/#{target[:name]}", :src => target[:dynamic_executable] } res << "build #{dst}: phony | #{deps.join(' ')}\n" res << "build #{dst}.sign: sign_executable #{dst}\n" res << "build #{dst}.run: run_application #{dst} | #{dst}.sign\n" res << " appname = #{target[:name]}\n" res << "build #{dst}.debug: debug_executable #{dst}/Contents/MacOS/#{target[:name]} | #{dst}.sign\n" res << "build #{target[:name]}: phony #{dst}.sign\n" res << "build #{target[:name]}/run: phony #{dst}.run\n" res << "build #{target[:name]}/debug: phony #{dst}.debug\n" target[:rsrc_info] = all_files res end def upload(target) res = '' exe = target[:static_executable] base = "#{exe}_r${APP_REVISION}" res << "build #{exe}.sign: sign_executable #{exe}\n" res << "build #{base}.tbz: tbz_archive #{exe} | #{exe}.sign\n" res << "build #{base}.upload: upload #{base}.tbz\n" res << "build #{target[:name]}/deploy: phony #{base}.upload\n" end def deploy(target) res = '' base = target[:bundle].sub(/\.[^.]+$/, '_r${APP_REVISION}') res << "build #{base}-dSYM.tbz: dsym #{target[:bundle]}\n" res << "build #{base}.tbz: tbz_archive #{target[:bundle]} | #{target[:bundle]}.sign\n" res << "build #{base}.upload: upload #{base}.tbz\n" res << "build #{base}.deploy: deploy #{base}.upload | #{base}-dSYM.tbz\n" res << "build #{target[:name]}/dsym: phony #{base}-dSYM.tbz\n" res << "build #{target[:name]}/tbz: phony #{base}.tbz\n" res << "build #{target[:name]}/deploy: phony #{base}.deploy\n" end def all_targets(buildfile, builddir) res = Set.new return res unless File.exists? buildfile Dir.chdir(File.dirname(buildfile)) do targets = %x{ ${TM_NINJA:-ninja} -f #{[ buildfile ].shelljoin } -t targets all } targets.each do |line| if line =~ /.*(?=:(?! phony))/ path = $& res << path if path =~ /^#{Regexp.escape(builddir)}/ # ignore targets outside builddir end end end res end # ======================= # = Program Starts Here = # ======================= opts = GetoptLong.new( [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--output', '-o', GetoptLong::REQUIRED_ARGUMENT ], [ '--build-directory', '-C', GetoptLong::REQUIRED_ARGUMENT ], [ '--define', '-d', GetoptLong::REQUIRED_ARGUMENT ] ) outfile, builddir = '/dev/stdout', File.expand_path('~/build') variables = [ ] opts.each do |opt, arg| case opt when '--help' RDoc::usage when '--output' outfile = arg when '--build-directory' builddir = arg when '--define' variables << [ $1, $2 ] if arg =~ /^(\w+)\s*=\s*(.*)$/ end end abort "No root target file specified" if ARGV.empty? manifest = ARGV[0] TARGETS = Targets.new(manifest) all_old_targets = all_targets(outfile, builddir) open(outfile, "w") do |io| userfile = "#{ENV['USER']}.ninja" io << "###########################################\n" io << "# AUTOMATICALLY GENERATED -- DO NOT EDIT! #\n" io << "###########################################\n" io << "\n" io << "builddir = #{builddir}\n" io << "include $builddir/build.ninja\n" io << "include #{userfile}\n" if File.exists?(File.join(File.dirname(outfile), userfile)) end open("#{builddir}/build.ninja", "w") do |io| user_variables = TARGETS.root.to_a.reject { |arr| arr[0].class != String || GLOB_KEYS.include?(arr[0]) }.sort { |lhs, rhs| lhs[0] <=> rhs[0] } tmp = variables.dup.concat(user_variables) width = tmp.map { |arr| arr[0].length }.max { |lhs, rhs| lhs <=> rhs } io << variables.map { |arr| format("%-#{width}s = %s\n", *arr) } io << "\n" io << user_variables.map { |arr| format("%-#{width}s = %s\n", *arr) } io << "\n" args = variables.map { |key, value| "-d'#{key}=#{value.gsub(/\$/, '$\&')}'" }.join(' ') io << "rule configure\n" io << " command = bin/gen_build -C \"$builddir\" #{args} -o $out $in\n" io << " depfile = $builddir/$out.d\n" io << " generator = 1\n" io << " description = Generate ‘$out’…\n" io << "\n" io << "build build.ninja: configure #{manifest} | bin/gen_build\n" io << "\n" io << DATA.read << "\n" COMPILER_INFO.each do |key, value| io << "rule #{value[:rule]}\n" io << " command = '#{value[:compiler]}' $FLAGS $RAVE_FLAGS #{value[:flags]} -o $out -MMD -MF $out.d -I$builddir/include $in\n" io << " depfile = $depfile\n" io << " description = Compile ‘$in’…\n\n" end TARGETS.root['PRELUDE'].each do |src| type = { 'c' => '-x c-header', 'm' => '-x objective-c-header', 'cc' => '-x c++-header', 'mm' => '-x objective-c++-header' } ext = src[/\b[a-z]+$/] cc = COMPILER_INFO[ext][:compiler] io << "build #{pch_for(src, TARGETS.root)}.gch: #{compiler_for(src)} #{src} | #{cc}\n" io << " depfile = #{pch_for(src, TARGETS.root)}.gch.d\n" io << " RAVE_FLAGS = #{type[ext]}\n\n" if ext =~ /^mm?$/ io << "build #{pch_for(src, TARGETS.root)}-arc.gch: #{compiler_for(src)} #{src} | #{cc}\n" io << " depfile = #{pch_for(src, TARGETS.root)}-arc.gch.d\n" io << " RAVE_FLAGS = #{type[ext]} -fobjc-arc\n\n" end end # ======================================================= # = Do a topological sort based on the dependency graph = # ======================================================= dag = '' TARGETS.each do |target| target['LINK'].each { |dep| dag << "#{target[:name]}\t#{dep}\n" } unless target['LINK'].nil? target.each do |key, value| value.each { |rsrc| dag << "#{target[:name]}\t#$'\n" if rsrc =~ /^@/ } if key =~ /^CP_/ end end dag = open('|/usr/bin/tsort -q', 'r+') { |tsort| tsort << dag; tsort.close_write; tsort.read } # ========================================================================= # = Now iterate the targets (in the sorted order) to generate build rules = # ========================================================================= dag.scan(/.+/).reverse_each do |name| target = TARGETS.target_named(name) io << clean(target) io << sources(target) io << resources(target) if target[:type] == :library io << headers(target) io << object_archive(target) io << tests(target) io << dynamic_lib(target) elsif target[:type] == :executable io << static_executable(target) io << upload(target) else io << dynamic_executable(target) io << app_bundle(target) io << deploy(target) end end io << "\n" io << "build run: phony #{variables.find { |k, _| k == 'APP_NAME' }.last}/run\n" io << "build deploy: phony #{variables.find { |k, _| k == 'APP_NAME' }.last}/deploy\n" io << "build debug: phony #{variables.find { |k, _| k == 'APP_NAME' }.last}/debug\n" io << "default run\n" end open("#{builddir}/build.ninja.d", "w") do |io| deps = TARGETS.dependencies.map { |path| path.gsub(/ /, '\\ ') } io << outfile << ": " << deps.join(" \\\n ") << "\n" end all_new_targets = all_targets(outfile, builddir) targets_lost = all_old_targets - all_new_targets targets_lost.each do |path| if File.exists?(path) STDERR << "Remove old target ‘#{path.sub(/#{Regexp.escape(builddir)}/, '$builddir')}’…\n" File.unlink(path) end end # ======================= __END__ rule cp command = cp -p $in $out description = Copy ‘$in’… rule ln command = ln -fs $link $out description = Link ‘$out’… rule gen_ragel command = ragel -o $out $in description = Generate source from ‘$in’… rule gen_test command = bin/CxxTest/bin/cxxtestgen --have-std -o $out --runner=unix $in description = Generate test ‘$out’… rule link_test command = '$CXX' -o $out $in $LN_FLAGS $RAVE_FLAGS description = Link test ‘$out’… rule run_test command = $in && touch $out description = Run test ‘$in’… rule always_run_test command = $in description = Run test ‘$in’… rule skip_test command = touch "$seal" description = Skip test ‘$in’… rule link_static command = rm -f $out && ar -cqs $out $in description = Archive objects ‘$out’… rule link_dynamic command = '$CXX' -o $out $in $LN_FLAGS $RAVE_FLAGS -dynamiclib -current_version 1.0.1 -compatibility_version 1.0.0 description = Link dynamic library ‘$out’… rule link_executable command = '$CXX' -o $out $in $LN_FLAGS $RAVE_FLAGS description = Link executable ‘$out’… rule run_executable command = $in description = Run ‘$in’… rule debug_executable command = osascript >/dev/null -e $$'tell app "Terminal" \n activate \n do script "gdb $in" \n end tell' description = Debug ‘$in’… rule run_application command = { $ if pgrep >/dev/null "$appname"; then $ if [[ -x "$$DIALOG" && $$("$$DIALOG" < /dev/null alert --title "Relaunch $appname?" --body "Would you like to quit $appname and start the newly built version?" --button1 Relaunch --button2 Cancel|pl) != *"buttonClicked = 0"* ]]; $ then exit; $ fi; $ pkill "$appname"; $ while pgrep >/dev/null "$appname"; do $ if (( ++n == 10 )); then $ test -x "$$DIALOG" && "$$DIALOG" /dev/null & description = Run ‘$in’… rule sign_executable command = codesign -fs "$identity" $in && touch $out description = Sign ‘$in’… rule tbz_archive command = tar $bzip2_flag -cf $out -C "$$(dirname $in)" "$$(basename $in)" description = Archive ‘$in’… rule upload command = bin/upload -k$upload_keyfile -d$upload_destination $in > $out~ && mv $out~ $out description = Upload ‘$in’… rule deploy command = curl -sfnd @$in '${rest_api}/releases/nightly' && touch $out description = Deploy… rule bump_revision command = ruby -pe '$$_.gsub!(/(APP_REVISION\s*=\s*)(\d+)/) { newRev = $$2.to_i + 1; STDERR << "r#$$2 → r#{newRev}\n"; "#$$1#{newRev}#$$3" }' $in > $in~ && mv $in~ $in && touch $out description = Increment version number… build $builddir/revision.$APP_REVISION: bump_revision $builddir/build.ninja build bump: phony $builddir/revision.$APP_REVISION rule clean command = rm -r '$path' rule dsym command = $ DST=$$(mktemp -dt dsym); $ find $in -name rmate -or -type f -perm +0111 -print|while read line; $ do dsymutil --flat --out "$$DST/$$(basename "$$line" .dylib).dSYM" "$$line"; $ done && tar $bzip2_flag -cf $out -C "$$DST" . && rm -rf "$$DST" description = Archive dSYM info for ‘$in’… rule compile_xib command = "$xcodedir/usr/bin/ibtool" --errors --warnings --notices --output-format human-readable-text --compile $out $in description = Compile xib ‘$in’… rule process_plist command = bin/process_plist > $out~ $in $PLIST_FLAGS $RAVE_FLAGS && mv $out~ $out description = Process plist ‘$in’… rule markdown command = bin/gen_html > $out~ $md_flags $in && mv $out~ $out description = Generate ‘$out’… rule index_help command = /usr/bin/hiutil -Caf $out "$HELP_BOOK" description = Index help book ‘$HELP_BOOK’… rule compile_mom command = "$xcodedir/usr/bin/momc" $in $out description = Generate ‘$out’…