How to configure TravisCI free for multi project repos
Travis CI is a great hosted continuous integration service for Github repositories. Especially for fast tests. While I was hooking Firebase iOS quickstarts with Travis CI, I had to implement some hacks.
--
Because we,
- have multiple projects inside 1 repo,
- use CocoaPods 1.0 (default is 0.5),
- include Google plist file into each project at test,
- update info.plist of each project at test,
- parallelize builds across VMs,
- cache bundler and dependencies (pods).
The Build Lifecycle
A build on Travis CI is made up of two steps:
install
: install any dependencies requiredscript
: run the build script
You can run custom commands before the installation step (before_install
), and before (before_script
) or after (after_script
) the script step.
In a
before_install
step, you can install additional dependencies required by your project such as Ubuntu packages or custom services. So here I,
- install CocoaPods 1.0 (default is 0.5)
- get into each example folder and install pods
- copy in a mock Google plist file
- update Google plist and info.plist with sed
- add Google plist file into each target. (using ruby and xcodeproj)
- gem uninstall cocoapods -a
— gem install cocoapods -v ‘1.1.1’
— gem install xcpretty
— cd $SAMPLE
— pod install — repo-update
— cp ../mock-GoogleService-Info.plist ./GoogleService-Info.plist
— sed -i ‘’ ‘/<key>BUNDLE_ID</{n;s/id/com.google.firebase.quickstart.’$SAMPLE’Example/;}’ GoogleService-Info.plist
— sed -i ‘’ ‘s/YOUR_REVERSED_CLIENT_ID/com.googleusercontent.apps.123456789000-hjugbg6ud799v4c49dim8ce2usclthar/’ “$SAMPLE”Example/Info.plist
— gem install xcodeproj
— ruby ../info_script.rb $SAMPLE
— cd -
Then in
script
, I run the xcodebuild
before_install
and script
are merged in execution and executed for each
entry in the build matrix. Your environment variables you set withenv:
is accessible to both.
Parallelizing your builds across virtual machines
To speed up a test suite, you can break it up into several parts using
Travis CI’s build matrix feature.
Say you want to split up your unit tests and your integration tests into two
different build jobs. They’ll run in parallel and fully utilize the available
build capacity for your account.
Here’s an example on how to utilize this feature in your .travis.yml:
env:
— TEST_SUITE=units
— TEST_SUITE=integration
Here I use the “SAMPLE” variable for each sample name. That helps me to go into each folder, set their bundle ids respectively, and find out project and scheme names to build.
env: SAMPLE=AdMob
Caching directories (Bundler, dependencies)
With caches, Travis CI can persist directories between builds. This is
especially useful for dependencies that need to be downloaded and/or compiled from source.
Build phases
Travis CI attempts to upload cache after script, but before either after_success
or after_failure
is run. Note that the failure to upload the cache does not mark the job a failure.
Bundler
On Ruby and Objective-C projects, installing dependencies via Bundler can make up a large portion of the build duration. Caching the bundle between builds drastically reduces the time a build takes to run.
The logic for fetching and storing the cache is described below.
Enabling Bundler caching
Bundler caching is automatically enabled for Ruby projects that include a
Gemfile.lock. (Bundler caching is not yet enabled automatically) You can explicitly enable Bundler caching in your .travis.yml:
language: ruby
cache: bundler
Whenever you update your bundle, Travis CI will also update the cache.
CocoaPods
On Objective-C projects, installing dependencies via CocoaPods can take up a good portion of your build. Caching the compiled Pods between builds helps reduce this time.
If you want to enable both Bundler caching and CocoaPods caching, you can list them both:
language: objective-c
cache:
- bundler
- cocoapods
Determining the Podfile path
By default, Travis CI will assume that your Podfile is in the root of the
repository. If this is not the case, you can specify where the Podfile is
like this:
language: objective-c
podfile: path/to/Podfile
To be able to cache the pods:
- I had to specify podfile for each sample. That’s why I had to specify it inside the build-matrix.
- I tried usepodfile: $SAMPLE/Podfile
but it wouldn’t pick up the
environment variable, so I had to explicitly define it in the build-matrix.
- Instead of using just theenv
, I created the first sample(Admob) by
default and added all entries into build matrix using thematrix: include:
.
cache:
- bundler
- cocoapodspodfile: admob/Podfile
env: SAMPLE=AdMobmatrix:
include:
- podfile: analytics/Podfile
env: SAMPLE=Analytics
- podfile: authentication/Podfile
env: SAMPLE=Authentication
Results:
- I set up right bundle-id and run pods for each sample.
- I cache right bundler and pods for each sample.
- All these tests run parallel, fast, well under travis time limit.
- With caching & build matrix, my tests finish under 10 minutes instead of 1 hour 40 minutes as before.
PS: Recently, I had an issue with my tests failing with error 65, and the reason is that simulator not being ready in time. So I wrote a script to retry them and it worked like magic!
set -o pipefail && xcodebuild \
-workspace ${SAMPLE}/${SAMPLE}Example.xcworkspace \
-scheme ${SAMPLE}Example \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 7' \
build \
test \
ONLY_ACTIVE_ARCH=YES \
CODE_SIGNING_REQUIRED=NO\
| xcpretty
RESULT=$?
if [ $RESULT == 65 ]; then
echo "xcodebuild exited with 65, retrying"
set -o pipefail && xcodebuild \
...
...else
exit $RESULT
fi
Further read: “How to run TravisCI locally on Docker”.