-
-
Save petejkim/4681824 to your computer and use it in GitHub Desktop.
use product name instead of project name to find the app bundle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/Library/Frameworks/MacRuby.framework/Versions/0.13/usr/bin/macruby | |
# MacRuby Deployer. | |
# | |
# This file is covered by the Ruby license. | |
# | |
# Copyright (C) 2012, The MacRuby Team | |
# Copyright (C) 2009-2011, Apple Inc | |
require 'optparse' | |
require 'rbconfig' | |
begin | |
load File.join(File.dirname(__FILE__), 'macrubyc') | |
rescue LoadError | |
load File.join(File.dirname(__FILE__), 'rubyc') | |
end | |
class Deployer | |
BATCH_SIZE = 40 | |
NAME = File.basename(__FILE__) | |
def initialize(argv) | |
@stdlib = [] | |
@no_compile = [] | |
@gems = [] | |
OptionParser.new do |opts| | |
opts.banner = "Usage: #{NAME} [options] application-bundle" | |
opts.on('--compile', 'Compile the bundle source code') { @compile = true } | |
opts.on('--exclude-compile FILE', 'Folders or files to exclude from compilation') { |f| @no_compile << f } | |
opts.on('--embed', 'Embed MacRuby inside the bundle') { @embed = true } | |
opts.on('--no-stdlib', 'Do not embed the standard library') { @no_stdlib = true } | |
opts.on('--stdlib [LIB]', 'Embed only LIB from the standard library') { |lib| @stdlib << lib } | |
opts.on('--gem [GEM]', 'Embed GEM and its dependencies') { |gem| @gems << gem } | |
opts.on('--bs', 'Embed the system BridgeSupport files') { @embed_bs = true } | |
opts.on('--verbose', 'Log all commands to standard out') { @verbose = true } | |
opts.on('--codesign [CERT]', String, 'Sign the files with the specified certificate') { |cert| @certificate = cert } | |
opts.on('-v', '--version', 'Display the version') do | |
puts RUBY_DESCRIPTION | |
exit 0 | |
end | |
begin | |
opts.parse!(argv) | |
rescue OptionParser::InvalidOption => e | |
die e, opts | |
end | |
if argv.empty? | |
# If we are ran from Xcode, determine the application bundle from the environment. | |
build_dir = ENV['TARGET_BUILD_DIR'] | |
product_name = ENV['PRODUCT_NAME'] | |
if build_dir and product_name | |
@app_bundle = File.join(build_dir, product_name + '.app') | |
end | |
end | |
unless @app_bundle | |
die opts if argv.size != 1 | |
@app_bundle = argv[0] | |
end | |
end | |
ensure_path @app_bundle, 'The application bundle was not found; make sure you build the app before running macruby_deploy' | |
ensure_path File.join(@app_bundle, 'Contents'), "Path `%s' doesn't seem to be a valid application bundle" | |
# Locate necessary programs. | |
@install_name_tool = locate('install_name_tool') | |
# Locate the MacRuby framework. | |
@macruby_framework_path = RbConfig::CONFIG['libdir'].scan(/^.+MacRuby\.framework/)[0] | |
ensure_path @macruby_framework_path, "Cannot locate MacRuby.framework from rbconfig.rb" | |
@macruby_install_version = RbConfig::CONFIG["INSTALL_VERSION"] | |
end | |
def run | |
die "Nothing to do, please specify --compile or --embed" if !@compile and !@embed | |
die "--no-stdlib can only be used with --embed" if @no_stdlib and !@embed | |
die "--stdlib can only be used with --embed" if [email protected]? and !@embed | |
die "--bs can only be used with --embed" if @embed_bs and !@embed | |
log "Deployment started" | |
embed if @embed | |
compile if @compile | |
sign if @certificate | |
log "Deployment ended" | |
end | |
private | |
# FileUtils::Verbose doesn't work with MacRuby yet. However, it doesn't print | |
# out failures anyways, just the command. Use the command-line tools directly | |
# to circumvent this. | |
{ :cp => 'cp', :cp_r => 'cp -R', :mkdir_p => 'mkdir -p', :rm_rf => 'rm -rf', :rsync => 'rsync', :mv => 'mv' }.each do |name, cmd| | |
define_method(name) do |*args| | |
arg_string = args.map { |a| "'#{a}'" }.join(' ') | |
execute "#{cmd} #{arg_string}" | |
end | |
end | |
def app_frameworks | |
File.join(@app_bundle, 'Contents', 'Frameworks') | |
end | |
def app_resources | |
File.join(@app_bundle, 'Contents', 'Resources') | |
end | |
def app_bs | |
File.join(app_resources, 'BridgeSupport') | |
end | |
def app_macruby | |
File.join(app_frameworks, 'MacRuby.framework') | |
end | |
def app_macruby_usr | |
File.join(app_macruby, 'Versions', 'Current', 'usr') | |
end | |
def app_binary | |
File.join(@app_bundle, 'Contents', 'MacOS', File.basename(@app_bundle, '.app')) | |
end | |
def app_archs | |
unless @archs | |
@archs = if ENV['ARCHS'] | |
# Use Xcode ARCHS env var to determine which archs to compile for | |
ENV['ARCHS'].strip.split | |
else | |
# Try to infer the archs the app was built for from output like: | |
# * Architectures in the fat file: /usr/bin/ruby are: x86_64 i386 ppc7400 | |
# * Non-fat file: Progress.app/Contents/MacOS/Progress is architecture: x86_64 | |
`/usr/bin/lipo -info "#{app_binary}"`.split(':').last.split | |
end | |
# Check that `archs' contains valid values | |
supported_archs = RbConfig::CONFIG['ARCH_FLAG'].gsub('-arch', '').strip.split | |
unsupported_archs = @archs - supported_archs | |
@archs -= unsupported_archs | |
unless unsupported_archs.empty? | |
count = unsupported_archs.size | |
msg = "Can't build for arch#{'s' if count > 1} `#{unsupported_archs.join(' ')}', " | |
msg << "because #{count == 1 ? 'it is' : 'they are'} not supported by this MacRuby build (#{supported_archs.join(' ')})." | |
# No point in trying to compile for no archs. | |
@archs.empty? ? die(msg) : puts(msg) | |
end | |
end | |
@archs | |
end | |
def macruby_version_base | |
@macruby_version_base ||= ensure_path(File.join(@macruby_framework_path, 'Versions', @macruby_install_version)) | |
end | |
def macruby_usr | |
@macruby_usr ||= ensure_path(File.join(macruby_version_base, 'usr')) | |
end | |
def compile_files | |
Dir.glob(File.join(app_resources, '**', '*.rb')).reject do |path| | |
path.split('/').any? do |c| | |
@no_compile.include? c | |
end | |
end | |
end | |
def try_to_compile(source, to: obj) | |
unless File.exist?(obj) && File.mtime(obj) > File.mtime(source) | |
MacRuby::Compiler.compile_file(source, archs: app_archs) | |
end | |
rescue | |
die "Can't compile \"#{source}\"" | |
end | |
def compile | |
log "Compiling files" | |
compile_files.each_slice(BATCH_SIZE) do |batch| | |
log "Compiling #{batch.map { |f| File.basename f } }..." | |
batch.each do |source| | |
base = File.basename(source, '.rb') | |
next if base == 'rb_main' | |
obj = File.join(File.dirname(source), base + '.rbo') | |
try_to_compile(source, to: obj) | |
rm_rf(source) if File.exists?(obj) # mtime doesn't matter, MR prefers .rbo | |
end | |
end | |
fix_install_name if File.exist?(app_macruby) | |
end | |
def gem_deps_libdirs(gem_name) | |
# Locate gem spec. | |
require 'rubygems' | |
begin | |
gemspec = Gem::Specification::find_by_name(gem_name) | |
rescue Gem::LoadError | |
die "Cannot locate gem `#{gem_name}' in #{Gem.path}" | |
end | |
# Load dependencies libdirs first. | |
gem_libdirs = [] | |
gemspec.runtime_dependencies.each do |dep| | |
gem_libdirs.concat(gem_deps_libdirs(dep.name)) | |
end | |
# Load the gem libdirs. | |
gem_libdirs.concat(gemspec.require_paths.map { |x| File.join(gemspec.full_gem_path, x) }) | |
return gem_libdirs | |
end | |
STDLIB_PATTERN = "lib/ruby/{,site_ruby/}1.9.*{,/universal-darwin*}" | |
KEEP_STDLIB_PATTERN = "{%s}{,{.,/**/*.}{rb,rbo,bundle}}" | |
def embed | |
# Prepare the list of gems to embed. | |
gems_libdirs_to_embed = @gems.map { |x| gem_deps_libdirs(x) }.flatten | |
# Exclude unnecessary things in the MacRuby.framework copy. | |
dirs = ['bin', 'include', 'lib/libmacruby-static.a', 'share', 'lib/ruby/Gems'] | |
dirs << 'lib/ruby' if @no_stdlib | |
exclude_dirs = dirs.map { |dir| "usr/#{dir}" } | |
exclude_dirs << 'Resources/Templates' # do not include Xcode templates | |
relative_base = macruby_version_base.sub(/#{@macruby_framework_path}\//, '') | |
exclude_dirs.map! { |dir| "--exclude='#{File.join(relative_base,dir)}'" } | |
# Only copy the Current version of the MacRuby.framework. | |
Dir.glob(File.join(@macruby_framework_path, 'Versions/*')).select { |x| | |
base = File.basename(x) | |
base != @macruby_install_version and base != 'Current' | |
}.each { |x| | |
exclude_dirs << "--exclude='#{x.sub(/#{@macruby_framework_path}\//, '')}'" | |
} | |
# Copy MacRuby.framework inside MyApp.app/Contents/Frameworks. | |
log "Embedding MacRuby.framework" | |
mkdir_p(app_frameworks) | |
rm_rf(app_macruby) | |
rsync('-rl', *exclude_dirs, @macruby_framework_path, app_frameworks) | |
# Orphaned symlinks prevent AppStore validation. | |
rm_rf(File.join(app_macruby, 'Headers')) | |
rm_rf(File.join(app_macruby, 'Versions', 'Current', 'Headers')) | |
lib = File.join(app_macruby_usr, STDLIB_PATTERN) | |
all = Dir.glob(File.join(lib, '**/*')) | |
# Only if specific libs from stdlib are being kept. | |
unless @stdlib.empty? | |
keep = Dir.glob(File.join(lib, KEEP_STDLIB_PATTERN % @stdlib.join(','))) | |
all.select { |f| keep.grep(/^#{f}/).empty? }.each { |x| rm_rf(x) } | |
end | |
# Remove .rb files if the .rbo exists | |
all.each do |f| | |
rm_rf f if File.extname(f) == '.rb' && all.include?("#{f}o") | |
end | |
# Copy the gems libdirs. | |
unless gems_libdirs_to_embed.empty? | |
log "Embed RubyGems libdirs: #{gems_libdirs_to_embed.join(', ')}" | |
gems_libdirs_dest = File.join(app_macruby_usr, 'lib', 'ruby', 'site_ruby', RUBY_VERSION) | |
mkdir_p(gems_libdirs_dest) | |
gems_libdirs_to_embed.each do |libdir| | |
execute("/usr/bin/ditto \"#{libdir}\" \"#{gems_libdirs_dest}\"") | |
end | |
end | |
# Copy the system BridgeSupport files if asked | |
if @embed_bs | |
log "Embed BridgeSupport system files" | |
mkdir_p(app_bs) | |
Dir.glob('/System/Library/Frameworks/**/BridgeSupport/*.{bridgesupport,dylib}').each do |path| | |
cp(path, app_bs) | |
end | |
end | |
# Wait with fixing install name until all binaries are available. | |
fix_install_name | |
check_linked_bundles | |
end | |
# Hack the application binaries to link against the MacRuby.framework copy. | |
def fix_install_name | |
log "Fix install path of binaries" | |
patterns = [File.join(@app_bundle, 'Contents/MacOS/*'), | |
File.join(app_macruby_usr, 'lib/ruby/**/*.{bundle,rbo}'), | |
File.join(@app_bundle, 'Contents/Resources/**/*.rbo')] | |
patterns.each do |pat| | |
Dir.glob(pat).each do |bin| | |
execute("#{@install_name_tool} -change #{macruby_usr}/lib/libmacruby.dylib @executable_path/../Frameworks/MacRuby.framework/Versions/Current/usr/lib/libmacruby.dylib '#{bin}'") | |
end | |
end | |
log "Fix identification name of libmacruby" | |
patterns = [ File.join(app_macruby_usr, 'lib/libmacruby*.dylib') ] | |
patterns.each do |pat| | |
Dir.glob(pat).each do |bin| | |
execute("#{@install_name_tool} -id @executable_path/../Frameworks/MacRuby.framework/Versions/Current/usr/lib/libmacruby.dylib '#{bin}'") | |
end | |
end | |
end | |
def check_linked_bundles | |
dirs = [%r{^/opt}, %r{^/usr/local/lib}, %r{^/Users}, %r{^/Library}] | |
Dir.glob(File.join(app_macruby, '**', '*.bundle')).each do |bundle| | |
libs = execute("/usr/bin/otool -L \"#{bundle}\"").split("\n").map(&:strip) | |
libs[1..-1].each do |lib| | |
bad_libs = dirs.map { |dir| lib.match(dir) ? lib : nil }.compact | |
next if bad_libs.empty? | |
name = File.basename(bundle) | |
warn '******WARNING******' | |
warn "'#{name}' is linked against libraries that are not standard to Mac OS X" | |
warn "#{bundle}\n links against:" | |
bad_libs.each { |lib| warn "\t#{lib}" } | |
warn '******WARNING******' | |
end | |
end | |
end | |
def execute(line, error_message = nil) | |
$stdout.puts(line) if @verbose | |
ret = `#{line}` | |
die(error_message || "Error when executing `#{line}'") unless $?.success? | |
ret | |
end | |
def locate(progname) | |
path = `which #{progname}`.strip | |
die "Can't locate program `#{progname}'" if path.empty? | |
path | |
end | |
def ensure_path(path, message = "Path does not exist `%s'") | |
die(message % path) unless File.exist?(path) | |
path | |
end | |
def die(*args) | |
$stderr.puts args | |
exit 1 | |
end | |
def log(msg) | |
$stderr.puts "*** #{msg}" | |
end | |
def sign | |
log "Using codesign to sign files with #{@certificate}" | |
files = [] | |
#get the .bundle, .rbo, .dylib, and .framework | |
files += Dir.glob(File.join(@app_bundle, '**', 'Gemfile*')) | |
files += Dir.glob(File.join(@app_bundle, '**', '*.bundle')) | |
files += Dir.glob(File.join(@app_bundle, '**', '*.pem')) | |
files += Dir.glob(File.join(@app_bundle, '**', '*.rb')) | |
files += Dir.glob(File.join(@app_bundle, '**', '*.gemspec')) | |
files += Dir.glob(File.join(@app_bundle, '**', '*.rbo')) | |
files += Dir.glob(File.join(@app_bundle, '**', '*.dylib')) | |
files += Dir.glob(File.join(@app_bundle, '**', '*.framework')) | |
files += Dir.glob(File.join(@app_bundle, '**', '*.js')) | |
files << @app_bundle | |
files.each_slice(BATCH_SIZE) do |batch| | |
log "Code-signing #{batch.map { |f| File.basename f } }..." | |
execute("/usr/bin/codesign -f -s \"#{@certificate}\" #{batch.collect {|f| "\"#{f}\"" }.join(' ')}") | |
end | |
end | |
end | |
Deployer.new(ARGV).run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment