Changing lanes with Travis CI + Fastlane

Jun 18, 2018

|

Fastlane is one of those iOS development gems, that sometimes not everyone talks about because it’s so helpful. Luckily I’ll tell you all about it with a lengthy demo, code samples, and my GitHub repository with a working Fastlane/Travis CI project.

Fastlane is a Google project that I’ve always found really useful. After doing a whole day’s research, I was able to successfully use Fastlane. This tool is still very useful in my opinion when it comes to actually splitting tests. Some things I saw people forget, is the simple start. So in that case let’s press continue, and get started!

If you want to checkout my repo before you start, feel free! https://github.com/Montana/travis-fastlane

Getting in motion

When using Fastlanes you should really remember if you’re going to pass parameters from the command line to your lane, use the following syntax:

fastlane [lane] key:value key2:value2
fastlane deploy submit:false build_number:24

Passing values between lanes

In theory, you can have as many lanes as you want, so you can invoke variant lanes. Example being:

before_each do |lane, options|
  # lane number (Montana likes # 3) 
end

Now within lanes, you can INVOKE other lanes for various other reasons, in other words, each lane can have different functions essentially:

error do |lane, exception, options|
  if options[:debug]
    puts "This is a cool lane because you're in it with Montana"
  end
end

Switching lanes

If you’ve used Fastlane at length, you’ll know switching lanes is usually called lane hopping. You can lane hop feasibly, without using too much CPU:

lane :deploy do |options|
  # lane 3 
  build(release: true) # don't forget this part! - Montana
  Montana Mendy
  # invoking lane 4 with a conditional 
end

Querying lanes

Just like anything you query, you can retrieve the return value in this case it’s through a lane callback. In Ruby, the last line of the lane definition is the return value. Here is an example:

lane :deploy do |options|
  value = calculate(value: 3)
  puts value # => 5
end

lane :calculate do |options|
  # ...
  2 + options[:value] # This is always the return value line. - Montana 
end

Executing lanes early

You in theory never want to reach the end of a lane, and if you do – you’re not going to be executing much longer:

lane :build do |options|
  if cached_build_available?
    UI.important 'Montana cached this Lane!'
    next # Montana is skipping doing the rest of this lane
  end
  match
  gym
end

private_lane :cached_build_available? do |options|
  # ...
  true
end

So while executing lanes – there are conditionals you can use, such as next is used during a lane and switchcontrol returns to the previous lane that was executing, so lanes in theory can revert back and forth.

lane :first_lane do |options|
  puts "If you run: `fastlane first_lane`"
  puts "You'll see this!"
  second_lane
  puts "Montana is in this lane!"
end

private_lane :second_lane do |options|
  next
  puts "Hidden!"
end

When you stop executing a lane early with next (lane switching), any after_each and after_all blocks you have will still trigger as usual :+1:. This would include being called (via a lane callback) before each lane you’ve switched to.

before_each do |lane, options|
  # ...
end

So this is a good reminder from myself, that when doing after_each blocks are called (again, by the lane callback) after any lane is called. This would include being called after each lane you’ve switched to. Just like after_allafter_each is not called if an error occurs. The error block should be used in this case.

Feasibility of lanes

With all these scenarios, before_each and after_each would be called 4 times, by the lane callback. Remember before the deploy lane, before the switch to archive, sign, and upload, and after each of these lanes as well, a lot of users forget.

Lane context

Now Fastlane has different actions that can communicate with each other using a shared hash, in other words, communicating with other lanes. You can access this in your (lanes, actions, plugins etc.):

lane_context[SharedValues::BUILD_NUMBER]                # Generated by `increment_build_number`
lane_context[SharedValues::VERSION_NUMBER]              # Generated by `increment_version_number`
lane_context[SharedValues::SNAPSHOT_SCREENSHOTS_PATH]   # Generated by _snapshot_
lane_context[SharedValues::PRODUCE_APPLE_ID]            # The Apple ID of the newly created app
lane_context[SharedValues::IPA_OUTPUT_PATH]             # Generated by _gym_
lane_context[SharedValues::DSYM_OUTPUT_PATH]            # Generated by _gym_
lane_context[SharedValues::SIGH_PROFILE_PATH]           # Generated by _sigh_
lane_context[SharedValues::SIGH_UDID]                   # The UDID of the generated provisioning profile
lane_context[SharedValues::HOCKEY_DOWNLOAD_LINK]        # Generated by `hockey`
lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]      # Generated by `gradle`
lane_context[SharedValues::GRADLE_ALL_APK_OUTPUT_PATHS] # Generated by `gradle`
lane_context[SharedValues::GRADLE_FLAVOR]               # Generated by `gradle`
lane_context[SharedValues::GRADLE_BUILD_TYPE]           # Generated by `gradle`

To feasibly get information about available lane variables, available lanes to switch on, run fastlane action [action_name] or look at the generated table in the action documentation.

Lane properties

It can be useful to dynamically access some properties of the current lane. These are available in the lane_context as well:

lane_context[SharedValues::PLATFORM_NAME]        # Platform name, e.g. `:ios`, `:android` or empty (for root level lanes)
lane_context[SharedValues::LANE_NAME]            # The name of the current lane preceded by the platform name (stays the same when switching lanes)
lane_context[SharedValues::DEFAULT_PLATFORM]     # Default platform

They are also available as environment variables:

ENV["FASTLANE_PLATFORM_NAME"]
ENV["FASTLANE_LANE_NAME"]

Private lanes

Sometimes you might have a lane that is used from different lanes, or a lane from a hopped lane:

lane :production do
  # ...
  build(release: true)
  appstore # Deploy to the Montana's brain
  # ...
end

->

lane :beta do
  # ...
  build(release: false)
  crashlytics # Distribute to testers
  # ...
end

->

lane :build do |options|
  # ...
  Montana likes beer
  # ...
end
It probably doesn't make sense to execute the build lane directly using fastlane build. You can hide this lane using

->

private_lane :build do |options|
  # ...
end

This will hide the lane from:

fastlane lanes
fastlane list
fastlane docs

And also, you can’t call the private lane using fastlane build. The resulting private lane can only be called from another lane using the lane switching technology, or the lane callback.

Control configuration by lane and by platform

In general, configuration files take only the first value given for a particular configuration item. That means that for an Appfile like the following:

app_identifier "com.used.id"
app_identifier "com.ignored.id"

So, the app_identfier will be “com.used.id” and the second value will be ignored. The for_lane and for_platform configuration blocks provide a limited exception to this rule.

All configuration files (Appfile, Matchfile, Screengrabfile, etc.) can use for_lane and for_platform blocks to control (and override) configuration values for those circumstances.

So for_lane blocks will be called when the name of lane invoked on the command line matches the one specified by the block. So, given a Screengrabfile like:

For the locales (language lane): locales ['en-US', 'fr-FR', 'ja-JP']

for_lane :screenshots_english_only do
  locales ['en-US']
end
for_lane :screenshots_french_only do
  locales ['fr-FR']
end

So as you can see, the locales will have the values [‘en-US’, ‘fr-FR’, ‘ja-JP’] by default, but will only have one value when running the fastlane screenshots_english_only or fastlane screenshots_french_only.

Then, for_platform gives you similar control based on the platform for which you have invoked fastlane. So, for an Appfile configured like:

app_identifier "com.default.id"

for_lane :enterprise do
  app_identifier "com.forlane.enterprise"
end
for_platform :mac do
  app_identifier "com.forplatform.mac"

  for_lane :release do
    app_identifier "com.forplatform.mac.forlane.release"
  end
end

So now you can expect the app_identifier to equal “com.forplatform.mac.forlane.release” when invoking Fastlane mac release.

Is it plausible to change the fastlane execution folder while inside a lane?

Yes, you can do this. The way I’ve classically done this is make two fastlane lanes, one for the old location, one for the new, so then your new script looks somethng like this:

cd old-location
fastlane old_lane
cp -r old-location new-location
cd new-location
fastlane new_lane

Setting fastlane up in iOS for different export methods and provision profiles?

So my guess is that anyway your project is scoped you have more than 1 bundle identifier to the different apps in question.

Remember with Fastlane you can setup a new target and just create the same lane with a different scheme for that target (or build a function and send the correct scheme as a parameter). You will also have to create a different bundle id.

If you don’t wish to create a different target and you are you using Automatic signing on your project, (some SSO), you will have to change it to manual and specify the provisioning profile there.

Please make sure there’s nothing that could conflict with Travis or Fastlane, so it should look like something like this:

Prod

  build_app(
  workspace: "XXXX.xcworkspace",
  scheme: "XXXXX",
  ......
  export_options: {
    method: "app-store",
    signingStyle: 'manual',
    provisioningProfiles: {
      "bundle id": "Prod profile full name",
    }
  })

Ad-hoc

 build_app(
  workspace: "XXXX.xcworkspace",
  scheme: "XXXXX",
  ......
  export_options: {
    method: "ad-hoc",
    signingStyle: 'manual',
    provisioningProfiles: {
      "bundle id": "Montana's project",
    }
  })

Can fastlane skip before_all or after_all in certain lanes in Travis?

You certainly can with Travis and Fastlane. One way to accomplish this with certain lanes would be the following:

before_all do |lane|
  if lane == :test
    puts "Do something for test"
  else
    puts "Montana"
  end
end

Additionally:

lanes = [:test, :foo, :bar]
lanes.include?(:test) # => true
lanes.include?(:baz) # => false

So the final output would look like this:

before_all do |lane|
  lanes_to_say_foo = [:test, :build, :other]
  if lanes_to_say_foo.include?(lane)
    puts "Montana"
  end
end

Happy building! Any other questions please feel free to contact [email protected], and I’ll be as quick as I can in the Fastlane to answer your question.

TravisCI Mascot

Questions or feedback?

Let us know what you’re thinking by emailing [email protected]!

TravisCI Mascot
Tessa

You might enjoy reading

Want updates on new posts?

Sign up for our newsletter, we’ll send you coffee tips as well!