diff --git a/deploygate.gemspec b/deploygate.gemspec index bdbb466..bf0f7dd 100644 --- a/deploygate.gemspec +++ b/deploygate.gemspec @@ -32,6 +32,7 @@ POST_INSTALL_MESSAGE spec.add_dependency 'xcodeproj', '~> 0.28.2' spec.add_dependency 'github_issue_request', '~> 0.0.2' spec.add_dependency 'highline', '~> 1.7.8' + spec.add_dependency 'uuid', '~> 2.3.8' # ios build spec.add_dependency 'gym', '~> 1.0.0' diff --git a/lib/deploygate.rb b/lib/deploygate.rb index 82bbed6..5e3ae48 100644 --- a/lib/deploygate.rb +++ b/lib/deploygate.rb @@ -9,6 +9,7 @@ require "find" require "github_issue_request" require "highline" +require "uuid" # ios build require "gym" diff --git a/lib/deploygate/builds/ios/analyze.rb b/lib/deploygate/builds/ios/analyze.rb index 936fbcc..21603f3 100644 --- a/lib/deploygate/builds/ios/analyze.rb +++ b/lib/deploygate/builds/ios/analyze.rb @@ -30,22 +30,38 @@ def initialize(workspaces) # @return [String] def target_bundle_identifier - scheme_file = find_xcschemes - xs = Xcodeproj::XCScheme.new(scheme_file) - target_name = xs.profile_action.buildable_product_runnable.buildable_reference.target_name - - project = Xcodeproj::Project.open(@xcodeproj) - target = project.native_targets.reject{|target| target.name != target_name}.first - product_name = target.product_name - conf = target.build_configuration_list.build_configurations.reject{|conf| conf.name != BUILD_CONFIGRATION}.first - identifier = conf.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] + product_name = target_product_name + identifier = target_build_configration.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] identifier.gsub!(/\$\(PRODUCT_NAME:.+\)/, product_name) identifier end + # @return [String] + def target_xcode_setting_provisioning_profile_uuid + uuid = target_build_configration.build_settings['PROVISIONING_PROFILE'] + UUID.validate(uuid) ? uuid : nil + end + private + def target_build_configration + target_project_setting.build_configuration_list.build_configurations.reject{|conf| conf.name != BUILD_CONFIGRATION}.first + end + + def target_product_name + target_project_setting.product_name + end + + def target_project_setting + scheme_file = find_xcschemes + xs = Xcodeproj::XCScheme.new(scheme_file) + target_name = xs.profile_action.buildable_product_runnable.buildable_reference.target_name + + project = Xcodeproj::Project.open(@xcodeproj) + project.native_targets.reject{|target| target.name != target_name}.first + end + def find_xcschemes shared_schemes = Dir[File.join(@xcodeproj, 'xcshareddata', 'xcschemes', '*.xcscheme')].reject do |scheme| @scheme != File.basename(scheme, '.xcscheme') diff --git a/lib/deploygate/builds/ios/export.rb b/lib/deploygate/builds/ios/export.rb index 3ee5510..e8a17f8 100644 --- a/lib/deploygate/builds/ios/export.rb +++ b/lib/deploygate/builds/ios/export.rb @@ -9,25 +9,29 @@ class Export class << self # @param [String] bundle_identifier + # @param [String] uuid # @return [Hash] - def find_local_data(bundle_identifier) + def find_local_data(bundle_identifier, uuid = nil) result_profiles = {} teams = {} - profiles.each do |profile_path| - plist = analyze_profile(profile_path) - entities = plist['Entitlements'] + profile_paths = load_profile_paths + profiles = profile_paths.map{|p| profile_to_plist(p)} + profiles.reject! {|profile| profile['UUID'] != uuid} unless uuid.nil? + + profiles.each do |profile| + entities = profile['Entitlements'] unless entities['get-task-allow'] team = entities['com.apple.developer.team-identifier'] application_id = entities['application-identifier'] application_id.slice!(/^#{team}\./) application_id = '.' + application_id if application_id == '*' if bundle_identifier.match(application_id) && - DateTime.now < plist['ExpirationDate'] && - installed_certificate?(profile_path) + DateTime.now < profile['ExpirationDate'] && + installed_certificate?(profile['Path']) - teams[team] = plist['TeamName'] if teams[team].nil? + teams[team] = profile['TeamName'] if teams[team].nil? result_profiles[team] = [] if result_profiles[team].nil? - result_profiles[team].push(profile_path) + result_profiles[team].push(profile['Path']) end end end @@ -41,11 +45,14 @@ def find_local_data(bundle_identifier) # @param [String] profile_path # @return [Boolean] def installed_certificate?(profile_path) - plist = analyze_profile(profile_path) - certificate_str = plist['DeveloperCertificates'].first.read - certificate = OpenSSL::X509::Certificate.new certificate_str - id = OpenSSL::Digest::SHA1.new(certificate.to_der).to_s.upcase! - installed_identies.include?(id) + profile = profile_to_plist(profile_path) + certs = profile['DeveloperCertificates'].map do |cert| + certificate_str = cert.read + certificate = OpenSSL::X509::Certificate.new certificate_str + id = OpenSSL::Digest::SHA1.new(certificate.to_der).to_s.upcase! + installed_identies.include?(id) + end + certs.include?(true) end # @return [Array] @@ -64,14 +71,14 @@ def installed_identies ids end - # @param [Array] profiles + # @param [Array] profile_paths # @return [String] - def select_profile(profiles) + def select_profile(profile_paths) select = nil - profiles.each do |profile| - select = profile if adhoc?(profile) && select.nil? - select = profile if inhouse?(profile) + profile_paths.each do |path| + select = path if adhoc?(path) && select.nil? + select = path if inhouse?(path) end select end @@ -79,10 +86,24 @@ def select_profile(profiles) # @param [String] profile_path # @return [String] def codesigning_identity(profile_path) - plist = analyze_profile(profile_path) - method = method(profile_path) - identity = "iPhone Distribution: #{plist['TeamName']}" - identity += " (#{plist['Entitlements']['com.apple.developer.team-identifier']})" if method == AD_HOC + profile = profile_to_plist(profile_path) + identity = nil + + profile['DeveloperCertificates'].each do |cert| + certificate_str = cert.read + certificate = OpenSSL::X509::Certificate.new certificate_str + id = OpenSSL::Digest::SHA1.new(certificate.to_der).to_s.upcase! + + available = `security find-identity -v -p codesigning` + available.split("\n").each do |current| + next if current.include? "REVOKED" + begin + search = current.match(/.*\) (.*) \"(.*)\"/) + identity = search[2] if id == search[1] + rescue + end + end + end identity end @@ -96,46 +117,32 @@ def method(profile_path) # @param [String] profile_path # @return [Boolean] def adhoc?(profile_path) - plist = analyze_profile(profile_path) - !plist['Entitlements']['get-task-allow'] && plist['ProvisionsAllDevices'].nil? + profile = profile_to_plist(profile_path) + !profile['Entitlements']['get-task-allow'] && profile['ProvisionsAllDevices'].nil? end # @param [String] profile_path # @return [Boolean] def inhouse?(profile_path) - plist = analyze_profile(profile_path) - !plist['Entitlements']['get-task-allow'] && !plist['ProvisionsAllDevices'].nil? + profile = profile_to_plist(profile_path) + !profile['Entitlements']['get-task-allow'] && !profile['ProvisionsAllDevices'].nil? + end + + def load_profile_paths + profiles_path = File.expand_path("~") + "/Library/MobileDevice/Provisioning Profiles/*.mobileprovision" + Dir[profiles_path] end # @param [String] profile_path # @return [Hash] - def analyze_profile(profile_path) - plist = nil + def profile_to_plist(profile_path) File.open(profile_path) do |profile| asn1 = OpenSSL::ASN1.decode(profile.read) plist_str = asn1.value[1].value[0].value[2].value[1].value[0].value plist = Plist.parse_xml plist_str.force_encoding('UTF-8') + plist['Path'] = profile_path + return plist end - plist - end - - # @return [Array] - def profiles - profiles = [] - Find.find(profile_dir_path) do |path| - next if path == profile_dir_path - Find.prune if FileTest.directory?(path) - if File.extname(path) == PROFILE_EXTNAME - profiles.push(path) - end - end - - profiles - end - - # @return [String] - def profile_dir_path - File.join(ENV['HOME'], 'Library/MobileDevice/Provisioning Profiles') end end end diff --git a/lib/deploygate/builds/ios/set_profile.rb b/lib/deploygate/builds/ios/set_profile.rb index cf457d1..fa5b8f4 100644 --- a/lib/deploygate/builds/ios/set_profile.rb +++ b/lib/deploygate/builds/ios/set_profile.rb @@ -39,8 +39,54 @@ def app_id_create false end + # @param [String] uuid # @return [Array] - def create_provisioning + def create_provisioning(uuid) + FileUtils.mkdir_p(OUTPUT_PATH) + + if uuid.nil? + return install_provisioning + else + return select_uuid_provisioning(uuid) + end + end + + private + + def select_uuid_provisioning(uuid) + adhoc_profiles = Spaceship.provisioning_profile.ad_hoc.all + inhouse_profiles = Spaceship.provisioning_profile.in_house.all + + adhoc_profiles.reject!{|p| p.uuid != uuid} + inhouse_profiles.reject!{|p| p.uuid != uuid} + select_profile = nil + method = nil + unless adhoc_profiles.empty? + select_profile = adhoc_profiles.first + method = Export::AD_HOC + end + unless inhouse_profiles.empty? + select_profile = inhouse_profiles.first + method = Export::ENTERPRISE + end + raise 'Not Xcode selected Provisioning Profile' if select_profile.nil? + + values = { + :adhoc => method == Export::AD_HOC ? true : false, + :app_identifier => @identifier, + :username => @username, + :output_path => OUTPUT_PATH, + :provisioning_name => select_profile.name, + :team_id => Spaceship.client.team_id + } + v = FastlaneCore::Configuration.create(Sigh::Options.available_options, values) + Sigh.config = v + download_profile_path = Sigh::Manager.start + + [download_profile_path] + end + + def install_provisioning if @method == Export::AD_HOC prod_certs = Spaceship.certificate.production.all else @@ -58,7 +104,6 @@ def create_provisioning end raise 'Not local install certificate' if distribution_cert_ids.empty? - FileUtils.mkdir_p(OUTPUT_PATH) provisionings = [] distribution_cert_ids.each do |cert_id| values = { @@ -71,7 +116,8 @@ def create_provisioning } v = FastlaneCore::Configuration.create(Sigh::Options.available_options, values) Sigh.config = v - provisionings.push(Sigh::Manager.start) + download_profile_path = Sigh::Manager.start + provisionings.push(download_profile_path) end provisionings diff --git a/lib/deploygate/command_builder.rb b/lib/deploygate/command_builder.rb index 0ab6405..0ce1cd6 100644 --- a/lib/deploygate/command_builder.rb +++ b/lib/deploygate/command_builder.rb @@ -17,7 +17,7 @@ def run begin Commands::Init.run rescue => e - error_handling("Commands::Init Error: #{e.class}", e.message, ['bug', 'Init']) + error_handling("Commands::Init Error: #{e.class}", create_error_issue_body(e), ['bug', 'Init']) raise e end end @@ -35,7 +35,7 @@ def run begin Commands::Deploy.run(args, options) rescue => e - error_handling("Commands::Deploy Error: #{e.class}", e.message, ['bug', 'Deploy']) + error_handling("Commands::Deploy Error: #{e.class}", create_error_issue_body(e), ['bug', 'Deploy']) raise e end end @@ -49,7 +49,7 @@ def run begin Commands::Logout.run rescue => e - error_handling("Commands::Logout Error: #{e.class}", e.message, ['bug', 'Logout']) + error_handling("Commands::Logout Error: #{e.class}", create_error_issue_body(e), ['bug', 'Logout']) raise e end end @@ -58,6 +58,23 @@ def run run! end + # @param [Exception] error + # @return [String] + def create_error_issue_body(error) + return < title, diff --git a/lib/deploygate/commands/deploy/build.rb b/lib/deploygate/commands/deploy/build.rb index 2c6df19..5f5ea81 100644 --- a/lib/deploygate/commands/deploy/build.rb +++ b/lib/deploygate/commands/deploy/build.rb @@ -34,14 +34,15 @@ def ios(workspaces, options) puts 'Example: com.example.ios' identifier = input_bundle_identifier end + uuid = analyze.target_xcode_setting_provisioning_profile_uuid - data = DeployGate::Builds::Ios::Export.find_local_data(identifier) + data = DeployGate::Builds::Ios::Export.find_local_data(identifier, uuid) profiles = data[:profiles] teams = data[:teams] target_provisioning_profile = nil if teams.empty? - target_provisioning_profile = create_provisioning(identifier) + target_provisioning_profile = create_provisioning(identifier, uuid) elsif teams.count == 1 target_provisioning_profile = DeployGate::Builds::Ios::Export.select_profile(profiles[teams.keys.first]) elsif teams.count >= 2 @@ -98,8 +99,9 @@ def select_teams(teams, profiles) end # @param [String] identifier + # @param [String] uuid # @return [String] - def create_provisioning(identifier) + def create_provisioning(identifier, uuid) puts < e DeployGate::Message::Error.print("Error: Failed to create provisioning profile") raise e diff --git a/lib/deploygate/version.rb b/lib/deploygate/version.rb index 54b6a83..8421afa 100644 --- a/lib/deploygate/version.rb +++ b/lib/deploygate/version.rb @@ -1,3 +1,3 @@ module DeployGate - VERSION = "0.0.2" + VERSION = "0.0.3" end diff --git a/spec/deploygate/builds/ios/export_spec.rb b/spec/deploygate/builds/ios/export_spec.rb index 6b52d7d..4a61701 100644 --- a/spec/deploygate/builds/ios/export_spec.rb +++ b/spec/deploygate/builds/ios/export_spec.rb @@ -1,58 +1,58 @@ describe DeployGate::Builds::Ios::Export do describe "#adhoc?" do it "when adhoc plist" do - plist = { + profile = { 'ProvisionsAllDevices' => nil, 'Entitlements' => {'get-task-allow' => false} } - allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + allow(DeployGate::Builds::Ios::Export).to receive(:profile_to_plist).and_return(profile) expect(DeployGate::Builds::Ios::Export.adhoc?('path')).to be_truthy end it "when inhouse plist" do - plist = { + profile = { 'ProvisionsAllDevices' => true, 'Entitlements' => {'get-task-allow' => false} } - allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + allow(DeployGate::Builds::Ios::Export).to receive(:profile_to_plist).and_return(profile) expect(DeployGate::Builds::Ios::Export.adhoc?('path')).to be_falsey end it "when not distribution plist" do - plist = { + profile = { 'ProvisionsAllDevices' => nil, 'Entitlements' => {'get-task-allow' => true} } - allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + allow(DeployGate::Builds::Ios::Export).to receive(:profile_to_plist).and_return(profile) expect(DeployGate::Builds::Ios::Export.adhoc?('path')).to be_falsey end end describe "#inhouse?" do it "when adhoc plist" do - plist = { + profile = { 'ProvisionsAllDevices' => nil, 'Entitlements' => {'get-task-allow' => false} } - allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + allow(DeployGate::Builds::Ios::Export).to receive(:profile_to_plist).and_return(profile) expect(DeployGate::Builds::Ios::Export.inhouse?('path')).to be_falsey end it "when inhouse plist" do - plist = { + profile = { 'ProvisionsAllDevices' => true, 'Entitlements' => {'get-task-allow' => false} } - allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + allow(DeployGate::Builds::Ios::Export).to receive(:profile_to_plist).and_return(profile) expect(DeployGate::Builds::Ios::Export.inhouse?('path')).to be_truthy end it "when not distribution plist" do - plist = { + profile = { 'ProvisionsAllDevices' => nil, 'Entitlements' => {'get-task-allow' => true} } - allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + allow(DeployGate::Builds::Ios::Export).to receive(:profile_to_plist).and_return(profile) expect(DeployGate::Builds::Ios::Export.inhouse?('path')).to be_falsey end end