mirror of
https://github.com/textmate/textmate.git
synced 2026-04-28 03:00:34 -04:00
We compile all assets into one file that resides in the main app bundle so while we can have multiple asset catalogs (per Framework, etc.), individual assets should still be namespaced.
758 lines
25 KiB
Ruby
Executable File
758 lines
25 KiB
Ruby
Executable File
#!/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby
|
||
# == 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.
|
||
$KCODE = 'U' if RUBY_VERSION < "1.9"
|
||
require 'optparse'
|
||
require 'shellwords'
|
||
require 'set'
|
||
require 'pp'
|
||
|
||
GLOB_KEYS = %w{ CP_Resources CP_SharedSupport CP_PlugIns CP_Library/QuickLook EXPORT HTML_FOOTER HTML_HEADER MARKDOWN_HEADER MARKDOWN_FOOTER PRELUDE SOURCES TARGETS TESTS TEST_SOURCES }
|
||
STRING_KEYS = %w{ TARGET_NAME BUNDLE_EXTENSION FLAGS C_FLAGS CXX_FLAGS OBJC_FLAGS OBJCXX_FLAGS LN_FLAGS PLIST_FLAGS }
|
||
ARRAY_KEYS = %w{ FRAMEWORKS LIBS LINK }
|
||
|
||
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', },
|
||
}
|
||
|
||
BuildDependencies = Set.new
|
||
|
||
class BuildFile
|
||
def initialize(variables, target_variables)
|
||
@commands = [ ]
|
||
@user_variables = variables
|
||
@target_variables = target_variables
|
||
@headers = Hash.new { |hash, key| hash[key] = [ ] }
|
||
@variable_hash = Hash[*variables.flatten]
|
||
@targets = Set.new
|
||
end
|
||
|
||
def header(src, target)
|
||
dst = builddir("include/#{target.name}/#{File.basename(src)}")
|
||
@commands << "build #{dst}: cp #{escape(src)}"
|
||
@headers[target.name] << dst
|
||
end
|
||
|
||
def compile(src, dependencies, target)
|
||
dst = builddir(src.sub(/\.[^.]+$/, ".o"))
|
||
ext = source_extension(src)
|
||
|
||
case src
|
||
when /\.(c|cc|c\+\+|m|mm)$/ then
|
||
@commands << "build #{dst}: build_#{ext} #{escape(src)} | #{pch_for(src, target)}.gch || #{escape(dependencies).join(' ')}"
|
||
@commands << " PCH_FLAGS = -include #{pch_for(src, target)}"
|
||
@commands << flags_for_target(target)
|
||
|
||
when /\.(rl)$/ then
|
||
tmp = builddir(src.sub(/\.[^.]+$/, ".cc"))
|
||
@commands << "build #{tmp}: gen_ragel #{escape(src)} || #{escape(dependencies).join(' ')}"
|
||
|
||
@commands << "build #{dst}: build_cc #{tmp} | #{pch_for(tmp, target)}.gch || #{escape(dependencies).join(' ')}"
|
||
@commands << " PCH_FLAGS = -include #{pch_for(tmp, target)} -iquote#{escape(File.dirname(src))}"
|
||
@commands << flags_for_target(target)
|
||
|
||
when /\.capnp$/ then
|
||
@commands << "build #{escape(src)}.c++ #{escape(src)}.h: gen_capnp #{escape(src)} || #{escape(dependencies).join(' ')}"
|
||
|
||
@commands << "build #{dst}: build_cc #{escape(src)}.c++ | #{pch_for('.cc', target)}.gch || #{escape(dependencies).join(' ')}"
|
||
@commands << " PCH_FLAGS = -include #{pch_for('.cc', target)}"
|
||
@commands << flags_for_target(target)
|
||
|
||
else
|
||
STDERR << "*** unhandled source type ‘#{path}’\n"
|
||
end
|
||
|
||
dst
|
||
end
|
||
|
||
def link(dst, objects, frameworks, libs, target)
|
||
dst = builddir(dst)
|
||
|
||
static_libs = libs.select { |lib| lib =~ /\blib(.*)\.a\z/ }
|
||
dynamic_libs = libs.reject { |lib| lib =~ /\blib(.*)\.a\z/ }
|
||
|
||
@commands << "build #{dst}: link_executable #{escape(objects).join(' ')}"
|
||
@commands << " RAVE_FLAGS =#{frameworks.map { |item| " -framework #{item}" }.join}#{static_libs.map { |item| " #{item}" }.join}#{dynamic_libs.map { |item| " -l#{item}" }.join}"
|
||
@commands << flags_for_target(target)
|
||
dst
|
||
end
|
||
|
||
def copy_resource(src, dir, target)
|
||
dst = builddir(File.join(dir, File.basename(src)))
|
||
|
||
if @targets.member?(dst.downcase)
|
||
unless dst =~ %r{/Contents/./Info.plist}
|
||
STDERR << "*** skip #{src} since #{dst} already exists.\n"
|
||
end
|
||
return nil
|
||
end
|
||
|
||
@targets.add(dst.downcase)
|
||
|
||
if src =~ /\$builddir\b/
|
||
@commands << "build #{dst}: cp #{src}"
|
||
return dst
|
||
end
|
||
|
||
case File.basename(src)
|
||
when /(.+)\.xib$/ then
|
||
dst = builddir(File.join(dir, "#$1.nib"))
|
||
@commands << "build #{dst}: compile_xib #{escape(src)}"
|
||
|
||
when /(.+)\.strings$/ then
|
||
@commands << "build #{dst}: cp_as_utf16 #{escape(src)}"
|
||
|
||
when /(.+)\.xcdatamodeld$/ then
|
||
dst = builddir(File.join(dir, "#$1.mom"))
|
||
@commands << "build #{dst}: compile_mom #{escape(src)}"
|
||
|
||
when /\bInfo\.plist$/ then
|
||
@commands << "build #{dst}: process_plist #{escape(src)} | bin/process_plist"
|
||
@commands << " RAVE_FLAGS = -dTARGET_NAME='#{target.name}'"
|
||
@commands << flags_for_target(target)
|
||
|
||
when /(.+)\.md(own)?$/ then
|
||
dst = builddir(File.join(dir, "#$1.html"))
|
||
prefix, suffix = target.values['MARKDOWN_HEADER'].to_a, target.values['MARKDOWN_FOOTER'].to_a
|
||
@commands << "build #{dst}: markdown #{escape(prefix).join(' ')} #{escape(src)} #{escape(suffix).join(' ')} | bin/gen_html"
|
||
header, footer = target.values['HTML_HEADER'], target.values['HTML_FOOTER']
|
||
flags = ''
|
||
flags << " -h #{escape(header.first)}" if header.to_a.size == 1
|
||
flags << " -f #{escape(footer.first)}" if footer.to_a.size == 1
|
||
@commands << " md_flags =#{flags}"
|
||
|
||
else
|
||
@commands << "build #{dst}: cp #{escape(src)}"
|
||
end
|
||
|
||
dst
|
||
end
|
||
|
||
def compile_xcassets(catalogs, files, dir, target)
|
||
dst = builddir(File.join(dir, "Assets.car"))
|
||
@commands << "build #{dst}: compile_xcassets #{escape(catalogs).join(' ')} | #{escape(files).join(' ')}"
|
||
@commands << " outputdir = #{builddir(dir)}"
|
||
dst
|
||
end
|
||
|
||
def index_help_book(index, files, target)
|
||
@commands << "build #{builddir(index)}: index_help | #{escape(files).join(' ')}"
|
||
@commands << " HELP_BOOK = #{builddir(File.dirname(index))}"
|
||
builddir(index)
|
||
end
|
||
|
||
def create_target(target, all_targets)
|
||
app_name = target.name
|
||
app_path = "#{builddir(File.dirname(target.path))}/#{app_name}"
|
||
|
||
if target.resources?
|
||
app_path = "#{app_path}.#{target.values['BUNDLE_EXTENSION'] || 'app'}"
|
||
resources = target.resources(self, all_targets).keys
|
||
@commands << "build #{app_path}: phony | #{resources.join(' ')}"
|
||
else
|
||
target.resources(self, all_targets)
|
||
end
|
||
|
||
@commands << "build #{app_path}.sign: sign_executable #{app_path}"
|
||
@commands << "build #{app_path}.run: run_application #{app_path} | #{app_path}.sign"
|
||
@commands << " appname = #{app_name}"
|
||
|
||
tbz = builddir("archive/#{app_name}") + '_${APP_VERSION}.tbz'
|
||
dsym = builddir("archive/dsym/#{app_name}") + '_${APP_VERSION}.tbz'
|
||
|
||
@commands << "build #{dsym}: dsym #{app_path}"
|
||
@commands << "build #{tbz}: tbz_archive #{app_path} | #{app_path}.sign"
|
||
@commands << "build #{tbz}-uploaded: upload #{tbz}"
|
||
@commands << "build #{tbz}-deployed: deploy #{tbz}-uploaded | #{dsym}"
|
||
|
||
@commands << "build #{app_name}: phony #{app_path}.sign"
|
||
@commands << "build #{app_name}/run: phony #{app_path}.run"
|
||
@commands << "build #{app_name}/dsym: phony #{dsym}"
|
||
@commands << "build #{app_name}/tbz: phony #{tbz}"
|
||
@commands << "build #{app_name}/upload: phony #{tbz}-uploaded"
|
||
@commands << "build #{app_name}/deploy: phony #{tbz}-deployed"
|
||
end
|
||
|
||
def write(io, target)
|
||
variables = @user_variables + @target_variables
|
||
width = variables.map { |arr| arr.first.length }.max { |lhs, rhs| lhs <=> rhs }
|
||
variables.each { |arr| io << format("%-#{width}s = %s\n", *arr) }
|
||
|
||
if app_name = variables.find { |arr| arr.first == 'APP_NAME' }.last
|
||
io << "\n"
|
||
io << "build run: phony #{app_name}/run\n"
|
||
io << "build tbz: phony #{app_name}/tbz\n"
|
||
io << "build dsym: phony #{app_name}/dsym\n"
|
||
io << "build deploy: phony #{app_name}/deploy\n"
|
||
io << "default run\n"
|
||
end
|
||
|
||
args = @user_variables.map { |key, value| "-d'#{key}=#{value =~ /\$/ ? value.gsub(/\$/, '$$') : "$#{key}"}'" }.join(' ')
|
||
io << "\nrule configure\n"
|
||
io << " command = bin/gen_build -C \"$builddir\" #{args} -o $out $in\n"
|
||
io << " depfile = $builddir/$out.d\n"
|
||
io << " generator = true\n"
|
||
io << " description = Generate ‘$out’…\n"
|
||
io << "\n"
|
||
io << "build build.ninja: configure #{target.path} | bin/gen_build\n"
|
||
|
||
io << "\n" << DATA.read << "\n"
|
||
|
||
COMPILER_INFO.each do |key, value|
|
||
io << "rule #{value[:rule]}\n"
|
||
io << " command = #{value[:compiler]} $PCH_FLAGS $FLAGS #{value[:flags]} $RAVE_FLAGS -o $out -MMD -MF $out.d -I$builddir/include $in\n"
|
||
io << " depfile = $out.d\n"
|
||
io << " deps = gcc\n"
|
||
io << " description = Compile ‘$in’…\n\n"
|
||
end
|
||
|
||
target.values['PRELUDE'].each do |src|
|
||
flags = { 'c' => '-x c-header', 'm' => '-x objective-c-header', 'cc' => '-x c++-header', 'mm' => '-x objective-c++-header' }
|
||
ext = source_extension(src)
|
||
|
||
io << "build #{pch_for(src, target)}.gch: build_#{ext} #{escape(src)}\n"
|
||
io << " RAVE_FLAGS = #{flags[ext]}\n\n"
|
||
end
|
||
|
||
io << @commands.join("\n") << "\n"
|
||
@headers.each do |key, value|
|
||
io << "build #{key}/headers: phony | #{value.join(' ')}\n"
|
||
end
|
||
end
|
||
|
||
def dependencies(io, dependencies)
|
||
deps = dependencies.sort.uniq.map { |path| path.gsub(/ /, '\\ ') }
|
||
io << "build.ninja: " << deps.join(" \\\n ") << "\n"
|
||
end
|
||
|
||
private
|
||
|
||
def escape(path)
|
||
if path.class == String
|
||
return path if path =~ /^\$builddir/
|
||
path.gsub(/[ :$]/, '$\\&')
|
||
else
|
||
path.map { |obj| escape(obj) }
|
||
end
|
||
end
|
||
|
||
def builddir(path)
|
||
"$builddir/#{path.gsub(/[ :$]/, '$\\&')}"
|
||
end
|
||
|
||
def source_extension(src)
|
||
srcExt = src[/\b[a-z\+]+$/]
|
||
srcExt == 'c++' ? 'cc' : srcExt
|
||
end
|
||
|
||
def pch_for(src, target)
|
||
ext = source_extension(src)
|
||
target.values['PRELUDE'].each do |pch|
|
||
return builddir(pch) if source_extension(pch) == ext
|
||
end
|
||
abort "*** no pre-compiled header for #{src} (target: #{target[:name]})"
|
||
end
|
||
|
||
def flags_for_target(target)
|
||
res = ""
|
||
target.values.each do |key, value|
|
||
if key.class == String && key =~ /FLAGS$/ && @variable_hash[key] != value
|
||
res << " #{key} = #{value}\n"
|
||
end
|
||
end
|
||
res
|
||
end
|
||
end
|
||
|
||
class Target
|
||
attr_reader :path, :name
|
||
attr_reader :values
|
||
attr_accessor :depend
|
||
|
||
def initialize(path, base_values = nil)
|
||
BuildDependencies.add(path)
|
||
|
||
if base_values.nil?
|
||
base_values = Hash.new { |hash, key| hash[key] = [ ] if GLOB_KEYS.member?(key) || ARRAY_KEYS.member?(key) }
|
||
end
|
||
|
||
@path = path
|
||
@values = base_values.dup
|
||
|
||
dir = File.dirname(path)
|
||
assignments = File.read(path).scan(/^([\w\/]+)\s*(\+=|-=|=)[ \t]*(.*)$/)
|
||
|
||
assignments.each do |arr|
|
||
key, op, value = *arr
|
||
|
||
old_value = @values[key]
|
||
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(dir) do
|
||
value = Dir.glob(globs)
|
||
BuildDependencies.add(dir)
|
||
BuildDependencies.merge(value.map { |file| File.join(dir, File.directory?(file) ? file : File.dirname(file)) })
|
||
value = value.map { |file| File.join(dir, file) } unless dir == '.'
|
||
value = value.concat(targets)
|
||
end
|
||
end
|
||
|
||
if STRING_KEYS.include? key
|
||
if op == '+='
|
||
value = old_value.nil? ? value : "#{old_value} #{value}"
|
||
elsif op == '-='
|
||
value = old_value.to_s.gsub(/(^| )#{Regexp.escape value}( |$)/, ' ')
|
||
end
|
||
else
|
||
if op == '+='
|
||
value = old_value.dup.concat(value)
|
||
elsif op == '-='
|
||
abort "Operator -= not implemented for non-string keys"
|
||
end
|
||
end
|
||
@values[key] = value
|
||
end
|
||
@name = @values['TARGET_NAME'] || File.basename(dir)
|
||
end
|
||
|
||
def headers(builder)
|
||
@values['EXPORT'].each do |path|
|
||
builder.header(path, self)
|
||
end
|
||
end
|
||
|
||
def objects(builder)
|
||
if @objects.nil?
|
||
dependencies = all_dependencies(false).map { |target| "#{target.name}/headers" }
|
||
@objects = @values['SOURCES'].map do |src|
|
||
builder.compile(src, dependencies, self)
|
||
end
|
||
end
|
||
@objects
|
||
end
|
||
|
||
def link(builder)
|
||
return @link unless @link.nil?
|
||
|
||
objects = all_dependencies.map { |target| target.objects(builder) }.flatten
|
||
frameworks = all_dependencies.map { |target| target.values['FRAMEWORKS'] }.flatten.sort.uniq
|
||
libs = all_dependencies.map { |target| target.values['LIBS'] }.flatten.sort.uniq
|
||
|
||
dst = File.dirname(@path)
|
||
dst << "/#{@name}.#{@values['BUNDLE_EXTENSION'] || 'app'}/Contents/MacOS" if resources?
|
||
|
||
@link = builder.link(File.join(dst, @name), objects, frameworks, libs, self)
|
||
end
|
||
|
||
def resources(builder, all_targets)
|
||
return @resources unless @resources.nil?
|
||
|
||
exe = resources? ? "#{@name}.#{@values['BUNDLE_EXTENSION'] || 'app'}/Contents/MacOS" : ""
|
||
@resources = { self.link(builder) => exe }
|
||
|
||
raw = { }
|
||
raw.default = [ ]
|
||
|
||
all_dependencies.each do |target|
|
||
target.values.keys.each do |key|
|
||
raw[$'] += target.values[key] if key =~ /^CP_/
|
||
end
|
||
end
|
||
|
||
asset_catalogs, asset_catalog_files = [ ], [ ]
|
||
contents = "#{@name}.#{@values['BUNDLE_EXTENSION'] || 'app'}/Contents"
|
||
raw.each do |dst, paths|
|
||
paths.each do |path|
|
||
if path =~ /^@/
|
||
if all_targets.has_key?($')
|
||
all_targets[$'].resources(builder, all_targets).each do |src, relative|
|
||
@resources[builder.copy_resource(src, "#{File.dirname(@path)}/#{contents}/#{dst}/#{relative}", self)] = "#{contents}/#{dst}/#{relative}"
|
||
end
|
||
else
|
||
STDERR << "*** no such target: #{$'}\n"
|
||
end
|
||
elsif File.directory?(path)
|
||
asset_catalog_dir = path =~ /(.+)\.xcassets$/ ? path : nil
|
||
asset_catalogs << asset_catalog_dir unless asset_catalog_dir.nil?
|
||
help_book_dir = path =~ /.* Help$/ ? path : nil
|
||
help_book_files = [ ]
|
||
Dir.chdir(path) do
|
||
BuildDependencies.add(path)
|
||
Dir.glob("**/*").each do |file|
|
||
BuildDependencies.add(File.join(path, File.directory?(file) ? file : File.dirname(file)))
|
||
next if File.directory?(file)
|
||
full = File.join(path, file)
|
||
help_book_files << full unless help_book_dir.nil?
|
||
if asset_catalog_dir
|
||
asset_catalog_files << full
|
||
else
|
||
@resources[builder.copy_resource(File.join(path, file), "#{File.dirname(@path)}/#{contents}/#{dst}/#{File.basename(path)}/#{File.dirname(file)}", self)] = "#{contents}/#{dst}/#{File.basename(path)}/#{File.dirname(file)}"
|
||
end
|
||
end
|
||
end
|
||
unless help_book_dir.nil?
|
||
index = "#{File.dirname(@path)}/#{contents}/#{dst}/#{File.basename(path)}/#{@name}.helpindex"
|
||
@resources[builder.index_help_book(index, help_book_files, self)] = help_book_dir
|
||
end
|
||
else
|
||
tmp = File.basename(path) == 'Info.plist' ? '.' : dst
|
||
@resources[builder.copy_resource(path, "#{File.dirname(@path)}/#{contents}/#{tmp}", self)] = "#{contents}/#{tmp}"
|
||
end
|
||
end
|
||
end
|
||
|
||
unless asset_catalogs.empty?
|
||
@resources[builder.compile_xcassets(asset_catalogs, asset_catalog_files, "#{File.dirname(@path)}/#{contents}/Resources", self)] = "#{File.dirname(@path)}/#{contents}/Resources"
|
||
end
|
||
|
||
@resources.delete(nil)
|
||
@resources
|
||
end
|
||
|
||
def resources?
|
||
all_dependencies.any? do |target|
|
||
target.values.keys.any? { |key| key =~ /^CP_/ }
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
def all_dependencies(including_self = true)
|
||
targets = [ ]
|
||
names = Set.new
|
||
|
||
new_targets = including_self ? [ self ] : self.depend.dup
|
||
until new_targets.empty?
|
||
target = new_targets.pop
|
||
next if names.member?(target.name)
|
||
|
||
targets << target
|
||
names << target.name
|
||
new_targets.concat(target.depend)
|
||
end
|
||
|
||
targets
|
||
end
|
||
end
|
||
|
||
def targets_at_path(path)
|
||
res = [ ]
|
||
|
||
new_targets = [ Target.new(path) ]
|
||
until new_targets.empty?
|
||
target = new_targets.pop
|
||
|
||
paths = target.values['TARGETS']
|
||
if paths.empty?
|
||
res << target
|
||
else
|
||
values = target.values.dup
|
||
values['TARGETS'] = [ ]
|
||
paths.each do |target_path|
|
||
new_targets << Target.new(target_path, values)
|
||
end
|
||
end
|
||
end
|
||
|
||
return res
|
||
end
|
||
|
||
def atomic_write(path)
|
||
temp = path + '~'
|
||
open(temp, 'w') do |io|
|
||
yield(io)
|
||
end
|
||
File.rename(temp, path)
|
||
end
|
||
|
||
def ninja_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.shellescape} -t targets all }
|
||
return nil if $? != 0
|
||
targets.each_line do |line|
|
||
if line =~ /.*(?=:(?! phony))/
|
||
path = $&
|
||
res << path if path =~ /^#{Regexp.escape(builddir)}/ # ignore targets outside builddir
|
||
end
|
||
end
|
||
end
|
||
|
||
res
|
||
end
|
||
|
||
outfile, builddir = '/dev/stdout', File.expand_path('~/build')
|
||
variables = [ ]
|
||
|
||
OptionParser.new do |opts|
|
||
opts.banner = "Usage: gen_build [options] "
|
||
opts.separator "Synopsis"
|
||
opts.separator "gen_build: create build.ninja based on target files"
|
||
opts.separator "Options:"
|
||
|
||
opts.on("-h", "--help", "show help.") do |v|
|
||
puts opts
|
||
exit
|
||
end
|
||
|
||
opts.on("-o", "--output FILE", "the «file» to write build info into.") do |v|
|
||
outfile = v
|
||
end
|
||
|
||
opts.on("-C", "--build-directory DIRECTORY", "where build files should go.") do |v|
|
||
builddir = v
|
||
end
|
||
|
||
opts.on("-d", "--define NAME=VALUE", "define a variable that ends up in build.ninja") do |v|
|
||
variables << [ $1, $2 ] if v =~ /^(\w+)\s*=\s*(.*)$/
|
||
end
|
||
end.parse!
|
||
|
||
abort "No root target file specified" if ARGV.empty?
|
||
path = ARGV[0]
|
||
|
||
Dir.chdir(File.dirname(path)) do
|
||
targets = { }
|
||
targets_at_path(File.basename(path)).each { |target| targets[target.name] = target }
|
||
|
||
targets.values.each do |target|
|
||
target.depend = target.values['LINK'].map { |name| targets[name] }
|
||
end
|
||
|
||
target_variables = [ ]
|
||
Target.new(File.basename(path)).values.each do |key, value|
|
||
target_variables << [ key, value.kind_of?(Array) ? value.join(' ') : value ] unless key.class != String || GLOB_KEYS.include?(key)
|
||
end
|
||
|
||
builder = BuildFile.new(variables, target_variables)
|
||
|
||
targets.values.each do |target|
|
||
target.headers(builder)
|
||
end
|
||
|
||
libraries = Set.new
|
||
targets.values.each { |target| libraries.merge(target.values['LINK']) }
|
||
roots = targets.values.reject { |target| libraries.member?(target.name) || !target.values['EXPORT'].empty? }
|
||
|
||
roots.each do |target|
|
||
builder.create_target(target, targets)
|
||
end
|
||
|
||
all_old_targets = ninja_targets(outfile, builddir)
|
||
|
||
atomic_write(outfile) do |io|
|
||
userfile = "#{ENV['USER']}.ninja"
|
||
io << "ninja_required_version = 1.5\n"
|
||
io << "\n"
|
||
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 userfile != 'build.ninja' && File.exists?(File.join(File.dirname(outfile), userfile))
|
||
|
||
atomic_write("#{builddir}/build.ninja") do |io|
|
||
builder.write(io, Target.new(path))
|
||
|
||
atomic_write("#{builddir}/build.ninja.d") do |io|
|
||
dep = BuildDependencies.to_a.map do |file|
|
||
file.gsub(%r{^\./|/\.(?=/|$)}, '')
|
||
end
|
||
builder.dependencies(io, dep)
|
||
end
|
||
end
|
||
end
|
||
|
||
all_new_targets = ninja_targets(outfile, builddir)
|
||
|
||
if all_old_targets && all_new_targets
|
||
targets_lost = all_old_targets - all_new_targets
|
||
targets_lost.each do |path|
|
||
if path !~ %r{/archive/} && File.exists?(path)
|
||
STDERR << "Remove old target ‘#{path.sub(/#{Regexp.escape(builddir)}/, '$builddir')}’…\n"
|
||
File.unlink(path)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
# =======================
|
||
|
||
__END__
|
||
rule touch
|
||
command = touch $out
|
||
generator = true
|
||
description = Touch ‘$out’…
|
||
|
||
rule cp
|
||
command = cp -XRp $in $out
|
||
description = Copy ‘$in’…
|
||
|
||
rule cp_as_utf16
|
||
command = if [[ $$(head -c2 $in) == $$'\xFF\xFE' || $$(head -c2 $in) == $$'\xFE\xFF' ]]; then cp -XRp $in $out; else iconv -f utf-8 -t utf-16 < $in > $out~ && mv $out~ $out; fi
|
||
description = Copy ‘$in’ as UTF-16…
|
||
|
||
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_capnp
|
||
command = PATH="$capnp_prefix/bin:$PATH" capnp compile -oc++ $in
|
||
description = Generate source from ‘$in’…
|
||
|
||
rule gen_cxx_test
|
||
command = bin/CxxTest/bin/cxxtestgen --have-std -o $out --runner=unix $in
|
||
description = Generate test ‘$out’…
|
||
|
||
rule link_cxx_test
|
||
command = $CXX -o $out $in $LN_FLAGS $RAVE_FLAGS
|
||
description = Link test ‘$out’…
|
||
|
||
rule gen_oak_test
|
||
command = bin/gen_test $in > $out~ && mv $out~ $out
|
||
description = Generate test ‘$out’…
|
||
|
||
rule link_oak_test
|
||
command = $CXX -o $out $in $LN_FLAGS $RAVE_FLAGS -framework CoreFoundation
|
||
description = Link test ‘$out’…
|
||
|
||
rule run_test
|
||
command = $in $test_flags && touch $out
|
||
description = Run test ‘$in’…
|
||
|
||
rule always_run_test
|
||
command = $in $test_flags
|
||
description = Run 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 = lldb $in
|
||
pool = console
|
||
description = Debug ‘$in’…
|
||
|
||
rule run_application
|
||
command = { $
|
||
if pgrep "$appname"; then $
|
||
if [[ -x "$$DIALOG" && $$("$$DIALOG" 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 "$appname"; do $
|
||
if (( ++n == 10 )); then $
|
||
test -x "$$DIALOG" && "$$DIALOG" alert --title "Relaunch Timed Out" --body "Unable to exit $appname." --button1 OK; $
|
||
exit; $
|
||
fi; $
|
||
sleep .2; $
|
||
done; $
|
||
fi; $
|
||
open $in --args -disableSessionRestore NO; $
|
||
} </dev/null &>/dev/null &
|
||
description = Run ‘$in’…
|
||
|
||
rule sign_executable
|
||
command = xcrun codesign --timestamp=none --deep -fs "$identity" $in && touch $out
|
||
description = Sign ‘$in’…
|
||
|
||
rule tbz_archive
|
||
command = COPYFILE_DISABLE=1 tar $bzip2_flag -cf $out~ -C "$$(dirname $in)" "$$(basename $in)" && mv $out~ $out
|
||
generator = true
|
||
description = Archive ‘$in’…
|
||
|
||
rule upload
|
||
command = bin/upload -k$upload_keyfile -d$upload_destination -t'v$APP_VERSION' -m'{"version":"$APP_VERSION","depends":"os (>= $APP_MIN_OS), app (>= 2.0-alpha)"}' $in > $out~ && mv $out~ $out
|
||
pool = console
|
||
generator = true
|
||
description = Upload ‘$in’…
|
||
|
||
rule deploy
|
||
command = curl -sfnd @$in '${rest_api}/releases/nightly' && touch $out
|
||
generator = true
|
||
description = Deploy…
|
||
|
||
rule bump_revision
|
||
command = ruby -pe '$$_.gsub!(/(APP_VERSION\s*=\s*(.*?))(\d+)$$/) { newRev = $$3.to_i + 1; STDERR << "#$$2#$$3 → #$$2#{newRev}\n"; "#$$1#{newRev}" }' $in > $in~ && mv $in~ $in && touch $builddir/bumped
|
||
description = Increment version number…
|
||
|
||
build $builddir/always_bump_revision: bump_revision $builddir/build.ninja
|
||
build bump: phony $builddir/always_bump_revision
|
||
|
||
rule clean
|
||
command = rm -r '$path'
|
||
|
||
rule dsym
|
||
command = $
|
||
DST=$$(/usr/bin/mktemp -dt dsym); $
|
||
find $in -name rmate -or -type f -perm +0111 -print|while read line; $
|
||
do xcrun dsymutil --flat --out "$$DST/$$(basename "$$line" .dylib).dSYM" "$$line"; $
|
||
done && tar $bzip2_flag -cf $out~ -C "$$DST" . && rm -rf "$$DST" && mv $out~ $out
|
||
generator = true
|
||
description = Archive dSYM info for ‘$in’…
|
||
|
||
rule compile_xib
|
||
command = xcrun ibtool --errors --warnings --notices --output-format human-readable-text --minimum-deployment-target $APP_MIN_OS --compile $out $in
|
||
description = Compile xib ‘$in’…
|
||
|
||
rule compile_xcassets
|
||
command = xcrun actool --errors --warnings --notices --output-format human-readable-text --platform macosx --minimum-deployment-target $APP_MIN_OS --compile $outputdir $in
|
||
description = Compile xcassets ‘$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 -Cvaf $out "$HELP_BOOK"
|
||
description = Index help book ‘$HELP_BOOK’…
|
||
|
||
rule compile_mom
|
||
command = xcrun momc $in $out
|
||
description = Generate ‘$out’…
|