diff --git a/lib/motion/plist.rb b/lib/motion/plist.rb
index a67454d0..4ec025e8 100644
--- a/lib/motion/plist.rb
+++ b/lib/motion/plist.rb
@@ -1,4 +1,7 @@
# Simple property list helper.
+
+require 'time' # For Time#iso8601
+
module Motion
class PropertyList
class << self
@@ -39,6 +42,10 @@ EOS
str << indent_line("", indent)
when FalseClass
str << indent_line("", indent)
+ when Time
+ str << indent_line("#{plist.utc.iso8601}", indent)
+ when Integer
+ str << indent_line("#{plist}", indent)
else
raise "Invalid plist object of type `#{plist.class}' (must be either a Hash, Array, String, or boolean true/false value)"
end
diff --git a/lib/motion/project.rb b/lib/motion/project.rb
index f7edda50..15415301 100644
--- a/lib/motion/project.rb
+++ b/lib/motion/project.rb
@@ -8,20 +8,20 @@ task :default => :simulator
App = Motion::Project::App
+desc "Build everything"
+task :build => ['build:simulator', 'build:device']
+
namespace :build do
desc "Build the simulator version"
task :simulator do
App.build('iPhoneSimulator')
end
- desc "Build the iOS version"
- task :ios do
+ desc "Build the device version"
+ task :device do
App.build('iPhoneOS')
App.codesign('iPhoneOS')
end
-
- desc "Build everything"
- task :all => [:simulator, :ios]
end
desc "Run the simulator"
@@ -55,8 +55,10 @@ task :simulator => ['build:simulator'] do
sh "#{sim} #{debug} #{family_int} #{target} \"#{app}\""
end
-desc "Create an .ipa archive"
-task :archive => ['build:ios'] do
+desc "Create archives for everything"
+task :archive => ['archive:development', 'archive:release']
+
+def create_ipa
app_bundle = App.config.app_bundle('iPhoneOS')
archive = App.config.archive
if !File.exist?(archive) or File.mtime(app_bundle) > File.mtime(archive)
@@ -73,6 +75,22 @@ task :archive => ['build:ios'] do
end
end
+namespace :archive do
+ desc "Create an .ipa archive for development"
+ task :development do
+ App.config_mode = :development
+ Rake::Task["build:device"].execute
+ App.archive
+ end
+
+ desc "Create an .ipa and .xcarchive for release (AppStore)"
+ task :release do
+ App.config_mode = :release
+ Rake::Task["build:device"].execute
+ App.archive
+ end
+end
+
desc "Run specs"
task :spec do
App.config.spec_mode = true
@@ -80,7 +98,7 @@ task :spec do
end
desc "Deploy on the device"
-task :deploy => :archive do
+task :device => :archive do
App.info 'Deploy', App.config.archive
unless App.config.provisioned_devices.include?(App.config.device_id)
App.fail "Connected device ID `#{App.config.device_id}' not provisioned in profile `#{App.config.provisioning_profile}'"
diff --git a/lib/motion/project/app.rb b/lib/motion/project/app.rb
index bf746535..ce5e27a2 100644
--- a/lib/motion/project/app.rb
+++ b/lib/motion/project/app.rb
@@ -13,8 +13,23 @@ module Motion; module Project
end
class << self
+ def config_mode
+ @config_mode or :development
+ end
+
+ def config_mode=(mode)
+ @config_mode = mode
+ end
+
+ def configs
+ @configs ||= {
+ :development => Motion::Project::Config.new('.', :development),
+ :release => Motion::Project::Config.new('.', :release)
+ }
+ end
+
def config
- @config ||= Motion::Project::Config.new('.')
+ configs[config_mode]
end
def builder
@@ -22,7 +37,7 @@ module Motion; module Project
end
def setup
- yield config
+ configs.each_value { |x| yield x }
config.validate
end
@@ -30,6 +45,10 @@ module Motion; module Project
builder.build(config, platform)
end
+ def archive
+ builder.archive(config)
+ end
+
def codesign(platform)
builder.codesign(config, platform)
end
diff --git a/lib/motion/project/builder.rb b/lib/motion/project/builder.rb
index 5ded09b1..f0f331f2 100644
--- a/lib/motion/project/builder.rb
+++ b/lib/motion/project/builder.rb
@@ -23,8 +23,9 @@ module Motion; module Project;
cc = File.join(config.platform_dir(platform), 'Developer/usr/bin/gcc')
cxx = File.join(config.platform_dir(platform), 'Developer/usr/bin/g++')
- build_dir = File.join(config.versionized_build_dir, platform)
-
+ build_dir = File.join(config.versionized_build_dir(platform))
+ App.info 'Build', build_dir
+
# Prepare the list of BridgeSupport files needed.
bs_files = config.bridgesupport_files
@@ -77,7 +78,8 @@ module Motion; module Project;
# Object.
arch_obj = File.join(objs_build_dir, "#{path}.#{arch}.o")
sh "#{cc} -fexceptions -c -arch #{arch} \"#{asm}\" -o \"#{arch_obj}\""
-
+
+ [bc, asm].each { |x| File.unlink(x) }
arch_objs << arch_obj
end
@@ -247,7 +249,8 @@ EOS
end
# Link executable.
- main_exec = File.join(bundle_path, config.name)
+ main_exec = config.app_bundle_executable(platform)
+ main_exec_created = false
if !File.exist?(main_exec) \
or File.mtime(config.project_file) > File.mtime(main_exec) \
or objs.any? { |path, _| File.mtime(path) > File.mtime(main_exec) } \
@@ -262,6 +265,7 @@ EOS
framework_stubs_objs << "\"#{stubs_obj}\"" if File.exist?(stubs_obj)
end
sh "#{cxx} -o \"#{main_exec}\" #{objs_list} #{arch_flags} #{framework_stubs_objs.join(' ')} -isysroot \"#{sdk}\" -miphoneos-version-min=#{config.deployment_target} -L#{File.join(datadir, platform)} -lmacruby-static -lobjc -licucore #{frameworks} #{config.libs.join(' ')} #{vendor_libs.map { |x| '-force_load ' + x }.join(' ')}"
+ main_exec_created = true
end
# Create bundle/Info.plist.
@@ -321,6 +325,12 @@ EOS
App.info "Create", dsym_path
sh "/usr/bin/dsymutil \"#{main_exec}\" -o \"#{dsym_path}\""
end
+
+ # Strip all symbols. Only in release mode.
+ if main_exec_created and config.release?
+ App.info "Strip", main_exec
+ sh "/usr/bin/strip \"#{main_exec}\""
+ end
end
def codesign(config, platform)
@@ -374,10 +384,57 @@ PLIST
if File.mtime(config.project_file) > File.mtime(bundle_path) \
or !system("#{codesign_cmd} --verify \"#{bundle_path}\" >& /dev/null")
App.info 'Codesign', bundle_path
- entitlements = File.join(config.versionized_build_dir, platform, "Entitlements.plist")
+ entitlements = File.join(config.versionized_build_dir(platform), "Entitlements.plist")
File.open(entitlements, 'w') { |io| io.write(config.entitlements_data) }
sh "#{codesign_cmd} -f -s \"#{config.codesign_certificate}\" --resource-rules=\"#{resource_rules_plist}\" --entitlements #{entitlements} \"#{bundle_path}\""
end
end
+
+ def archive(config)
+ # Create .ipa archive.
+ app_bundle = config.app_bundle('iPhoneOS')
+ archive = config.archive
+ if !File.exist?(archive) or File.mtime(app_bundle) > File.mtime(archive)
+ App.info 'Create', archive
+ tmp = "/tmp/ipa_root"
+ sh "/bin/rm -rf #{tmp}"
+ sh "/bin/mkdir -p #{tmp}/Payload"
+ sh "/bin/cp -r \"#{app_bundle}\" #{tmp}/Payload"
+ Dir.chdir(tmp) do
+ sh "/bin/chmod -R 755 Payload"
+ sh "/usr/bin/zip -q -r archive.zip Payload"
+ end
+ sh "/bin/cp #{tmp}/archive.zip \"#{archive}\""
+ end
+
+ # Create .xcarchive. Only in release mode.
+ if config.release?
+ xcarchive = File.join(File.dirname(app_bundle), config.name + '.xcarchive')
+ if !File.exist?(xcarchive) or File.mtime(app_bundle) > File.mtime(xcarchive)
+ App.info 'Create', xcarchive
+ apps = File.join(xcarchive, 'Products', 'Applications')
+ FileUtils.mkdir_p apps
+ sh "/bin/cp -r \"#{app_bundle}\" \"#{apps}\""
+ dsyms = File.join(xcarchive, 'dSYMs')
+ FileUtils.mkdir_p dsyms
+ sh "/bin/cp -r \"#{config.app_bundle_dsym('iPhoneOS')}\" \"#{dsyms}\""
+ app_path = "Applications/#{config.name}.app"
+ info_plist = {
+ 'ApplicationProperties' => {
+ 'ApplicationPath' => app_path,
+ 'CFBundleIdentifier' => config.identifier,
+ 'IconPaths' => config.icons.map { |x| File.join(app_path, x) },
+ },
+ 'ArchiveVersion' => 1,
+ 'CreationDate' => Time.now,
+ 'Name' => config.name,
+ 'SchemeName' => config.name
+ }
+ File.open(File.join(xcarchive, 'Info.plist'), 'w') do |io|
+ io.write Motion::PropertyList.to_s(info_plist)
+ end
+ end
+ end
+ end
end
end; end
diff --git a/lib/motion/project/config.rb b/lib/motion/project/config.rb
index fc55eb32..d6de47a5 100644
--- a/lib/motion/project/config.rb
+++ b/lib/motion/project/config.rb
@@ -30,7 +30,9 @@ module Motion; module Project
:device_family, :interface_orientations, :version, :icons,
:prerendered_icon, :seed_id, :entitlements, :fonts
- def initialize(project_dir)
+ attr_accessor :spec_mode
+
+ def initialize(project_dir, build_mode)
@project_dir = project_dir
@files = Dir.glob(File.join(project_dir, 'app/**/*.rb'))
@dependencies = {}
@@ -50,6 +52,8 @@ module Motion; module Project
@prerendered_icon = false
@vendor_projects = []
@entitlements = {}
+ @spec_mode = false
+ @build_mode = build_mode
end
def variables
@@ -65,6 +69,13 @@ module Motion; module Project
map
end
+ def load_setup(b)
+ @setups ||= []
+ @setups << b
+ b.call self
+ validate
+ end
+
def validate
# sdk_version
['iPhoneSimulator', 'iPhoneOS'].each do |platform|
@@ -103,8 +114,28 @@ module Motion; module Project
@build_dir
end
- def versionized_build_dir
- File.join(build_dir, deployment_target)
+ def build_mode_name
+ @build_mode.to_s.capitalize
+ end
+
+ def development?
+ @build_mode == :development
+ end
+
+ def release?
+ @build_mode == :release
+ end
+
+ def development
+ yield if development?
+ end
+
+ def release
+ yield if release?
+ end
+
+ def versionized_build_dir(platform)
+ File.join(build_dir, platform + '-' + deployment_target + '-' + build_mode_name)
end
attr_reader :project_dir
@@ -191,8 +222,6 @@ module Motion; module Project
end
end
- attr_accessor :spec_mode
-
def spec_files
Dir.glob(File.join(specs_dir, '**', '*.rb'))
end
@@ -243,15 +272,19 @@ module Motion; module Project
end
def app_bundle(platform)
- File.join(versionized_build_dir, platform, bundle_name + '.app')
+ File.join(versionized_build_dir(platform), bundle_name + '.app')
end
def app_bundle_dsym(platform)
- File.join(versionized_build_dir, platform, bundle_name + '.dSYM')
+ File.join(versionized_build_dir(platform), bundle_name + '.dSYM')
+ end
+
+ def app_bundle_executable(platform)
+ File.join(app_bundle(platform), name)
end
def archive
- File.join(versionized_build_dir, bundle_name + '.ipa')
+ File.join(versionized_build_dir('iPhoneOS'), bundle_name + '.ipa')
end
def identifier
diff --git a/lib/motion/project/testflight.rb b/lib/motion/project/testflight.rb
index f28da322..d39cdcbd 100644
--- a/lib/motion/project/testflight.rb
+++ b/lib/motion/project/testflight.rb
@@ -72,8 +72,8 @@ module Motion; module Project; class Config
end
end; end; end
-desc "Submit build to TestFlight"
-task :testflight => :archive do
+desc "Submit a development archive to TestFlight"
+task :testflight => 'archive:development' do
# Retrieve configuration settings.
prefs = App.config.testflight
unless prefs.api_token