相关文章推荐
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

We have these environment variables within the Xcode Scheme

Which works well locally with this code

let webHost = ProcessInfo.processInfo.environment["HOST_URL"]!
let apiHost = ProcessInfo.processInfo.environment["API_URL"]!
let beamsKey = ProcessInfo.processInfo.environment["BEAMS_KEY"]!
let mixpanelKey = ProcessInfo.processInfo.environment["MIXPANEL_KEY"]!

However, when deploying using Xcode Cloud with the same environment variables.

It succeeds in building, but the app crashes with this log.

What is the right way to read these environment variables when using Xcode Cloud?

Hi @SamuelHassid, not yet, but an upvote might help for more visibility on this post. I found out though that you need to create a custom CI Script that reads the environment variables and writes it to an Info.plist, which adds more complexity than what it's worth for me (at least for now). – ygee Aug 21, 2022 at 2:10 I have the same question. Seems like the only option is to use the value in combination with a script that will write the value into the info.plist before build. Once its "burned" into the info.plist you can access it using Bundle.main.object(forInfoDictionaryKey: "...") – ra9r Oct 10, 2022 at 6:05

I had a similar issue, mostly i wanted to add an api-key in the project without this exist in the source code. So I had to create a ci_pre_xcodebuild.sh file

#!/bin/sh
echo "Stage: PRE-Xcode Build is activated .... "
# for future reference
# https://developer.apple.com/documentation/xcode/environment-variable-reference
cd ../ProjectName/
plutil -replace API_KEY_DEBUG -string $API_KEY_DEBUG Info.plist
plutil -replace API_KEY_RELEASE -string $API_KEY_RELEASE Info.plist
plutil -p Info.plist
echo "Stage: PRE-Xcode Build is DONE .... "
exit 0

and in the code we have

let key = config.preferences.debug ? "API_KEY_DEBUG" : "API_KEY_RELEASE"
guard let apiKey = Bundle.main.infoDictionary?[key] as? String
                I had some issues with this when it was in ci_pre_xcodebuild.sh file but I moved it to ci_post_clone.sh and it works fine there. Also according to apple documentation: "ci_post_clone.sh - The post-clone script runs after Xcode Cloud clones your Git repository. You might use a post-clone script to install an additional tool, or to add a new entry to a property list."
– Leszek Szary
                Sep 29 at 9:07

So, this was an absolute headache but I finally figured out a satisfactory way to access and use these variables in code.

My solution uses:

  • A (gitignored) JSON file to store the variables locally
  • Xcode Cloud to send the variables in the CI
  • A ci_pre_xcodebuild.sh file to write the environment variables in the JSON
  • A Swift file that allows you to conveniently access the secrets.
  • Step 1: Basic JSON file

  • In your .gitignore file, add a new entry for the JSON file and its path
  • Create a JSON file through Xcode
  • Add your keys to this JSON file.
  • Secrets.json

    (at: YourProject/SupportingFiles/secrets.json)

    "STRIPE_KEY": "", "GOOGLE_MAPS_KEY": "", "GOOGLE_PLACES_KEY": "", "BASE_URL": "https://dev.api.example.fr"

    Step 2: Write the variables in Xcode Cloud

    In this screenshot you can see that I've duplicated the keys for different environments. I didn't expand on this for the sake of brevity, but you can definitely have different secrets JSON files for different Xcode Scheme configurations.

    Step 3: Add a ci_pre_xcodebuild.sh file

    Important: the name of the files and their position matter.

    The goal here is to add a script that the CI (Xcode Cloud) will execute each time it builds. In this script, we're going to create and fill our JSON.

  • Add a new group at the root of your project named "ci_scripts"
  • In this group, add a new file called ci_pre_xcodebuild.sh

  • Write the following:

    #!/bin/sh
    echo "Stage: PRE-Xcode Build is activated .... "
    # Move to the place where the scripts are located.
    # This is important because the position of the subsequently mentioned files depend of this origin.
    cd $CI_WORKSPACE/ci_scripts || exit 1
    # Write a JSON File containing all the environment variables and secrets.
    printf "{\"STRIPE_KEY\":\"%s\",\"GOOGLE_MAPS_KEY\":\"%s\",\"GOOGLE_PLACES_KEY\":\"%s\",\"BASE_URL\":\"%s\"}" "$STRIPE_KEY" "$GOOGLE_MAPS_KEY" "$GOOGLE_PLACES_KEY" "$BASE_URL" >> ../Dream\ Energy/SupportingFiles/Secrets.json
    echo "Wrote Secrets.json file."
    echo "Stage: PRE-Xcode Build is DONE .... "
    exit 0
    

    Of course, you need to change this text depending on your keys and the location of the file. I added a few keys as an example.

  • In your Terminal, navigate to the new file's folder and run this command: chmod +x ci_pre_xcodebuild.sh. This fixes a warning in Xcode Cloud.
  • Step 4: [BONUS] A simple Swift file to access the environment variables

    import Foundation
    struct Secrets {
        private static func secrets() -> [String: Any] {
            let fileName = "Secrets"
            let path = Bundle.main.path(forResource: fileName, ofType: "json")!
            let data = try! Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
            return try! JSONSerialization.jsonObject(with: data) as! [String: Any]
        static var baseURL: String {
            return secrets()["BASE_URL"] as! String
        static var stripeKey: String {
            return secrets()["STRIPE_KEY"] as! String
                    Thanks for this. But OK this stops secrets from being exposed in the code, and means they don't have to live in the repository, which is good. But in terms of writing it to a JSON file in the project directory, aren't you still exposing it to someone who is able to reverse-engineer the app and read this file?
    – jakedunc
                    Oct 3 at 22:06
                    In-app secrets are impossible to be secured. You can add a layer of obfuscation if you want, but that just will just slow down someone who would want to get the secrets. Please read this interesting article that talks about it better than I ever will: nshipster.com/secrets
    – Clément Cardonnel
                    Oct 4 at 8:11
    

    These variables are available and valid while the temporary environment is active to build the app only, not when the app is running on the device.

    The environment variables can, however, be “captured” during the build process using shell scripts (see the Xcode "Build Phases" under the target settings or the Xcode Cloud custom build scripts).

    Another good solution is to use some code generation tool like Arkana. This tool creates obfuscated code to make the variables available at the runtime eventually.

    Again the tool or shell script must run in the Xcode Cloud environment. The steps to do this are out of the scope of this response.

    Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center. – Community Sep 8, 2022 at 6:32

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.

  •  
    推荐文章