add support for app bundle, codesign, .ipa archiving

This commit is contained in:
Laurent Sansonetti
2011-07-19 15:17:13 -07:00
parent f81f02e8e8
commit e883c35809
3 changed files with 207 additions and 23 deletions

View File

@@ -12,12 +12,13 @@ task :default => :simulator
namespace :build do
desc "Build the simulator version"
task :simulator do
Rubixir::BUILDER.compile(Rubixir::CONFIG, 'iPhoneSimulator')
Rubixir::BUILDER.build(Rubixir::CONFIG, 'iPhoneSimulator')
end
desc "Build the iOS version"
task :ios do
Rubixir::BUILDER.compile(Rubixir::CONFIG, 'iPhoneOS')
Rubixir::BUILDER.build(Rubixir::CONFIG, 'iPhoneOS')
Rubixir::BUILDER.codesign(Rubixir::CONFIG, 'iPhoneOS')
end
desc "Build everything"
@@ -27,8 +28,20 @@ end
desc "Run the simulator"
task :simulator => ['build:simulator'] do
sim = File.join(Rubixir::CONFIG.platform_dir('iPhoneSimulator'), '/Developer/Applications/iPhone Simulator.app/Contents/MacOS/iPhone Simulator')
app = File.join(Rubixir::CONFIG.build_dir, 'iPhoneSimulator/main')
sh "\"#{sim}\" -SimulateApplication \"#{app}\""
sh "\"#{sim}\" -SimulateApplication \"#{Rubixir::CONFIG.app_bundle('iPhoneSimulator', true)}\""
end
desc "Create an .ipa package"
task :package => ['build:ios'] do
tmp = "/tmp/ipa_root"
rm_rf tmp
mkdir_p "#{tmp}/Payload"
cp_r Rubixir::CONFIG.app_bundle('iPhoneOS'), "#{tmp}/Payload"
Dir.chdir(tmp) do
sh "/bin/chmod -R 755 Payload"
sh "/usr/bin/zip -r archive.zip Payload"
end
cp "#{tmp}/archive.zip", Rubixir::CONFIG.archive
end
desc "Deploy on the device"

View File

@@ -1,6 +1,6 @@
module Rubixir
class Builder
def compile(config, platform)
def build(config, platform)
datadir = File.join(File.dirname(__FILE__), '../../../data')
libstatic = File.join(datadir, 'libmacruby-static.a')
archs = Dir.glob(File.join(datadir, platform, '*.bc')).map do |path|
@@ -17,7 +17,8 @@ module Rubixir
cxx = File.join(config.platform_dir(platform), 'Developer/usr/bin/g++')
build_dir = File.join(config.build_dir, platform)
# Prepare the list of BridgeSupport files needed.
bs_flags = ''
config.frameworks.each do |framework|
bs_path = File.join(datadir, 'BridgeSupport', framework + '.bridgesupport')
@@ -26,10 +27,11 @@ module Rubixir
end
end
files_compiled = false
objs = []
objs_build_dir = File.join(build_dir, 'objs')
FileUtils.mkdir_p(objs_build_dir)
config.files.each do |path|
obj = File.join(build_dir, "#{path}.o")
obj = File.join(objs_build_dir, "#{path}.o")
should_rebuild = (!File.exist?(obj) or File.mtime(path) > File.mtime(obj))
# Generate or retrieve init function.
@@ -37,7 +39,6 @@ module Rubixir
objs << [obj, init_func]
next unless should_rebuild
files_compiled = true
arch_objs = []
archs.each do |arch|
@@ -46,14 +47,14 @@ module Rubixir
raise "Can't locate kernel file" unless File.exist?(kernel)
# Prepare build_dir.
bc = File.join(build_dir, "#{path}.#{arch}.bc")
bc = File.join(objs_build_dir, "#{path}.#{arch}.bc")
FileUtils.mkdir_p(File.dirname(bc))
# LLVM bitcode.
sh "/usr/bin/env VM_KERNEL_PATH=\"#{kernel}\" #{ruby} #{bs_flags} --emit-llvm \"#{bc}\" #{init_func} \"#{path}\""
# Assembly.
asm = File.join(build_dir, "#{path}.#{arch}.s")
asm = File.join(objs_build_dir, "#{path}.#{arch}.s")
llc_arch = case arch
when 'i386'; 'x86'
when 'x86_64'; 'x86-64'
@@ -63,7 +64,7 @@ module Rubixir
sh "#{llc} \"#{bc}\" -o=\"#{asm}\" -march=#{llc_arch} -relocation-model=pic -disable-fp-elim -jit-enable-eh"
# Object.
arch_obj = File.join(build_dir, "#{path}.#{arch}.o")
arch_obj = File.join(objs_build_dir, "#{path}.#{arch}.o")
sh "#{cc} -fexceptions -c -arch #{arch} \"#{asm}\" -o \"#{arch_obj}\""
arch_objs << arch_obj
@@ -74,10 +75,6 @@ module Rubixir
sh "lipo -create #{arch_objs_list} -output \"#{obj}\""
end
# No need to rebuild the main executable in case no source files changed as well as the static library.
main_exec = File.join(build_dir, "main")
return if !files_compiled and File.exist?(main_exec) and File.mtime(main_exec) > File.mtime(File.join(datadir, platform, 'libmacruby-static.a'))
# Generate main file.
main_txt = <<EOS
#import <UIKit/UIKit.h>
@@ -126,21 +123,81 @@ EOS
EOS
# Compile main file.
main = File.join(build_dir, 'main.mm')
File.open(main, 'w') { |io| io.write(main_txt) }
main_o = File.join(build_dir, 'main.o')
arch_flags = archs.map { |x| "-arch #{x}" }.join(' ')
sh "#{cxx} \"#{main}\" #{arch_flags} -fexceptions -fblocks -isysroot \"#{sdk}\" -miphoneos-version-min=4.3 -fobjc-legacy-dispatch -fobjc-abi-version=2 -c -o \"#{main_o}\""
main = File.join(objs_build_dir, 'main.mm')
main_o = File.join(objs_build_dir, 'main.o')
if !(File.exist?(main) and File.exist?(main_o) and File.read(main) == main_txt)
File.open(main, 'w') { |io| io.write(main_txt) }
sh "#{cxx} \"#{main}\" #{arch_flags} -fexceptions -fblocks -isysroot \"#{sdk}\" -miphoneos-version-min=4.3 -fobjc-legacy-dispatch -fobjc-abi-version=2 -c -o \"#{main_o}\""
end
# Prepare bundle.
bundle_path = File.join(build_dir, config.app_name + '.app')
FileUtils.mkdir_p(bundle_path)
# Link executable.
main_exec = File.join(bundle_path, config.app_name)
objs_list = objs.map { |path, _| path }.unshift(main_o).map { |x| "\"#{x}\"" }.join(' ')
frameworks = config.frameworks.map { |x| "-framework #{x}" }.join(' ')
framework_stubs_objs = []
config.frameworks.each do |framework|
stubs_obj = File.join(datadir, platform, "#{framework}_stubs.o")
framework_stubs_objs << stubs_obj if File.exist?(stubs_obj)
framework_stubs_objs << "\"#{stubs_obj}\"" if File.exist?(stubs_obj)
end
sh "#{cxx} -o #{main_exec} #{objs_list} #{arch_flags} -isysroot \"#{sdk}\" -L#{File.join(datadir, platform)} -lmacruby-static -lobjc -licucore #{frameworks} #{framework_stubs_objs.join(' ')}"
sh "#{cxx} -o \"#{main_exec}\" #{objs_list} #{arch_flags} -isysroot \"#{sdk}\" -L#{File.join(datadir, platform)} -lmacruby-static -lobjc -licucore #{frameworks} #{framework_stubs_objs.join(' ')}"
# Create bundle/Info.plist.
bundle_info_plist = File.join(bundle_path, 'Info.plist')
File.open(bundle_info_plist, 'w') { |io| io.write(config.plist_data) }
sh "/usr/bin/plutil -convert binary1 \"#{bundle_info_plist}\""
# Create bundle/PkgInfo.
File.open(File.join(bundle_path, 'PkgInfo'), 'w') { |io| io.write(config.pkginfo_data) }
end
def codesign(config, platform)
bundle_path = File.join(config.build_dir, platform, config.app_name + '.app')
raise unless File.exist?(bundle_path)
# Create bundle/ResourceRules.plist.
resource_rules_plist = File.join(bundle_path, 'ResourceRules.plist')
File.open(resource_rules_plist, 'w') do |io|
io.write(<<-PLIST)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>rules</key>
<dict>
<key>.*</key>
<true/>
<key>Info.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>ResourceRules.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>100</real>
</dict>
</dict>
</dict>
</plist>
PLIST
end
# Copy the provisioning profile.
File.open(File.join(bundle_path, "embedded.mobileprovision"), 'w') do |io|
io.write(File.read(config.provisioning_profile))
end
# Do the codesigning.
sh "/usr/bin/codesign -f -s \"#{config.codesign_certificate}\" --resource-rules=\"#{resource_rules_plist}\" \"#{bundle_path}\""
end
end
end

View File

@@ -1,7 +1,8 @@
module Rubixir
class Config
attr_accessor :files, :platforms_dir, :sdk_version, :frameworks,
:app_delegate_class, :app_name, :build_dir
:app_delegate_class, :app_name, :build_dir, :resources_dir,
:codesign_certificate, :provisioning_profile
def initialize(project_dir)
@files = Dir.glob(File.join(project_dir, 'app/**/*.rb'))
@@ -11,6 +12,8 @@ module Rubixir
@app_delegate_class = 'AppDelegate'
@app_name = 'My App'
@build_dir = File.join(project_dir, 'build')
@resources_dir = File.join(project_dir, 'resources')
@bundle_signature = '????'
end
def platform_dir(platform)
@@ -21,5 +24,116 @@ module Rubixir
File.join(platform_dir(platform), 'Developer/SDKs',
platform + @sdk_version + '.sdk')
end
def app_bundle(platform, exec=false)
path = File.join(@build_dir, platform, @app_name + '.app')
path = File.join(path, @app_name) if exec
path
end
def archive
File.join(@build_dir, @app_name + '.ipa')
end
def plist_data
<<DATA
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>#{`sw_vers -buildVersion`.strip}</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>#{@app_name}</string>
<key>CFBundleExecutable</key>
<string>#{@app_name}</string>
<key>CFBundleIdentifier</key>
<string>com.omgwtf.#{@app_name}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>#{@app_name}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleResourceSpecification</key>
<string>ResourceRules.plist</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>#{@bundle_signature}</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvmgcc42</string>
<key>DTPlatformBuild</key>
<string>8H7</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>DTPlatformVersion</key>
<string>#{@sdk_version}</string>
<key>DTSDKBuild</key>
<string>8H7</string>
<key>DTSDKName</key>
<string>iphoneos#{@sdk_version}</string>
<key>DTXcode</key>
<string>0402</string>
<key>DTXcodeBuild</key>
<string>4A2002a</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>#{@sdk_version}</string>
<key>NSMainNibFile</key>
<string></string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
DATA
end
def pkginfo_data
"AAPL#{@bundle_signature}"
end
def codesign_certificate
@codesign_certificate ||= begin
certs = `/usr/bin/security -q find-certificate`.scan(/"iPhone Developer: [^"]+"/).uniq
if certs.size == 0
$stderr.puts "can't find any iPhone Developer certificate in the keychain"
exit 1
elsif certs.size > 1
$stderr.puts "found #{certs.size} iPhone Developer certificates, will use the first one (#{certs[0]})"
end
certs[0][1..-2] # trim trailing `"` characters
end
end
def provisioning_profile
@provisioning_profile ||= begin
paths = Dir.glob(File.expand_path("~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision"))
if paths.size == 0
$stderr.puts "can't find any provisioning profile"
exit 1
elsif paths.size > 1
$stderr.puts "found #{paths.size} provisioning profiles, will use the first one (#{paths[0]})"
end
paths[0]
end
end
end
end