diff --git a/lib/motion/project/builder.rb b/lib/motion/project/builder.rb index 0f61c7c5..12edb919 100644 --- a/lib/motion/project/builder.rb +++ b/lib/motion/project/builder.rb @@ -426,112 +426,6 @@ EOS end end - def codesign(config, platform) - bundle_path = config.app_bundle(platform) - raise unless File.exist?(bundle_path) - - # Create bundle/ResourceRules.plist. - resource_rules_plist = File.join(bundle_path, 'ResourceRules.plist') - unless File.exist?(resource_rules_plist) - App.info 'Create', resource_rules_plist - File.open(resource_rules_plist, 'w') do |io| - io.write(<<-PLIST) - - - - - rules - - .* - - Info.plist - - omit - - weight - 10 - - ResourceRules.plist - - omit - - weight - 100 - - - - -PLIST - end - end - - # Copy the provisioning profile. - bundle_provision = File.join(bundle_path, "embedded.mobileprovision") - if !File.exist?(bundle_provision) or File.mtime(config.provisioning_profile) > File.mtime(bundle_provision) - App.info 'Create', bundle_provision - FileUtils.cp config.provisioning_profile, bundle_provision - end - - # Codesign. - codesign_cmd = "CODESIGN_ALLOCATE=\"#{File.join(config.platform_dir(platform), 'Developer/usr/bin/codesign_allocate')}\" /usr/bin/codesign" - 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") - 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 - -=begin - # 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 - class << self def common_build_dir dir = File.expand_path("~/Library/RubyMotion/build") diff --git a/lib/motion/project/template/ios.rb b/lib/motion/project/template/ios.rb index 42fba061..a39a779a 100644 --- a/lib/motion/project/template/ios.rb +++ b/lib/motion/project/template/ios.rb @@ -28,6 +28,7 @@ App.template = :ios require 'motion/project' require 'motion/project/template/ios/config' +require 'motion/project/template/ios/builder' desc "Build the project, then run the simulator" task :default => :simulator @@ -102,8 +103,7 @@ namespace :archive do task :distribution do App.config_without_setup.build_mode = :release App.config.distribution_mode = true - Rake::Task["build:device"].invoke - App.archive + Rake::Task["archive"].invoke end end diff --git a/lib/motion/project/template/ios/builder.rb b/lib/motion/project/template/ios/builder.rb new file mode 100644 index 00000000..746053d4 --- /dev/null +++ b/lib/motion/project/template/ios/builder.rb @@ -0,0 +1,103 @@ +# Copyright (c) 2012, HipByte SPRL and contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +require 'motion/project/builder' + +module Motion; module Project + class Builder + 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 + end + + def codesign(config, platform) + bundle_path = config.app_bundle(platform) + raise unless File.exist?(bundle_path) + + # Create bundle/ResourceRules.plist. + resource_rules_plist = File.join(bundle_path, 'ResourceRules.plist') + unless File.exist?(resource_rules_plist) + App.info 'Create', resource_rules_plist + File.open(resource_rules_plist, 'w') do |io| + io.write(<<-PLIST) + + + + + rules + + .* + + Info.plist + + omit + + weight + 10 + + ResourceRules.plist + + omit + + weight + 100 + + + + +PLIST + end + end + + # Copy the provisioning profile. + bundle_provision = File.join(bundle_path, "embedded.mobileprovision") + if !File.exist?(bundle_provision) or File.mtime(config.provisioning_profile) > File.mtime(bundle_provision) + App.info 'Create', bundle_provision + FileUtils.cp config.provisioning_profile, bundle_provision + end + + # Codesign. + codesign_cmd = "CODESIGN_ALLOCATE=\"#{File.join(config.platform_dir(platform), 'Developer/usr/bin/codesign_allocate')}\" /usr/bin/codesign" + 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") + 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 + end +end; end diff --git a/lib/motion/project/template/ios/config.rb b/lib/motion/project/template/ios/config.rb index 4d263630..7ff771a1 100644 --- a/lib/motion/project/template/ios/config.rb +++ b/lib/motion/project/template/ios/config.rb @@ -28,7 +28,8 @@ module Motion; module Project; register :ios variable :device_family, :interface_orientations, :background_modes, - :status_bar_style, :icons, :prerendered_icon, :fonts + :status_bar_style, :icons, :prerendered_icon, :fonts, :seed_id, + :provisioning_profile def initialize(project_dir, build_mode) super @@ -67,6 +68,64 @@ module Motion; module Project; App.fail "Can't locate compilers for platform `#{platform}'" end + def archive_extension + '.ipa' + end + + def codesign_certificate + super('iPhone') + end + + def provisioning_profile(name = /iOS Team Provisioning Profile/) + @provisioning_profile ||= begin + paths = Dir.glob(File.expand_path("~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision")).select do |path| + text = File.read(path) + text.force_encoding('binary') if RUBY_VERSION >= '1.9.0' + text.scan(/\s*Name\s*<\/key>\s*\s*([^<]+)\s*<\/string>/)[0][0].match(name) + end + if paths.size == 0 + App.fail "Can't find a provisioning profile named `#{name}'" + elsif paths.size > 1 + App.warn "Found #{paths.size} provisioning profiles named `#{name}'. Set the `provisioning_profile' project setting. Will use the first one: `#{paths[0]}'" + end + paths[0] + end + end + + def read_provisioned_profile_array(key) + text = File.read(provisioning_profile) + text.force_encoding('binary') if RUBY_VERSION >= '1.9.0' + text.scan(/\s*#{key}\s*<\/key>\s*(.*?)\s*<\/array>/m)[0][0].scan(/(.*?)<\/string>/).map { |str| str[0].strip } + end + private :read_provisioned_profile_array + + def provisioned_devices + @provisioned_devices ||= read_provisioned_profile_array('ProvisionedDevices') + end + + def seed_id + @seed_id ||= begin + seed_ids = read_provisioned_profile_array('ApplicationIdentifierPrefix') + if seed_ids.size == 0 + App.fail "Can't find an application seed ID in the provisioning profile `#{provisioning_profile}'" + elsif seed_ids.size > 1 + App.warn "Found #{seed_ids.size} seed IDs in the provisioning profile. Set the `seed_id' project setting. Will use the last one: `#{seed_ids.last}'" + end + seed_ids.last + end + end + + def entitlements_data + dict = entitlements + if distribution_mode + dict['application-identifier'] ||= seed_id + '.' + identifier + else + # Required for gdb. + dict['get-task-allow'] = true if dict['get-task-allow'].nil? + end + Motion::PropertyList.to_s(dict) + end + def common_flags(platform) super + " -miphoneos-version-min=#{deployment_target}" end diff --git a/lib/motion/project/template/osx.rb b/lib/motion/project/template/osx.rb index cc9b2bff..23b328b9 100644 --- a/lib/motion/project/template/osx.rb +++ b/lib/motion/project/template/osx.rb @@ -28,17 +28,26 @@ App.template = :osx require 'motion/project' require 'motion/project/template/osx/config' +require 'motion/project/template/osx/builder' desc "Build the project, then run it" task :default => :run -desc "Build the project" -task :build do - App.build('MacOSX') +namespace :build do + desc "Build the project for development" + task :development do + App.build('MacOSX') + end + + desc "Build the project for release" + task :release do + App.config_without_setup.build_mode = :release + App.build('MacOSX') + end end desc "Run the project" -task :run => 'build' do +task :run => 'build:development' do exec = App.config.app_bundle_executable('MacOSX') env = '' env << 'SIM_SPEC_MODE=1' if App.config.spec_mode @@ -55,3 +64,17 @@ task :spec do App.config.spec_mode = true Rake::Task["run"].invoke end + +desc "Create a .pkg archive" +task :archive => 'build:release' do + App.codesign('MacOSX') + App.archive +end + +namespace :archive do + desc "Create a .pkg archive for distribution (AppStore)" + task :distribution do + App.config.distribution_mode = true + Rake::Task['archive'].invoke + end +end diff --git a/lib/motion/project/template/osx/builder.rb b/lib/motion/project/template/osx/builder.rb new file mode 100644 index 00000000..53421d64 --- /dev/null +++ b/lib/motion/project/template/osx/builder.rb @@ -0,0 +1,47 @@ +# Copyright (c) 2012, HipByte SPRL and contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +require 'motion/project/builder' + +module Motion; module Project + class Builder + def archive(config) + # Create .pkg archive. + app_bundle = config.app_bundle_raw('MacOSX') + archive = config.archive + if !File.exist?(archive) or File.mtime(app_bundle) > File.mtime(archive) + App.info 'Create', archive + sh "/usr/bin/productbuild --quiet --component \"#{app_bundle}\" /Applications \"#{archive}\"" + end + end + + def codesign(config, platform) + app_bundle = config.app_bundle_raw('MacOSX') + if File.mtime(config.project_file) > File.mtime(app_bundle) \ + or !system("/usr/bin/codesign --verify \"#{app_bundle}\" >& /dev/null") + App.info 'Codesign', app_bundle + sh "/usr/bin/codesign --force --sign \"#{config.codesign_certificate}\" \"#{app_bundle}\"" + end + end + end +end; end diff --git a/lib/motion/project/template/osx/config.rb b/lib/motion/project/template/osx/config.rb index 67bb6713..7e8b58ea 100644 --- a/lib/motion/project/template/osx/config.rb +++ b/lib/motion/project/template/osx/config.rb @@ -62,12 +62,24 @@ module Motion; module Project; App.fail "Can't locate compilers for platform `#{platform}'" end + def archive_extension + '.pkg' + end + + def codesign_certificate + super('Mac') + end + def common_flags(platform) super + " -mmacosx-version-min=#{deployment_target}" end + def app_bundle_raw(platform) + File.join(versionized_build_dir(platform), bundle_name + '.app') + end + def app_bundle(platform) - File.join(versionized_build_dir(platform), bundle_name + '.app', 'Contents') + File.join(app_bundle_raw(platform), 'Contents') end def app_bundle_executable(platform) diff --git a/lib/motion/project/xcode_config.rb b/lib/motion/project/xcode_config.rb index f4d8758f..a57cec65 100644 --- a/lib/motion/project/xcode_config.rb +++ b/lib/motion/project/xcode_config.rb @@ -25,8 +25,8 @@ module Motion; module Project; class XcodeConfig < Config variable :xcode_dir, :sdk_version, :deployment_target, :frameworks, :weak_frameworks, :framework_search_paths, :libs, :resources_dirs, - :identifier, :codesign_certificate, :provisioning_profile, - :short_version, :seed_id, :entitlements, :delegate_class + :identifier, :codesign_certificate, :short_version, :entitlements, + :delegate_class def initialize(project_dir, build_mode) super @@ -239,8 +239,12 @@ EOS File.join(versionized_build_dir(platform), bundle_name + '.dSYM') end + def archive_extension + raise "not implemented" + end + def archive - File.join(versionized_build_dir(deploy_platform), bundle_name + '.ipa') + File.join(versionized_build_dir(deploy_platform), bundle_name + archive_extension) end def identifier @@ -276,69 +280,19 @@ EOS "AAPL#{@bundle_signature}" end - def codesign_certificate + def codesign_certificate(platform) @codesign_certificate ||= begin cert_type = (distribution_mode ? 'Distribution' : 'Developer') - certs = `/usr/bin/security -q find-certificate -a`.scan(/"iPhone #{cert_type}: [^"]+"/).uniq + certs = `/usr/bin/security -q find-certificate -a`.scan(/"#{platform} #{cert_type}: [^"]+"/).uniq if certs.size == 0 - App.fail "Can't find an iPhone Developer certificate in the keychain" + App.fail "Cannot find any #{platform} #{cert_type} certificate in the keychain" elsif certs.size > 1 - App.warn "Found #{certs.size} iPhone Developer certificates in the keychain. Set the `codesign_certificate' project setting. Will use the first certificate: `#{certs[0]}'" + App.warn "Found #{certs.size} #{platform} #{cert_type} certificates in the keychain. Set the `codesign_certificate' project setting. Will use the first certificate: `#{certs[0]}'" end certs[0][1..-2] # trim trailing `"` characters end end - def provisioning_profile(name = /iOS Team Provisioning Profile/) - @provisioning_profile ||= begin - paths = Dir.glob(File.expand_path("~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision")).select do |path| - text = File.read(path) - text.force_encoding('binary') if RUBY_VERSION >= '1.9.0' - text.scan(/\s*Name\s*<\/key>\s*\s*([^<]+)\s*<\/string>/)[0][0].match(name) - end - if paths.size == 0 - App.fail "Can't find a provisioning profile named `#{name}'" - elsif paths.size > 1 - App.warn "Found #{paths.size} provisioning profiles named `#{name}'. Set the `provisioning_profile' project setting. Will use the first one: `#{paths[0]}'" - end - paths[0] - end - end - - def read_provisioned_profile_array(key) - text = File.read(provisioning_profile) - text.force_encoding('binary') if RUBY_VERSION >= '1.9.0' - text.scan(/\s*#{key}\s*<\/key>\s*(.*?)\s*<\/array>/m)[0][0].scan(/(.*?)<\/string>/).map { |str| str[0].strip } - end - private :read_provisioned_profile_array - - def provisioned_devices - @provisioned_devices ||= read_provisioned_profile_array('ProvisionedDevices') - end - - def seed_id - @seed_id ||= begin - seed_ids = read_provisioned_profile_array('ApplicationIdentifierPrefix') - if seed_ids.size == 0 - App.fail "Can't find an application seed ID in the provisioning profile `#{provisioning_profile}'" - elsif seed_ids.size > 1 - App.warn "Found #{seed_ids.size} seed IDs in the provisioning profile. Set the `seed_id' project setting. Will use the last one: `#{seed_ids.last}'" - end - seed_ids.last - end - end - - def entitlements_data - dict = entitlements - if distribution_mode - dict['application-identifier'] ||= seed_id + '.' + identifier - else - # Required for gdb. - dict['get-task-allow'] = true if dict['get-task-allow'].nil? - end - Motion::PropertyList.to_s(dict) - end - def gen_bridge_metadata(headers, bs_file, c_flags, exceptions=[]) sdk_path = self.sdk(local_platform) includes = headers.map { |header| "-I'#{File.dirname(header)}'" }.uniq