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