Class: Fastlane::SetupIos
- Defined in:
- fastlane/lib/fastlane/setup/setup_ios.rb
Overview
rubocop:disable Metrics/ClassLength
Instance Attribute Summary collapse
-
#adp_team_id ⇒ Object
Returns the value of attribute adp_team_id.
-
#app_exists_on_itc ⇒ Object
Returns the value of attribute app_exists_on_itc.
-
#app_identifier ⇒ Object
App Identifier of the current app.
-
#automatic_versioning_enabled ⇒ Object
Returns the value of attribute automatic_versioning_enabled.
-
#itc_team_id ⇒ Object
If the current setup requires a login, this is where we’ll store the team ID.
-
#project ⇒ Object
Reference to the iOS project ‘project.rb`.
-
#scheme ⇒ Object
Scheme of the Xcode project.
Attributes inherited from Setup
#appfile_content, #fastfile_content, #had_multiple_projects_to_choose_from, #is_swift_fastfile, #lane_to_mention, #platform, #preferred_setup_method, #project_path, #user
Instance Method Summary collapse
- #apple_xcode_project_versioning_enabled ⇒ Object
- #ask_for_bundle_identifier ⇒ Object
- #ask_for_credentials(itc: true, adp: false) ⇒ Object
- #create_app_online!(mode: nil) ⇒ Object
-
#find_and_setup_xcode_project(ask_for_scheme: true) ⇒ Object
Every installation setup that needs an Xcode project should call this method.
- #finish_up ⇒ Object
- #increment_build_number_if_applicable ⇒ Object
- #ios_app_store ⇒ Object
- #ios_manual ⇒ Object
- #ios_screenshots ⇒ Object
-
#ios_testflight ⇒ Object
Different iOS flows.
-
#project_prefix ⇒ Object
Returns the ‘workspace` or `project` key/value pair for gym and snapshot, but only if necessary (when there are multiple projects in the current directory) it’s a prefix, and not a suffix, as Swift cares about the order of parameters.
- #setup_ios ⇒ Object
- #show_information_about_version_bumps ⇒ Object
- #verify_app_exists_adp! ⇒ Object
- #verify_app_exists_itc! ⇒ Object
Methods inherited from Setup
#add_or_update_gemfile, #append_lane, #append_team, #appfile_template_content, #continue_with_enter, #ensure_gemfile_valid!, #explain_concepts, #fastfile_template_content, #gemfile_exists?, #gemfile_path, #initialize, #setup_gemfile!, #setup_swift_support, #show_analytics_note, start, #suggest_next_steps, #welcome_to_fastlane, #write_fastfile!
Constructor Details
This class inherits a constructor from Fastlane::Setup
Instance Attribute Details
#adp_team_id ⇒ Object
Returns the value of attribute adp_team_id.
15 16 17 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 15 def adp_team_id @adp_team_id end |
#app_exists_on_itc ⇒ Object
Returns the value of attribute app_exists_on_itc.
17 18 19 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 17 def app_exists_on_itc @app_exists_on_itc end |
#app_identifier ⇒ Object
App Identifier of the current app
8 9 10 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 8 def app_identifier @app_identifier end |
#automatic_versioning_enabled ⇒ Object
Returns the value of attribute automatic_versioning_enabled.
19 20 21 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 19 def automatic_versioning_enabled @automatic_versioning_enabled end |
#itc_team_id ⇒ Object
If the current setup requires a login, this is where we’ll store the team ID
14 15 16 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 14 def itc_team_id @itc_team_id end |
#project ⇒ Object
Reference to the iOS project ‘project.rb`
5 6 7 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 5 def project @project end |
#scheme ⇒ Object
Scheme of the Xcode project
11 12 13 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 11 def scheme @scheme end |
Instance Method Details
#apple_xcode_project_versioning_enabled ⇒ Object
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 344 def apple_xcode_project_versioning_enabled self.automatic_versioning_enabled = false paths = self.project.project_paths return false if paths.count == 0 result = Fastlane::Actions::GetBuildNumberAction.run({ project: paths.first, # most of the times, there will only be one project in there hide_error_when_versioning_disabled: true }) if result.kind_of?(String) && result.to_f > 0 self.automatic_versioning_enabled = true end return self.automatic_versioning_enabled end |
#ask_for_bundle_identifier ⇒ Object
287 288 289 290 291 292 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 287 def ask_for_bundle_identifier loop do return if self.app_identifier.to_s.length > 0 self.app_identifier = UI.input("Bundle identifier of your app: ") end end |
#ask_for_credentials(itc: true, adp: false) ⇒ Object
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 294 def ask_for_credentials(itc: true, adp: false) UI.header("Login with your Apple ID") UI.("To use App Store Connect and Apple Developer Portal features as part of fastlane,") UI.("we will ask you for your Apple ID username and password") UI.("This is necessary for certain fastlane features, for example:") UI.("") UI.("- Create and manage your provisioning profiles on the Developer Portal") UI.("- Upload and manage TestFlight and App Store builds on App Store Connect") UI.("- Manage your App Store Connect app metadata and screenshots") UI.("") UI.("Your Apple ID credentials will only be stored in your Keychain, on your local machine") UI.("For more information, check out") UI.("\thttps://github.com/fastlane/fastlane/tree/master/credentials_manager".cyan) UI.("") if self.user.to_s.length == 0 UI.important("Please enter your Apple ID developer credentials") self.user = UI.input("Apple ID Username:") end UI.("Logging in...") # Disable the warning texts and information that's not relevant during onboarding ENV["FASTLANE_HIDE_LOGIN_INFORMATION"] = 1.to_s ENV["FASTLANE_HIDE_TEAM_INFORMATION"] = 1.to_s if itc Spaceship::Tunes.login(self.user) Spaceship::Tunes.select_team self.itc_team_id = Spaceship::Tunes.client.team_id if self.is_swift_fastfile self.append_team("var itcTeam: String? { return \"#{self.itc_team_id}\" } // App Store Connect Team ID") else self.append_team("itc_team_id(\"#{self.itc_team_id}\") # App Store Connect Team ID") end end if adp Spaceship::Portal.login(self.user) Spaceship::Portal.select_team self.adp_team_id = Spaceship::Portal.client.team_id if self.is_swift_fastfile self.append_team("var teamID: String { return \"#{self.adp_team_id}\" } // Apple Developer Portal Team ID") else self.append_team("team_id(\"#{self.adp_team_id}\") # Developer Portal Team ID") end end UI.success("✅ Logging in with your Apple ID was successful") end |
#create_app_online!(mode: nil) ⇒ Object
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 457 def create_app_online!(mode: nil) # mode is either :adp or :itc require 'produce' = { username: self.user, team_id: self.adp_team_id, itc_team_id: self.itc_team_id, platform: "ios", app_identifier: self.app_identifier } if mode == :adp [:skip_itc] = true else [:skip_devcenter] = true end # The retrying system allows people to correct invalid inputs # e.g. the app's name is already taken loop do # Creating config in the loop so user will be reprompted # for app name if app name is taken or too long Produce.config = FastlaneCore::Configuration.create( Produce::Options., ) begin Produce::Manager.start_producing UI.success("✅ Successfully created app") return # success rescue => ex # show the user facing error, and inform them of what went wrong if ex.kind_of?(Spaceship::Client::BasicPreferredInfoError) || ex.kind_of?(Spaceship::Client::UnexpectedResponse) UI.error(ex.preferred_error_info) else UI.error(ex.to_s) end UI.error(ex.backtrace.join("\n")) if FastlaneCore::Globals.verbose? UI.important("It looks like something went wrong when we tried to create your app on the Apple Developer Portal") unless UI.confirm("Would you like to try again (y)? If you enter (n), fastlane will fall back to the manual setup") raise ex end end end end |
#find_and_setup_xcode_project(ask_for_scheme: true) ⇒ Object
Every installation setup that needs an Xcode project should call this method
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 265 def find_and_setup_xcode_project(ask_for_scheme: true) UI.("Parsing your local Xcode project to find the available schemes and the app identifier") config = {} # this is needed as the first method call will store information in there if self.project_path.end_with?("xcworkspace") config[:workspace] = self.project_path else config[:project] = self.project_path end FastlaneCore::Project.detect_projects(config) self.project = FastlaneCore::Project.new(config) if ask_for_scheme self.scheme = self.project.select_scheme(preferred_to_include: self.project.project_name) end self.app_identifier = self.project.default_app_identifier # These two vars need to be accessed in order to be set if self.app_identifier.to_s.length == 0 ask_for_bundle_identifier end end |
#finish_up ⇒ Object
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 406 def finish_up # iOS specific things first if self.app_identifier self.appfile_content.gsub!("# app_identifier", "app_identifier") self.appfile_content.gsub!("[[APP_IDENTIFIER]]", self.app_identifier) end if self.user self.appfile_content.gsub!("# apple_id", "apple_id") self.appfile_content.gsub!("[[APPLE_ID]]", self.user) end if !self.automatic_versioning_enabled && @method_to_use != :ios_manual self.show_information_about_version_bumps end super end |
#increment_build_number_if_applicable ⇒ Object
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 439 def increment_build_number_if_applicable return nil unless self.automatic_versioning_enabled return nil if self.project.project_paths.first.to_s.length == 0 project_path = self.project.project_paths.first # Convert the absolute path to a relative path project_path_name = Pathname.new(project_path) current_path_name = Pathname.new(File.(".")) relative_project_path = project_path_name.relative_path_from(current_path_name) if self.is_swift_fastfile return "\tincrementBuildNumber(xcodeproj: \"#{relative_project_path}\")" else return " increment_build_number(xcodeproj: \"#{relative_project_path}\")" end end |
#ios_app_store ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 109 def ios_app_store UI.header("Setting up fastlane for iOS App Store distribution") find_and_setup_xcode_project apple_xcode_project_versioning_enabled ask_for_credentials(adp: true, itc: true) verify_app_exists_adp! verify_app_exists_itc! if self.app_exists_on_itc UI.header("Manage app metadata?") UI.("Would you like to have fastlane manage your app's metadata?") UI.("If you enable this feature, fastlane will download your existing metadata and screenshots.") UI.("This way, you'll be able to edit your app's metadata in local `.txt` files.") UI.("After editing the local `.txt` files, just run fastlane and all changes will be pushed up.") UI.("If you don't want to use this feature, you can still use fastlane to upload and distribute new builds to the App Store") = UI.confirm("Would you like fastlane to manage your app's metadata?") if require 'deliver' require 'deliver/setup' = FastlaneCore::Configuration.create( Deliver::Options., { run_precheck_before_submit: false, # precheck doesn't need to run during init username: self.user, app_identifier: self.app_identifier, team_id: self.itc_team_id } ) Deliver::DetectValues.new.run!(, {}) # needed to fetch the app details Deliver::Setup.new.run(, is_swift: self.is_swift_fastfile) end end if self.is_swift_fastfile lane = ["func releaseLane() {", "desc(\"Push a new release build to the App Store\")", increment_build_number_if_applicable, "\tbuildApp(#{project_prefix}scheme: \"#{self.scheme}\")"] if lane << "\tuploadToAppStore(username: \"#{self.user}\", app: \"#{self.app_identifier}\")" else lane << "\tuploadToAppStore(username: \"#{self.user}\", app: \"#{self.app_identifier}\", skipScreenshots: true, skipMetadata: true)" end lane << "}" else lane = ["desc \"Push a new release build to the App Store\"", "lane :release do", increment_build_number_if_applicable, " build_app(#{project_prefix}scheme: \"#{self.scheme}\")"] if lane << " upload_to_app_store" else lane << " upload_to_app_store(skip_metadata: true, skip_screenshots: true)" end lane << "end" end append_lane(lane) self.lane_to_mention = "release" finish_up end |
#ios_manual ⇒ Object
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 241 def ios_manual UI.header("Setting up fastlane so you can manually configure it") if self.is_swift_fastfile append_lane(["func customLane() {", "desc(\"Description of what the lane does\")", "\t// add actions here: https://docs.fastlane.tools/actions", "}"]) self.lane_to_mention = "custom" # lane is part of the notation else append_lane(["desc \"Description of what the lane does\"", "lane :custom_lane do", " # add actions here: https://docs.fastlane.tools/actions", "end"]) self.lane_to_mention = "custom_lane" end finish_up end |
#ios_screenshots ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 173 def ios_screenshots UI.header("Setting up fastlane to automate iOS screenshots") UI.("fastlane uses UI Tests to automate generating localized screenshots of your iOS app") UI.("fastlane will now create 2 helper files that are needed to get the setup running") UI.("For more information on how this works and best practices, check out") UI.("\thttps://docs.fastlane.tools/getting-started/ios/screenshots/".cyan) continue_with_enter begin find_and_setup_xcode_project(ask_for_scheme: false) # to get the bundle identifier rescue => ex # If this fails, it's no big deal, since we really just want the bundle identifier # so instead, we'll just ask the user UI.verbose(ex.to_s) end require 'snapshot' require 'snapshot/setup' Snapshot::Setup.create( FastlaneCore::FastlaneFolder.path, is_swift_fastfile: self.is_swift_fastfile, print_instructions_on_failure: true ) UI.("If you want more details on how to setup automatic screenshots, check out") UI.("\thttps://docs.fastlane.tools/getting-started/ios/screenshots/#setting-up-snapshot".cyan) continue_with_enter available_schemes = self.project.schemes ui_testing_scheme = UI.select("Which is your UI Testing scheme? If you can't find it in this list, make sure it's marked as `Shared` in the Xcode scheme list", available_schemes) UI.header("Automatically upload to iTC?") UI.("Would you like fastlane to automatically upload all generated screenshots to App Store Connect") UI.("after generating them?") UI.("If you enable this feature you'll need to provide your App Store Connect credentials so fastlane can upload the screenshots to App Store Connect") automatic_upload = UI.confirm("Enable automatic upload of localized screenshots to App Store Connect?") if automatic_upload ask_for_credentials(adp: true, itc: true) verify_app_exists_itc! end if self.is_swift_fastfile lane = ["func screenshotsLane() {", "desc(\"Generate new localized screenshots\")", "\tcaptureScreenshots(#{project_prefix}scheme: \"#{ui_testing_scheme}\")"] if automatic_upload lane << "\tuploadToAppStore(username: \"#{self.user}\", app: \"#{self.app_identifier}\", skipBinaryUpload: true, skipMetadata: true)" end lane << "}" else lane = ["desc \"Generate new localized screenshots\"", "lane :screenshots do", " capture_screenshots(#{project_prefix}scheme: \"#{ui_testing_scheme}\")"] if automatic_upload lane << " upload_to_app_store(skip_binary_upload: true, skip_metadata: true)" end lane << "end" end append_lane(lane) self.lane_to_mention = "screenshots" finish_up end |
#ios_testflight ⇒ Object
Different iOS flows
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 80 def ios_testflight UI.header("Setting up fastlane for iOS TestFlight distribution") find_and_setup_xcode_project apple_xcode_project_versioning_enabled ask_for_credentials(adp: true, itc: true) verify_app_exists_adp! verify_app_exists_itc! if self.is_swift_fastfile lane = ["func betaLane() {", "desc(\"Push a new beta build to TestFlight\")", increment_build_number_if_applicable, "\tbuildApp(#{project_prefix}scheme: \"#{self.scheme}\")", "\tuploadToTestflight(username: \"#{self.user}\")", "}"] else lane = ["desc \"Push a new beta build to TestFlight\"", "lane :beta do", increment_build_number_if_applicable, " build_app(#{project_prefix}scheme: \"#{self.scheme}\")", " upload_to_testflight", "end"] end self.append_lane(lane) self.lane_to_mention = "beta" finish_up end |
#project_prefix ⇒ Object
Returns the ‘workspace` or `project` key/value pair for gym and snapshot, but only if necessary
(when there are multiple projects in the current directory)
it’s a prefix, and not a suffix, as Swift cares about the order of parameters
429 430 431 432 433 434 435 436 437 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 429 def project_prefix return "" unless self.had_multiple_projects_to_choose_from if self.project_path.end_with?(".xcworkspace") return "workspace: \"#{self.project_path}\", " else return "project: \"#{self.project_path}\", " end end |
#setup_ios ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 21 def setup_ios require 'spaceship' self.platform = :ios welcome_to_fastlane self.fastfile_content = fastfile_template_content self.appfile_content = appfile_template_content if preferred_setup_method self.send(preferred_setup_method) return end = { "📸 Automate screenshots" => :ios_screenshots, "👩✈️ Automate beta distribution to TestFlight" => :ios_testflight, "🚀 Automate App Store distribution" => :ios_app_store, "🛠 Manual setup - manually setup your project to automate your tasks" => :ios_manual } selected = UI.select("What would you like to use fastlane for?", .keys) @method_to_use = [selected] begin self.send(@method_to_use) rescue => ex # If it's already manual, and it has failed # we need to re-raise the exception, as something definitely is wrong raise ex if @method_to_use == :ios_manual # If we're here, that means something else failed. We now show the # error message and fallback to `:ios_manual` UI.error("--------------------") UI.error("fastlane init failed") UI.error("--------------------") UI.verbose(ex.backtrace.join("\n")) if ex.kind_of?(Spaceship::Client::BasicPreferredInfoError) || ex.kind_of?(Spaceship::Client::UnexpectedResponse) UI.error(ex.preferred_error_info) else UI.error(ex.to_s) end UI.important("Something failed while running `fastlane init`") UI.important("Tried using Apple ID with email '#{self.user}'") UI.important("You can either retry, or fallback to manual setup which will create a basic Fastfile") if UI.confirm("Would you like to fallback to a manual Fastfile?") self.ios_manual else self.send(@method_to_use) end # the second time, we're just failing, and don't use a `begin` `rescue` block any more end end |
#show_information_about_version_bumps ⇒ Object
361 362 363 364 365 366 367 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 361 def show_information_about_version_bumps UI.important("It looks like your project isn't set up to do automatic version incrementing") UI.important("To enable fastlane to handle automatic version incrementing for you, please follow this guide:") UI.("\thttps://developer.apple.com/library/content/qa/qa1827/_index.html".cyan) UI.important("Afterwards check out the fastlane docs on how to set up automatic build increments") UI.("\thttps://docs.fastlane.tools/getting-started/ios/beta-deployment/#best-practices".cyan) end |
#verify_app_exists_adp! ⇒ Object
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 369 def verify_app_exists_adp! UI.user_error!("No app identifier provided") if self.app_identifier.to_s.length == 0 UI.("Checking if the app '#{self.app_identifier}' exists in your Apple Developer Portal...") app = Spaceship::Portal::App.find(self.app_identifier) if app.nil? UI.error("It looks like the app '#{self.app_identifier}' isn't available on the #{'Apple Developer Portal'.bold.underline}") UI.error("for the team ID '#{self.adp_team_id}' on Apple ID '#{self.user}'") if UI.confirm("Do you want fastlane to create the App ID for you on the Apple Developer Portal?") create_app_online!(mode: :adp) else UI.important("Alright, we won't create the app for you. Be aware, the build is probably going to fail when you try it") end else UI.success("✅ Your app '#{self.app_identifier}' is available in your Apple Developer Portal") end end |
#verify_app_exists_itc! ⇒ Object
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 |
# File 'fastlane/lib/fastlane/setup/setup_ios.rb', line 387 def verify_app_exists_itc! UI.user_error!("No app identifier provided") if self.app_identifier.to_s.length == 0 UI.("Checking if the app '#{self.app_identifier}' exists on App Store Connect...") app = Spaceship::Tunes::Application.find(self.app_identifier) if app.nil? UI.error("Looks like the app '#{self.app_identifier}' isn't available on #{'App Store Connect'.bold.underline}") UI.error("for the team ID '#{self.itc_team_id}' on Apple ID '#{self.user}'") if UI.confirm("Would you like fastlane to create the App on App Store Connect for you?") create_app_online!(mode: :itc) self.app_exists_on_itc = true else UI.important("Alright, we won't create the app for you. Be aware, the build is probably going to fail when you try it") end else UI.success("✅ Your app '#{self.app_identifier}' is available on App Store Connect") self.app_exists_on_itc = true end end |