iOS Sourcery with Flutter build

880 views Asked by At

Im trying to build my flutter app for iOS it has a google maps key that I want to protect and not check in to source control it needs to be buildable from azure, to achieve this I'm storing my maps key as a secret variable in azure and as a system environment variable locally, I'm using Sourcery https://github.com/krzysztofzablocki/Sourcery to generate a class for me that contains this key, it all works but only the second time I build, the first build always fails.

So I'm building using this command

flutter build ios --flavor dev --verbose

Which the first run will give me the error

 error: Build input file cannot be found:
           '/Users/martin/xxx/xxx/xxx/ios/Runner/Credentials.generated.swift' (in target

'Runner'

Then issuing the same command again

** BUILD SUCCEEDED **

this is my run script its called before compile sources and after the flutter run script

run script

this calls my script which calls another script to export the map api key and runs sourcery command using a .yml file as its config heres the script, (it also does some logging)

#!/bin/bash

echo "Generate Credentials Code"
CREDENTIALS_DIR="$SRCROOT/credentials"

# Set credentials if local script for adding environment variables exist
if [ -f "$CREDENTIALS_DIR/add_credentials_to_env.sh" ]; then
  echo "Add credentials to environement"
  source "$CREDENTIALS_DIR/add_credentials_to_env.sh"
  echo "finished running add_credentials_to_env.sh"
fi

echo "RUN SOURCERY"

$SRCROOT/Pods/Sourcery/bin/sourcery --config "$SRCROOT/config.yml" 
echo "FINISHED RUNNING SOURCERY"

for file in "$SRCROOT/Runner"/*; do
  echo "$file"
done

and here is my config file

sources:
  - .
project:
  file: Runner.xcodeproj
  target:
    name: Runner
    module: Runner
templates:
  - credentials/Credentials.stencil
output:
  path: ./Runner/
  link:
    project: Runner.xcodeproj
    target: Runner

args:
  mapsApiKey: ${MAPS_API_KEY_IOS}

this generates my class correctly on the first build and seems to be added correctly to the target (edited out my key) but the app will only compile if I run the build command again.

// Generated using Sourcery 1.4.2 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
public struct Credentials {
    let mapsApiKey: String
}
public let credentials = Credentials(mapsApiKey: 
"xxxxxxxxxxMY_KEYxxxxxxxxxxx")

Any ideas?

xcode 12.5 m1 macbook pro, swift 5

2

There are 2 answers

2
Andrew On BEST ANSWER

Looks like you generate the file too late. I'll suggest move your script to Aggregate and add it as a dependency to your target

  1. Add Aggregate

List item

enter image description here

  1. Move your script to 'Run script' section

enter image description here

  1. Add 'PreBuildScriptsRunner' as a dependency to your application target, make sure 'Dependencies' section on top of all other sections

enter image description here

1
Ben Butterworth On

Manually setting environment variables is an annoying thing developers would have to do on their own machines, and there are nicer/ more common ways of setting up private keys. After a few years of using environment variables/ bash, it still causes issues which are not easily detectable. You may want to automate/ document it, but then you have to consider developers using zsh, fish vs. bash? Also, I try to avoid using Xcode build phases where possible.

Solution? (This is what I have)

Why don't you use your CI (Azure pipeline?, I use Github workflows) to write a Xcode build configuration file (not a Swift file). The sensitive keys could be in a file Secrets.xcconfig, which is added to your Xcode as a build configuration. Then, in your Info.plist of your application, and your code can load them.

Create a file, Secrets.xcconfig:

SECRET_API_KEY = 12312rfiwhvde.wvascafsf.df325

Add it to your Xcode project, and then to the project's build configuration:

Xcode project info tab > configuration drop down

Add Secrets.xcconfig to your .gitignore

Make sure to git ignore the file before committing it to the repo. You can also keep an Example.Secrets.xcconfig which users can use. In the readme, tell users to run cp Example.Secrets.xcconfig Secrets.xcconfig and then to update values in it. Now you can clearly see what keys the application is using (its clearly in the directory). As a bonus, you can add this file the Xcode project, so that when the file is missing, it shows up in red (indicating to the user they really should acquire this file somehow):

Red font for Secrets.xcconfig

In Info.plist, reference the variable:

<dict>
    <key>SECRET_API_KEY</key>
    <string>$(SECRET_API_KEY)</string>
</dict>

In your code, load the variable that was stored in Info.plist:

let key = Environment.infoDictionary["SECRET_API_KEY"] as? String

In your CI/ Azure pipeline:

Run echo "SECRET_API_KEY = $SECRET_API_KEY_SAVED_IN_CONTINUOUS_INTEGRATION" >> Secrets.xcconfig

Then you can just .gitignore the file instead of setting environment variables. When you work with other developers, you just give them this file, and nothing else needs to be done to build locally.


So I have answered your question not by solving your direct problem, but giving you a more common/ canonical way of solving this problem that many developers have faced before.