require 'aws-sdk' require 'cfpropertylist' require 'json' # Workflow # Add gem to Gemfile or gem install # $ ios3 manifest # Creates a Manifest File from the template # $ ios3 upload # Uploads the Manifest and the .ipa from the RubyMotion build # First step is to upload the .ipa # After the ipa is uploaded then we can know the url of the ipa on S3 and put that in the manifest # Then we can upload the manifest # What info do I need from the user? # Bucket to upload to # Path within Bucket to upload to # this should be automatically /ios3/CFBundleName_underscores/CFBundleVersion # # # What do I need from the Filesystem? # AWSKEY # AWSSECRET # AWSREGION # CFBundleVersion # 1.0.0 # CFBundleIdentifier # com.humanswithkids.Blog # CFBundleName module S3 class Client def initialize(access_key_id, secret_access_key, region) # credentials = Aws.Credentials.new(access_key_id, secret_access_key) # @s3 = Aws::S3::Client.new(credentials: credentials, region: region) @s3 = Aws::S3::Client.new(:access_key_id => access_key_id, :secret_access_key => secret_access_key, :region => region) end def upload_build(ipa, options) path = options[:path] files = [] files << ipa # files << options[:dsym] if options[:dsym] files.each do |file| basename = File.basename(file) key = path ? File.join(path, basename) : basename File.open(file) do |descriptor| params = {body: descriptor, bucket: options[:bucket], key: key, acl: options[:acl]} response = @s3.put_object(params) upload_manifest(options) end end end private def upload_manifest(options) key = File.join(options[:path], "#{options[:cf_bundle_name]}.plist") params = {body: build_manifest_plist(options), bucket: options[:bucket], key: key, acl: options[:acl] } @s3.put_object(params) puts itms_url(options) end def itms_url(options) "itms-services://?#{URI.encode_www_form(:action => "download-manifest", :url => plist_url(options))}" end def plist_url(options) "https://s3.amazonaws.com/#{options[:bucket]}/#{options[:path]}/#{options[:cf_bundle_name]}.plist" end def determine_ipa_url(options) CGI.escapeHTML("https://s3.amazonaws.com/#{options[:bucket]}/#{options[:path]}/#{options[:cf_bundle_name]}.ipa") end def build_manifest_plist(options) ipa_url = determine_ipa_url(options) cf_bundle_identifier = options[:cf_bundle_identifier] cf_bundle_version = options[:cf_bundle_version] cf_bundle_name = options[:cf_bundle_name] # ERB.new(File.read('templates/manifest.plist.erb')).result(binding) return ERB.new(File.read(File.join(File.dirname(__FILE__), '..', '..', 'templates', 'manifest.plist.erb'))).result(binding) end # private # # def expand_path_with_substitutions_from_ipa_plist(ipa, path) # substitutions = path.scan(/\{CFBundle[^}]+\}/) # return path if substitutions.empty? # # Dir.mktmpdir do |dir| # system "unzip -q #{ipa} -d #{dir} 2> /dev/null" # # plist = Dir["#{dir}/**/*.app/Info.plist"].last # # substitutions.uniq.each do |substitution| # key = substitution[1...-1] # value = Shenzhen::PlistBuddy.print(plist, key) # # path.gsub!(Regexp.new(substitution), value) if value # end # end # # return path # end end end command :'testing' do |c| c.syntax = "ios3 testing [options]" c.summary = "CFBundle{Something}" c.description = "" c.action do |args, options| say_ok "Testing" # determine_cf_bundle_info! # ERB.new(File.read('templates/manifest.plist.erb')).result(binding) # puts File.read('templates/manifest.plist.erb') # ERB.new(File.read(File.join(File.dirname(__FILE__), '..', '..', 'templates', 'manifest.plist.erb'))).result(binding) puts File.read(File.join(File.dirname(__FILE__), '..', '..', 'templates', 'manifest.plist.erb')) say_ok "Done Testing" end end command :'upload' do |c| c.syntax = "ios3 upload [options]" c.summary = "Distribute an .ipa file over Amazon S3" c.description = "" c.example '', '$ ios3 upload -f ./file.ipa -a accesskeyid --bucket bucket-name' c.option '-b', '--bucket BUCKET', "S3 bucket" c.action do |args, options| determine_ipa! say_error "Missing or unspecified .ipa file" and abort unless @ipa and File.exist?(@ipa) determine_access_key_id! say_error "Missing AWS Access Key ID" and abort unless @access_key_id determine_secret_access_key! say_error "Missing AWS Secret Access Key" and abort unless @secret_access_key determine_bucket! unless @bucket = options.bucket say_error "Missing bucket" and abort unless @bucket determine_region! determine_acl! determine_cf_bundle_info! determine_s3_path! client = S3::Client.new(@access_key_id, @secret_access_key, @region) begin say_ok "Uploading..." client.upload_build @ipa, {:bucket => @bucket, :acl => @acl, :path => @s3_path, :cf_bundle_name => @cf_bundle_name, :cf_bundle_version => @cf_bundle_version, :cf_bundle_identifier => @cf_bundle_identifier} say_ok "Build successfully uploaded to S3" rescue => exception say_error "Error while uploading to S3: #{exception}" end end private def path_to_ipa Dir.exists?("test-data") ? "./test-data/build/{iPhoneOS}*-Release/*.{ipa}" : "./build/{iPhoneOS}*-Release/*.{ipa}" end def path_to_plist path = Dir.exists?("test-data") ? "./test-data/build/{iPhoneOS}*-Release/*.app/Info.plist" : "./build/{iPhoneOS}*-Release/*.app/Info.plist" Dir[path].last end def determine_ipa! unless File.exist?('Rakefile') say_error "Run on root directoy of RubyMotion project." end # plist = Dir["./test-data/build/{iPhoneOS}*-Release/*.app/Info.plist"].last # select *.ipa in Release directory. @ipa = Dir.glob(path_to_ipa).first unless @ipa say_error "Can't find *.ipa. First, need to create archive with `rake archive:distribution'." end @ipa end def determine_access_key_id! @access_key_id ||= ENV['AWS_ACCESS_KEY_ID'] @access_key_id ||= ask "Access Key ID:" end def determine_secret_access_key! @secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY'] @secret_access_key ||= ask "Secret Access Key:" end def determine_bucket! @bucket ||= ENV['S3_BUCKET'] @bucket ||= ask "S3 Bucket:" end def determine_region! @region ||= ENV['AWS_REGION'] || "us-east-1" end def determine_acl! @acl = "public-read" end def determine_s3_path! @s3_path = "ad-hoc-distribution/#{@cf_bundle_name}/#{@cf_bundle_version}" end def determine_cf_bundle_info! plist = CFPropertyList::List.new(:file => path_to_plist) data = CFPropertyList.native_types(plist.value) @cf_bundle_name = data['CFBundleName'] @cf_bundle_version = data['CFBundleVersion'] @cf_bundle_identifier = data['CFBundleIdentifier'] end end