#!/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 attr_reader :applications attr_reader :frameworks attr_reader :dependencies def initialize(path) targets = all_targets(path) linked = Set.new(targets.collect { |target| target['LINK'].to_a }.flatten) @root = targets.first @all, @applications, @frameworks, @dependencies = [ ], [ ], [ ], [ ] targets.each do |target| next if target['SOURCES'].to_a.empty? @all << target if linked.include?(target[:name]) || target['BUILD'] == 'lib' @frameworks << target else @applications << target end end paths = [ ] targets.each do |target| paths << target[:full_path] 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 @dependencies = paths.flatten.sort.uniq @all.sort! { |lhs, rhs| lhs[:name].downcase <=> rhs[:name].downcase } @applications.sort! { |lhs, rhs| lhs[:name].downcase <=> rhs[:name].downcase } @frameworks.sort! { |lhs, rhs| lhs[:name].downcase <=> rhs[:name].downcase } end def target_named(name) self.find { |target| target[:name] == name } end def each(&block) @all.each(&block) 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) target['PRELUDE'].each do |pch| return "#{build_path(pch)}" if src[/\b[a-z]+$/] == pch[/\b[a-z]+$/] 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 << "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}.coerce: skip_test #{dst}\n" res << " seal = #{dst}.run\n" res << "build #{target[:name]}: phony #{dst}.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 = [ ] target[key].to_a.each do |path| if path =~ /^@/ src = TARGETS.target_named($') abort "*** target does not exist: ‘#{path}’.\nKnown targets:\n - #{TARGETS.map { |target| target[:name] }.join("\n - ")}\n" if src.nil? 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$/ Dir.chdir(abs) { Dir.glob("**/*") }.each do |file| all_resources << { :path => File.dirname(src), :file => File.join(File.basename(src), file) } unless File.directory?(File.join(abs, file)) end else all_resources << { :path => File.dirname(src), :file => File.basename(src) } end end end 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}\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(' ')}\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] 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 #{target[:name]}: phony #{dst}\n" res << "build #{target[:name]}/run: phony #{dst}.run\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 #{target[:name]}: phony #{dst}.sign\n" res << "build #{target[:name]}/run: phony #{dst}.run\n" target[:rsrc_info] = all_files res 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 #{base}.bump: bump_revision $builddir/build.ninja | #{base}.deploy\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}.bump\n" 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) open(outfile, "w") do |io| io << "###########################################\n" io << "# AUTOMATICALLY GENERATED -- DO NOT EDIT! #\n" io << "###########################################\n" io << "\n" io << "builddir = #{builddir}\n" io << "subninja $builddir/build.ninja\n" end open("#{builddir}/build.ninja.d", "w") do |io| deps = TARGETS.dependencies.map { |path| path.gsub(/ /, '\\ ') } io << outfile << ": " << deps.join(" \\\n ") << "\n" 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, _| "-d#{key}='$#{key}'" }.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 << "\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' } io << "build #{pch_for(src, TARGETS.root)}.gch: #{compiler_for(src)} #{src}\n" io << " depfile = #{pch_for(src, TARGETS.root)}.gch.d\n" io << " RAVE_FLAGS = #{type[src[/\b[a-z]+$/]]}\n\n" end TARGETS.each do |target| io << headers(target) io << sources(target) io << clean(target) end TARGETS.frameworks.each do |target| io << resources(target) io << object_archive(target) target[:dylib] = build_path("#{target[:path]}/#{target[:name]}.dylib") end TARGETS.frameworks.each do |target| io << tests(target) io << dynamic_lib(target) end app, exe = [ ], [ ] TARGETS.applications.each do |target| if target['CP_Resources'].to_a.find { |path| path =~ /\bInfo\.plist$/ } app << target else exe << target end end exe.each do |target| io << static_executable(target) end app.each do |target| io << resources(target) io << dynamic_executable(target) io << app_bundle(target) io << deploy(target) end io << "\n" io << "default #{variables.find { |k, _| k == 'APP_NAME' }.last}/run\n" 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 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 run_application command = { $ if pgrep -q "$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 -q "$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 $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+)(\s*)$$/) { "#$$1#{$$2.to_i + 1}#$$3" }' $in > $in~ && mv $in~ $in && touch $out description = Increment version number… 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’…