diff --git a/README.md b/README.md index 0a96e5b..2ca8882 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,19 @@ $ gem install deploygate ``` ## Usage -Upload an application: + +### Upload apps ``` $ dg deploy [package file path] ``` +### iOS build and upload + +``` +$ dg deploy [ios project path] +``` + ## License Copyright (C) 2015 DeployGate All rights reserved. diff --git a/deploygate.gemspec b/deploygate.gemspec index 3ee3f6f..bdbb466 100644 --- a/deploygate.gemspec +++ b/deploygate.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |spec| dg installed! To get started fast: - $ dg deploy [app_file_path] + $ dg deploy Or see the docs at: @@ -28,6 +28,15 @@ POST_INSTALL_MESSAGE spec.add_dependency 'httpclient', '~> 2.2.5' spec.add_dependency 'commander', '~> 4.3.5' spec.add_dependency 'color_echo', '~> 2.0.1' + spec.add_dependency 'plist', '~> 3.1.0' + spec.add_dependency 'xcodeproj', '~> 0.28.2' + spec.add_dependency 'github_issue_request', '~> 0.0.2' + spec.add_dependency 'highline', '~> 1.7.8' + + # ios build + spec.add_dependency 'gym', '~> 1.0.0' + spec.add_dependency 'spaceship', '~> 0.12.3' + spec.add_dependency 'sigh', '~> 1.1.0' spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rake" diff --git a/lib/deploygate.rb b/lib/deploygate.rb index 21107b9..82bbed6 100644 --- a/lib/deploygate.rb +++ b/lib/deploygate.rb @@ -4,6 +4,17 @@ require "io/console" require "rbconfig" require "color_echo" +require "openssl" +require "plist" +require "find" +require "github_issue_request" +require "highline" + +# ios build +require "gym" +require "spaceship" +require "sigh" +require "xcodeproj" module DeployGate end @@ -16,9 +27,15 @@ module DeployGate require "deploygate/commands/logout" require "deploygate/commands/deploy" require "deploygate/commands/deploy/push" +require "deploygate/commands/deploy/build" require "deploygate/config" require "deploygate/session" require "deploygate/deploy" +require "deploygate/build" +require "deploygate/builds/ios" +require "deploygate/builds/ios/export" +require "deploygate/builds/ios/analyze" +require "deploygate/builds/ios/set_profile" require "deploygate/message/error" require "deploygate/message/success" require "deploygate/version" diff --git a/lib/deploygate/build.rb b/lib/deploygate/build.rb new file mode 100644 index 0000000..9368d2c --- /dev/null +++ b/lib/deploygate/build.rb @@ -0,0 +1,18 @@ +module DeployGate + class Build + class << self + + # @param [String] path + # @return [Boolean] + def ios?(path) + DeployGate::Builds::Ios.workspace?(path) || DeployGate::Builds::Ios.project?(path) || DeployGate::Builds::Ios.ios_root?(path) + end + + # @param [String] path + # @return [Boolean] + def android?(path) + false # TODO: support android build + end + end + end +end diff --git a/lib/deploygate/builds/ios.rb b/lib/deploygate/builds/ios.rb new file mode 100644 index 0000000..fca07d7 --- /dev/null +++ b/lib/deploygate/builds/ios.rb @@ -0,0 +1,81 @@ +module DeployGate + module Builds + module Ios + WORK_DIR_EXTNAME = '.xcworkspace' + PROJECT_DIR_EXTNAME = '.xcodeproj' + + class NotSupportExportMethodError < StandardError + end + + class << self + # @param [Analyze] ios_analyze + # @param [String] target_scheme + # @param [String] codesigning_identity + # @param [String] export_method + # @return [String] + def build(ios_analyze, target_scheme, codesigning_identity, export_method = Export::AD_HOC) + raise NotSupportExportMethodError, 'Not support export' unless Export::SUPPORT_EXPORT_METHOD.include?(export_method) + + values = { + :export_method => export_method, + :workspace => ios_analyze.build_workspace, + :configuration => Analyze::BUILD_CONFIGRATION, + :scheme => target_scheme, + :codesigning_identity => codesigning_identity + } + v = FastlaneCore::Configuration.create(Gym::Options.available_options, values) + absolute_ipa_path = File.expand_path(Gym::Manager.new.work(v)) + absolute_dsym_path = absolute_ipa_path.gsub(".ipa", ".app.dSYM.zip") # TODO: upload to deploygate + + absolute_ipa_path + end + + # @param [String] path + # @return [Boolean] + def workspace?(path) + WORK_DIR_EXTNAME == File.extname(path) + end + + # @param [String] path + # @return [Boolean] + def project?(path) + PROJECT_DIR_EXTNAME == File.extname(path) + end + + def ios_root?(base_path) + Find.find(base_path) do |path| + next if path == base_path + return true if workspace?(path) || project?(path) + Find.prune if FileTest.directory?(path) + end + false + end + + # @param [String] base_path + # @param [Boolean] current_only + # @return [Array] + def find_workspaces(base_path) + projects = [] + Find.find(base_path) do |path| + next if path == base_path + if File.extname(path) == WORK_DIR_EXTNAME + projects.push(path) + end + end + + projects + end + + # @param [String] path + # @return [String] + def project_root_path(path) + result = path + if workspace?(path) || project?(path) + result = project_root_path(File.dirname(path)) + end + result + end + end + end + end +end diff --git a/lib/deploygate/builds/ios/analyze.rb b/lib/deploygate/builds/ios/analyze.rb new file mode 100644 index 0000000..936fbcc --- /dev/null +++ b/lib/deploygate/builds/ios/analyze.rb @@ -0,0 +1,94 @@ +module DeployGate + module Builds + module Ios + class Analyze + attr_reader :workspaces, :scheme_workspace, :build_workspace, :scheme + + class NotLocalProvisioningProfileError < StandardError + end + + BASE_WORK_DIR_NAME = 'project.xcworkspace' + BUILD_CONFIGRATION = 'Release' + + # @param [Array] workspaces + # @return [DeployGate::Builds::Ios::Analyze] + def initialize(workspaces) + @workspaces = workspaces + @scheme_workspace = find_scheme_workspace(workspaces) + @build_workspace = find_build_workspace(workspaces) + @xcodeproj = File.dirname(@scheme_workspace) + + config = FastlaneCore::Configuration.create(Gym::Options.available_options, {:workspace => @scheme_workspace}) + project = FastlaneCore::Project.new(config) + if project.schemes.empty? + config = FastlaneCore::Configuration.create(Gym::Options.available_options, {:workspace => @build_workspace}) + project = FastlaneCore::Project.new(config) + end + project.select_scheme + @scheme = project.options[:scheme] + end + + # @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'] + identifier.gsub!(/\$\(PRODUCT_NAME:.+\)/, product_name) + + identifier + end + + private + + def find_xcschemes + shared_schemes = Dir[File.join(@xcodeproj, 'xcshareddata', 'xcschemes', '*.xcscheme')].reject do |scheme| + @scheme != File.basename(scheme, '.xcscheme') + end + user_schemes = Dir[File.join(@xcodeproj, 'xcuserdata', '*.xcuserdatad', 'xcschemes', '*.xcscheme')].reject do |scheme| + @scheme != File.basename(scheme, '.xcscheme') + end + + shared_schemes.concat(user_schemes).first + end + + # @param [Array] workspaces + # @return [String] + def find_scheme_workspace(workspaces) + return nil if workspaces.empty? + return workspaces.first if workspaces.count == 1 + + select = nil + workspaces.each do |workspace| + if BASE_WORK_DIR_NAME == File.basename(workspace) + select = workspace + end + end + + select + end + + # @param [Array] workspaces + # @return [String] + def find_build_workspace(workspaces) + return nil if workspaces.empty? + return workspaces.first if workspaces.count == 1 + + select = nil + workspaces.each do |workspace| + if BASE_WORK_DIR_NAME != File.basename(workspace) + select = workspace + end + end + + select + end + end + end + end +end diff --git a/lib/deploygate/builds/ios/export.rb b/lib/deploygate/builds/ios/export.rb new file mode 100644 index 0000000..3ee5510 --- /dev/null +++ b/lib/deploygate/builds/ios/export.rb @@ -0,0 +1,144 @@ +module DeployGate + module Builds + module Ios + class Export + AD_HOC = 'ad-hoc' + ENTERPRISE = 'enterprise' + SUPPORT_EXPORT_METHOD = [AD_HOC, ENTERPRISE] + PROFILE_EXTNAME = '.mobileprovision' + + class << self + # @param [String] bundle_identifier + # @return [Hash] + def find_local_data(bundle_identifier) + result_profiles = {} + teams = {} + profiles.each do |profile_path| + plist = analyze_profile(profile_path) + entities = plist['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) + + teams[team] = plist['TeamName'] if teams[team].nil? + result_profiles[team] = [] if result_profiles[team].nil? + result_profiles[team].push(profile_path) + end + end + end + + { + :teams => teams, + :profiles => result_profiles + } + end + + # @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) + end + + # @return [Array] + def installed_identies + available = `security find-identity -v -p codesigning` + ids = [] + available.split("\n").each do |current| + next if current.include? "REVOKED" + begin + (ids << current.match(/.*\) (.*) \".*/)[1]) + rescue + # the last line does not match + end + end + + ids + end + + # @param [Array] profiles + # @return [String] + def select_profile(profiles) + select = nil + + profiles.each do |profile| + select = profile if adhoc?(profile) && select.nil? + select = profile if inhouse?(profile) + end + select + end + + # @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 + + identity + end + + # @param [String] profile_path + # @return [String] + def method(profile_path) + adhoc?(profile_path) ? AD_HOC : ENTERPRISE + end + + # @param [String] profile_path + # @return [Boolean] + def adhoc?(profile_path) + plist = analyze_profile(profile_path) + !plist['Entitlements']['get-task-allow'] && plist['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? + end + + # @param [String] profile_path + # @return [Hash] + def analyze_profile(profile_path) + plist = nil + 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') + 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 + end + end +end diff --git a/lib/deploygate/builds/ios/set_profile.rb b/lib/deploygate/builds/ios/set_profile.rb new file mode 100644 index 0000000..cf457d1 --- /dev/null +++ b/lib/deploygate/builds/ios/set_profile.rb @@ -0,0 +1,82 @@ +module DeployGate + module Builds + module Ios + class SetProfile + attr_reader :method + + OUTPUT_PATH = '/tmp/dg/provisioning_profile/' + CERTIFICATE_OUTPUT_PATH = '/tmp/dg/certificate/' + + # @param [String] username + # @param [String] identifier + # @return [DeployGate::Builds::Ios::SetProfile] + def initialize(username, identifier) + @username = username + @identifier = identifier + Spaceship.login(username) + Spaceship.select_team + if Spaceship.client.in_house? + @method = Export::ENTERPRISE + else + @method = Export::AD_HOC + end + end + + # @return [Boolean] + def app_id_create + app_created = false + Spaceship.app.all.collect do |app| + if app.bundle_id == @identifier + app_created = true + break + end + end + unless app_created + Spaceship.app.create!(:bundle_id => @identifier, :name => "#{@identifier.split('.').join(' ')}") + return true + end + + false + end + + # @return [Array] + def create_provisioning + if @method == Export::AD_HOC + prod_certs = Spaceship.certificate.production.all + else + prod_certs = Spaceship.certificate.all.reject{|cert| cert.class != Spaceship::Portal::Certificate::InHouse} + end + + # check local install certificate + FileUtils.mkdir_p(CERTIFICATE_OUTPUT_PATH) + distribution_cert_ids = [] + prod_certs.each do |cert| + path = File.join(CERTIFICATE_OUTPUT_PATH, "#{cert.id}.cer") + raw_data = cert.download_raw + File.write(path, raw_data) + distribution_cert_ids.push(cert.id) if FastlaneCore::CertChecker.installed?(path) + 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 = { + :adhoc => @method == Export::AD_HOC ? true : false, + :app_identifier => @identifier, + :username => @username, + :output_path => OUTPUT_PATH, + :cert_id => cert_id, + :team_id => Spaceship.client.team_id + } + v = FastlaneCore::Configuration.create(Sigh::Options.available_options, values) + Sigh.config = v + provisionings.push(Sigh::Manager.start) + end + + provisionings + end + end + end + end +end diff --git a/lib/deploygate/command_builder.rb b/lib/deploygate/command_builder.rb index 85cac8c..0ab6405 100644 --- a/lib/deploygate/command_builder.rb +++ b/lib/deploygate/command_builder.rb @@ -4,6 +4,8 @@ class CommandBuilder attr_reader :arguments def run + GithubIssueRequest::Url.config('deploygate', 'deploygate-cli') + program :name, 'dg' program :version, VERSION program :description, 'You can control to DeployGate in your terminal.' @@ -12,7 +14,12 @@ def run c.syntax = 'dg init' c.description = 'project initial command' c.action do |args, options| - Commands::Init.run + begin + Commands::Init.run + rescue => e + error_handling("Commands::Init Error: #{e.class}", e.message, ['bug', 'Init']) + raise e + end end end @@ -25,7 +32,12 @@ def run c.option '--disable_notify', 'disable notify via email (iOS app only)' c.action do |args, options| options.default :message => '', :user => nil, :open => false, 'disable_notify' => false - Commands::Deploy.run(args, options) + begin + Commands::Deploy.run(args, options) + rescue => e + error_handling("Commands::Deploy Error: #{e.class}", e.message, ['bug', 'Deploy']) + raise e + end end end alias_command :'push', :deploy @@ -34,11 +46,31 @@ def run c.syntax = 'dg logout' c.description = 'logout' c.action do |args, options| - Commands::Logout.run + begin + Commands::Logout.run + rescue => e + error_handling("Commands::Logout Error: #{e.class}", e.message, ['bug', 'Logout']) + raise e + end end end run! end + + def error_handling(title, body, labels) + options = { + :title => title, + :body => body, + :labels => labels.push("v#{DeployGate::VERSION}") + } + url = GithubIssueRequest::Url.new(options).to_s + puts '' + if HighLine.agree('Do you want to report this issue on GitHub? (y/n) ') {|q| q.default = "n"} + puts "Please open github issue: #{url}" + system('open', url) if Commands::Deploy::Push.openable? + end + puts '' + end end end diff --git a/lib/deploygate/commands/deploy.rb b/lib/deploygate/commands/deploy.rb index 84f0f61..ba2f798 100644 --- a/lib/deploygate/commands/deploy.rb +++ b/lib/deploygate/commands/deploy.rb @@ -6,8 +6,18 @@ class << self # @param [Array] args # @param [Commander::Command::Options] options def run(args, options) + Init.login unless DeployGate::Session.new.login? + # push or build(android/ios) - Push.upload(args, options) + args.push(Dir.pwd) if args.empty? + + work_file_path = args.first + if File.directory?(work_file_path) + Build.run(args, options) + else + # file upload + Push.upload(args, options) + end end end end diff --git a/lib/deploygate/commands/deploy/build.rb b/lib/deploygate/commands/deploy/build.rb new file mode 100644 index 0000000..2c6df19 --- /dev/null +++ b/lib/deploygate/commands/deploy/build.rb @@ -0,0 +1,146 @@ +module DeployGate + module Commands + module Deploy + class Build + class << self + + # @param [Array] args + # @param [Hash] options + # @return [void] + def run(args, options) + # android/ios build + work_dir = args.first + + if DeployGate::Build.ios?(work_dir) + root_path = DeployGate::Builds::Ios.project_root_path(work_dir) + workspaces = DeployGate::Builds::Ios.find_workspaces(root_path) + ios(workspaces, options) + elsif DeployGate::Build.android?(work_dir) + # TODO: support android build + end + end + + # @param [Array] workspaces + # @param [Hash] options + # @return [void] + def ios(workspaces, options) + analyze = DeployGate::Builds::Ios::Analyze.new(workspaces) + target_scheme = analyze.scheme + begin + identifier = analyze.target_bundle_identifier + rescue + # not found bundle identifier + puts 'Please input bundle identifier' + puts 'Example: com.example.ios' + identifier = input_bundle_identifier + end + + data = DeployGate::Builds::Ios::Export.find_local_data(identifier) + profiles = data[:profiles] + teams = data[:teams] + + target_provisioning_profile = nil + if teams.empty? + target_provisioning_profile = create_provisioning(identifier) + elsif teams.count == 1 + target_provisioning_profile = DeployGate::Builds::Ios::Export.select_profile(profiles[teams.keys.first]) + elsif teams.count >= 2 + target_provisioning_profile = select_teams(teams, profiles) + end + method = DeployGate::Builds::Ios::Export.method(target_provisioning_profile) + codesigning_identity = DeployGate::Builds::Ios::Export.codesigning_identity(target_provisioning_profile) + + begin + ipa_path = DeployGate::Builds::Ios.build(analyze, target_scheme, codesigning_identity, method) + rescue => e + # TODO: build error handling + raise e + end + + Push.upload([ipa_path], options) + end + + def input_bundle_identifier + print 'bundle identifier: ' + identifier = STDIN.gets.chop + + if identifier == '' || identifier.nil? + puts 'You must input bundle identifier' + return input_bundle_identifier + end + + identifier + end + + # @param [Hash] teams + # @param [Hash] profiles + # @return [String] + def select_teams(teams, profiles) + result = nil + puts 'Select team:' + teams.each_with_index do |team, index| + puts "#{index + 1}. #{team[1]} (#{team[0]})" + end + print '? ' + select = STDIN.gets.chop + begin + team = teams.keys[Integer(select) - 1] + team_profiles = profiles[team].first + raise 'not select' if team_profiles.nil? + + result = DeployGate::Builds::Ios::Export.select_profile(profiles[team]) + rescue => e + puts 'Please select team number' + return select_teams(teams, profiles) + end + + result + end + + # @param [String] identifier + # @return [String] + def create_provisioning(identifier) + puts < e + DeployGate::Message::Error.print("Error: Please try login again") + raise e + end + + begin + if set_profile.app_id_create + puts "App ID #{identifier} was created" + end + rescue => e + DeployGate::Message::Error.print("Error: Failed to create App ID") + raise e + end + + begin + provisioning_profiles = set_profile.create_provisioning + rescue => e + DeployGate::Message::Error.print("Error: Failed to create provisioning profile") + raise e + end + + DeployGate::Builds::Ios::Export.select_profile(provisioning_profiles) + end + end + end + end + end +end diff --git a/lib/deploygate/version.rb b/lib/deploygate/version.rb index c0950c6..54b6a83 100644 --- a/lib/deploygate/version.rb +++ b/lib/deploygate/version.rb @@ -1,3 +1,3 @@ module DeployGate - VERSION = "0.0.1" + VERSION = "0.0.2" end diff --git a/spec/deploygate/api/v1/push_spec.rb b/spec/deploygate/api/v1/push_spec.rb index 8dbb49e..34e2389 100644 --- a/spec/deploygate/api/v1/push_spec.rb +++ b/spec/deploygate/api/v1/push_spec.rb @@ -21,7 +21,7 @@ to_return(:body => response.to_json) call_process_block = false - results = DeployGate::API::V1::Push.upload(test_file_path, target_user, token, message) {call_process_block = true} + results = DeployGate::API::V1::Push.upload(test_file_path, target_user, token, message) expect(results).to eq ({ :error => response[:error], :message => response[:because], @@ -31,7 +31,6 @@ :revision => response[:results][:revision], :web_url => DeployGate::API::V1::Base::BASE_URL + response[:results][:path] }) - expect(call_process_block).to be_truthy end it "failed" do diff --git a/spec/deploygate/build_spec.rb b/spec/deploygate/build_spec.rb new file mode 100644 index 0000000..55f3971 --- /dev/null +++ b/spec/deploygate/build_spec.rb @@ -0,0 +1,37 @@ +describe DeployGate::Build do + describe "#ios?" do + it "when select workspace" do + allow(DeployGate::Builds::Ios).to receive(:ios_root?).and_return(false) + allow(DeployGate::Builds::Ios).to receive(:workspace?).and_return(true) + allow(DeployGate::Builds::Ios).to receive(:project?).and_return(false) + + result = DeployGate::Build.ios?('path') + expect(result).to be_truthy + end + + it "when workspaces" do + allow(DeployGate::Builds::Ios).to receive(:ios_root?).and_return(false) + allow(DeployGate::Builds::Ios).to receive(:workspace?).and_return(false) + allow(DeployGate::Builds::Ios).to receive(:project?).and_return(true) + + result = DeployGate::Build.ios?('path') + expect(result).to be_truthy + end + + it "not ios" do + allow(DeployGate::Builds::Ios).to receive(:ios_root?).and_return(false) + allow(DeployGate::Builds::Ios).to receive(:workspace?).and_return(false) + allow(DeployGate::Builds::Ios).to receive(:project?).and_return(false) + + result = DeployGate::Build.ios?('path') + expect(result).to be_falsey + end + end + + describe "#android?" do + it "android not support" do + result = DeployGate::Build.android?('path') + expect(result).to be_falsey + end + end +end diff --git a/spec/deploygate/builds/ios/analyze_spec.rb b/spec/deploygate/builds/ios/analyze_spec.rb new file mode 100644 index 0000000..8bbdc94 --- /dev/null +++ b/spec/deploygate/builds/ios/analyze_spec.rb @@ -0,0 +1,3 @@ +describe DeployGate::Builds::Ios::Analyze do + # TODO: add test +end diff --git a/spec/deploygate/builds/ios/export_spec.rb b/spec/deploygate/builds/ios/export_spec.rb new file mode 100644 index 0000000..6b52d7d --- /dev/null +++ b/spec/deploygate/builds/ios/export_spec.rb @@ -0,0 +1,59 @@ +describe DeployGate::Builds::Ios::Export do + describe "#adhoc?" do + it "when adhoc plist" do + plist = { + 'ProvisionsAllDevices' => nil, + 'Entitlements' => {'get-task-allow' => false} + } + allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + expect(DeployGate::Builds::Ios::Export.adhoc?('path')).to be_truthy + end + + it "when inhouse plist" do + plist = { + 'ProvisionsAllDevices' => true, + 'Entitlements' => {'get-task-allow' => false} + } + allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + expect(DeployGate::Builds::Ios::Export.adhoc?('path')).to be_falsey + end + + it "when not distribution plist" do + plist = { + 'ProvisionsAllDevices' => nil, + 'Entitlements' => {'get-task-allow' => true} + } + allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + expect(DeployGate::Builds::Ios::Export.adhoc?('path')).to be_falsey + end + end + + describe "#inhouse?" do + it "when adhoc plist" do + plist = { + 'ProvisionsAllDevices' => nil, + 'Entitlements' => {'get-task-allow' => false} + } + allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + expect(DeployGate::Builds::Ios::Export.inhouse?('path')).to be_falsey + end + + it "when inhouse plist" do + plist = { + 'ProvisionsAllDevices' => true, + 'Entitlements' => {'get-task-allow' => false} + } + allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + expect(DeployGate::Builds::Ios::Export.inhouse?('path')).to be_truthy + end + + it "when not distribution plist" do + plist = { + 'ProvisionsAllDevices' => nil, + 'Entitlements' => {'get-task-allow' => true} + } + allow(DeployGate::Builds::Ios::Export).to receive(:analyze_profile).and_return(plist) + expect(DeployGate::Builds::Ios::Export.inhouse?('path')).to be_falsey + end + end +end diff --git a/spec/deploygate/builds/ios/set_profile_spec.rb b/spec/deploygate/builds/ios/set_profile_spec.rb new file mode 100644 index 0000000..d486cce --- /dev/null +++ b/spec/deploygate/builds/ios/set_profile_spec.rb @@ -0,0 +1,3 @@ +describe DeployGate::Builds::Ios::SetProfile do + # TODO: add test +end diff --git a/spec/deploygate/builds/ios_spec.rb b/spec/deploygate/builds/ios_spec.rb new file mode 100644 index 0000000..5838f76 --- /dev/null +++ b/spec/deploygate/builds/ios_spec.rb @@ -0,0 +1,95 @@ +describe DeployGate::Builds::Ios do + before do + class ProjectMock + def schemes + [] + end + end + class AnalyzeMock + def build_workspace + '' + end + end + end + + describe "#build" do + it "should call Gym Manager" do + call_gym_manager = false + allow(FastlaneCore::Configuration).to receive(:create) {} + allow_any_instance_of(Gym::Manager).to receive(:work) { call_gym_manager = true } + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:expand_path).and_return('path') + allow(FastlaneCore::Project).to receive(:new).and_return(ProjectMock.new) + + DeployGate::Builds::Ios.build(AnalyzeMock.new, '', '') + expect(call_gym_manager).to be_truthy + end + + it "raise not support export" do + allow(FastlaneCore::Configuration).to receive(:create) {} + allow_any_instance_of(Gym::Manager).to receive(:work) {} + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:expand_path).and_return('path') + allow(FastlaneCore::Project).to receive(:new).and_return(ProjectMock.new) + + expect { + DeployGate::Builds::Ios.build(AnalyzeMock.new, '', '', 'not support export method') + }.to raise_error DeployGate::Builds::Ios::NotSupportExportMethodError + end + end + + describe "#workspace?" do + it "pod workspace" do + allow(File).to receive(:extname).and_return('.xcworkspace') + + result = DeployGate::Builds::Ios.workspace?('path') + expect(result).to be_truthy + end + + it "xcode project" do + allow(File).to receive(:extname).and_return('.xcodeproj') + + result = DeployGate::Builds::Ios.workspace?('path') + expect(result).to be_falsey + end + end + + describe "#project?" do + it "pod workspace" do + allow(File).to receive(:extname).and_return('.xcworkspace') + + result = DeployGate::Builds::Ios.project?('path') + expect(result).to be_falsey + end + + it "xcode project" do + allow(File).to receive(:extname).and_return('.xcodeproj') + + result = DeployGate::Builds::Ios.project?('path') + expect(result).to be_truthy + end + end + + describe "#find_workspaces" do + # TODO: add test + end + + describe "#project_root_path" do + let(:root_path) {'test'} + it "when test/test.xcodeproj/project.xcworkspace" do + expect(DeployGate::Builds::Ios.project_root_path('test/test.xcodeproj/project.xcworkspace')).to eq root_path + end + + it "when test/test.xcodeproj" do + expect(DeployGate::Builds::Ios.project_root_path('test/test.xcodeproj')).to eq root_path + end + + it "when test/test.xcworkspace" do + expect(DeployGate::Builds::Ios.project_root_path('test/test.xcworkspace')).to eq root_path + end + + it "when test/" do + expect(DeployGate::Builds::Ios.project_root_path('test/')).to eq root_path + '/' + end + end +end