Codemagic not working for custom run script in iOS? - ios

I am trying to build a flutter app on Codemagic. The app uses multiple firebase project so I have configured it accordingly. I am able to run all flavors both for android and iOS locally. The android build is success on Codemagic as well but the iOS build fails.
In the below screenshot you can see after reaching the Firebase setup run script file the iOS build fails
Code for my Firebase Setup file is
environment="default"
# Regex to extract the scheme name from the Build Configuration
# We have named our Build Configurations as Debug-dev, Debug-prod etc.
# Here, dev and prod are the scheme names. This kind of naming is required by Flutter for flavors to work.
# We are using the $CONFIGURATION variable available in the XCode build environment to extract
# the environment (or flavor)
# For eg.
# If CONFIGURATION="Debug-prod", then environment will get set to "prod".
if [[ $CONFIGURATION =~ -([^-]*)$ ]]; then
environment=${BASH_REMATCH[1]}
fi
echo $environment
# Name and path of the resource we're copying
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
GOOGLESERVICE_INFO_FILE=${PROJECT_DIR}/config/${environment}/${GOOGLESERVICE_INFO_PLIST}
# Make sure GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_FILE}"
if [ ! -f $GOOGLESERVICE_INFO_FILE ]
then
echo "No GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Get a reference to the destination location for the GoogleService-Info.plist
# This is the default location where Firebase init code expects to find GoogleServices-Info.plist file
PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Will copy ${GOOGLESERVICE_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"
# Copy over the prod GoogleService-Info.plist for Release builds
cp "${GOOGLESERVICE_INFO_FILE}" "${PLIST_DESTINATION}"
I am following https://medium.com/#animeshjain/build-flavors-in-flutter-android-and-ios-with-different-firebase-projects-per-flavor-27c5c5dac10b article for setup.
Here is another approach which I took as recommended by the Codemagic team
Instead of creating flavors,i uploaded my firebase files to codemagic as environment variables. Everything works fine for android but the iOS build fails saying Could not get GOOGLE_APP_ID. I know many people have asked question regarding this on stack but none of them works for me as they are not related to code magic and instead dragging the file to Xcode manually.
I also did an SSH to check if the firebase file gets added to iOS folder and it does get added with the help of prebuild scripts but iOS build still fails.

I used the same Medium article for setting up flavors (prod and dev) on my local machine.
I followed the Codemagic tutorial for setting up environment variables for the Google Service files, and I kept running into the same Could not get GOOGLE_APP_ID error.
The key insight for me was to replicate my local environment in Codemagic as closely as possible and leverage the Firebase setup script in Xcode during Codemagic builds.
In my case, it meant recreating the exact same folder and file structure for the Runner/Firebase/prod/GoogleService-Info.plist so that the Firebase Setup script in Build Phases could run without any issues.
First, I changed the pre-build script in Codemagic to ensure that the GoogleService-Info.plist could be found by the Firebase Setup script:
#!/bin/bash
set -e # exit on first failed commandset
rm -rf $FCI_BUILD_DIR/iOS/Runner/GoogleService-Info.plist
mkdir -p “${FCI_BUILD_DIR}/iOS/Runner/Firebase/${FCI_FLUTTER_SCHEME}”
echo $IOS_FIREBASE_SECRET | base64 —decode > $FCI_BUILD_DIR/iOS/Runner/Firebase/prod/GoogleService-Info.plist
Second, Xcode must be aware of these files. A file present in directory does not mean it is on Xcode. Add the Firebase/ directory into Xcode by File -> Add files to “Runner..” and make sure to uncheck ‘Copy if required’.
If you are already building locally without any issues, you might be able to skip this step.
Here is how my folders appear in Xcode
Then run a build on Codemagic with FCI_FLUTTER_SCHEME=prod.
Lastly, reading through xcodebuild.log in the Codemagic build Artifacts might give you more details.

Related

Xcode build environment variable for scheme that is running

I've been trying to find a way to access the scheme name from a Run Script build phase. I'm writing a CLI command that updates a user's Xcode project so I don't have the luxury of tinkering with the Xcode user interface. I have been using Cocoapod's Xcodeproj dependency to update the build settings.
The first port of call is Xcode's build settings environment variables which do not contain the scheme. See here: https://developer.apple.com/documentation/xcode/build-settings-reference
I thought I would be able to do it programmatically via Xcodeproj dependency, but it appears the environment variables it sets are for the run-time application, not the build settings environment variables. See here: https://www.rubydoc.info/gems/xcodeproj/Xcodeproj/XCScheme/EnvironmentVariables
Apple's documentation on schemes makes no suggestion for this predicament:
https://developer.apple.com/documentation/xcode/customizing-the-build-schemes-for-a-project
QUESTION:
I was wondering if anyone had any suggestions for obtaining the scheme name that is running from a Run Script build phase? Some setting I could apply programmatically using Xcodeproj or another tool to configure the project that could pull the scheme name as a value at build time?
I don't want to hardcode the value into the script as this would require generating multiple scripts in the build phases for each scheme.
A scheme isn't really a "thing"; it is merely a unification of several other aspects of the build operation. The particular aspect you probably want is the configuration, which is available as the CONFIGURATION build environment variable. In a bash script, for example, you can include a line such as
if [ $CONFIGURATION = "MyWonderfulConfiguration" ]; then
You can create configurations in the Project editor, and you can tie each scheme to its own configuration in the Scheme editor.
Configuration:
Scheme:
Environment variables:

Failed to build iOS app on Codemagic when I have two different Firebase project

I use codemagic for my Flutter app. I have no Issue when building Android File. but it always failed whenever I build iOS.
I have two different Firebase project, one project is used for production environment and the other one is for development like this
so I will have two different GoogleService-Info.plist. I have two different schemes (Flavors) called production and development. I save the GoogleService-Info.plist file inside the config folder like this on my Xcode
I have pre-build script on my Xcode, that is used to decide which GoogleService-Info.plist that should be used based on the scheme/flavor used (Production or development) like this:
here is the script from my Xcode:
environment="default"
# Regex to extract the scheme name from the Build Configuration
# We have named our Build Configurations as Debug-dev, Debug-prod etc.
# Here, dev and prod are the scheme names. This kind of naming is required by Flutter for flavors to work.
# We are using the $CONFIGURATION variable available in the XCode build environment to extract
# the environment (or flavor)
# For eg.
# If CONFIGURATION="Debug-prod", then environment will get set to "prod".
if [[ $CONFIGURATION =~ -([^-]*)$ ]]; then
environment=${BASH_REMATCH[1]}
fi
echo $environment
# Name and path of the resource we're copying
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
GOOGLESERVICE_INFO_FILE=${PROJECT_DIR}/config/${environment}/${GOOGLESERVICE_INFO_PLIST}
# Make sure GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_FILE}"
if [ ! -f $GOOGLESERVICE_INFO_FILE ]
then
echo "No GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Get a reference to the destination location for the GoogleService-Info.plist
# This is the default location where Firebase init code expects to find GoogleServices-Info.plist file
PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Will copy ${GOOGLESERVICE_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"
# Copy over the prod GoogleService-Info.plist for Release builds
cp "${GOOGLESERVICE_INFO_FILE}" "${PLIST_DESTINATION}"
I believe I need to add/modify that script when I build my app on Codemagic because I always have this error message. but I don't know how. please help!
"No GoogleService-Info.plist found. Please ensure it's in the proper
directory."
As mentioned here, you have to create the file before Xcode starts building the project.
In the pipeline you can run a script that will create an empty file before the build starts: touch ios/Runner/GoogleServices-Info.plist.
In your local development, the first time you build, either create that empty file or run the build twice (the copy script is actually successful the first time).

Writing to build directory info.plist using Xcode's "New Build System"

Before using the "New Build System", we had a build phase script like this:
infoplist="$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH"
builddate=`date`
/usr/libexec/PlistBuddy -c "Set :BuildDateString $builddate" "${infoplist}"
The point of this way to write to the plist at runtime without dirtying the project and having to stash the changes. This still works well and perfectly when using the "Legacy Build System".
On the "New Build System", this script does not work. The directory variables and writing to the plist will works but the changes are somehow overwritten.
Is there a way to write to a built plist via the build phase script? If not, is there a way to achieve the goal of writing information only when the application runs without dirtying the local repo.
It looks like that sometimes, under the "New Build System", the Process Info.plist step is after all the Run custom scripts step.
So I use script to generate another custom.plist at the bundle
#!/usr/bin/env ruby
require 'cfpropertylist'
require 'pathname'
build_info = {
'Time' => Time.now.to_s,
'CommitHash' => `git log --pretty="%h" | head -n1`.rstrip
}
plist = CFPropertyList::List.new
plist.value = CFPropertyList.guess(build_info)
plist_path = Pathname.new(ENV['BUILT_PRODUCTS_DIR']) / ENV['CONTENTS_FOLDER_PATH'] / 'build_info.plist'
plist.save(plist_path, CFPropertyList::List::FORMAT_XML)
As #rpstw points out, any custom build steps will run before the New Build System's Process .../Info.plist step.
To run a shell script after Xcode finishes building, you can add it to your scheme(s) as a build post-action:
Product > Scheme > Edit Scheme... > Build > Post-actions
If you're going to reference any build system environment variables (e.g. BUILT_PRODUCTS_DIR or INFOPLIST_PATH), make sure you change the Provide build settings from selection.
Add your shell script, but remember that if you edit any file in the app bundle (i.e. Info.plist), you'll need to re-sign the app. Add this to your post build step:
export CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
/usr/bin/codesign --force --sign - --entitlements "${TARGET_TEMP_DIR}/${FULL_PRODUCT_NAME}.xcent" --timestamp=none "${CODESIGNING_FOLDER_PATH}"

Automate NewRelic dSYM upload outside Xcode?

My iOS project uses New Relic for tracking. NewRelic requires uploading a dSYM file.
https://docs.newrelic.com/docs/mobile-monitoring/new-relic-mobile-ios/configuration/upload-dsyms-bitcode-apps
I'd like to automate the process through Fastlane, but the provided script complains with:
./NewRelicAgent.framework/Versions/A/Resources/newrelic_postbuild.sh must be run from an XCode build
How can I execute this from within my standard deploy script? I don't want to add this as an XCode post-compile run script or upload manually through the web site.
After some research, I learned that the provided newrelic_postbuild.sh script simply zips up the dSYM folder and uploads it with a curl script.
Fastlane should already do the first part with the gym action. Just specify the output directory in your normal Fastfile build lane.
gym({output_directory: "./build")
When execute, the action above dumps out the symbol file to: ./build/HelloWorld.app.dSYM.zip
To upload that, add it to a variable and execute the following:
NEWRELIC_URL="https://mobile-symbol-upload.newrelic.com/symbol"
NEWRELIC_KEY = "ABCd3fgH1JkLmN0PqRsTuVW8Yz"
DYSM_ZIP_FILE = "./build/HelloWorld.app.dSYM.zip"
Dir.chdir("..") do
sh "curl -F dsym=#\"#{DYSM_ZIP_FILE}\" -H \"x-app-license-key: #{NEWRELIC_KEY}\" \"#{NEWRELIC_URL}\""
end
That'll do it. If you want to just do it from within a bash script, that command would be:
curl -F dsym=#"${DYSM_ZIP_FILE}" -H "x-app-license-key: ${NEWRELIC_KEY}" "${NEWRELIC_URL}"
The benefit of this approach is that we don't have to clutter our Xcode build settings with extra scripts, and we can avoid executing unnecessary and redundant scripting code.

How do you access Xcode environment (and build) variables from an external script?

I am writing a script to automate my iOS building. It will be run outside of Xcode, either via Terminal or from build automating software. Is there any way to have access to Xcode environment variables in my script, so I don't have to try and derive them myself?
For example, can I get access to PROJECT_DIR instead of assuming I'm in the current directory and running pwd?
I am currently hardcoding the product names for my different build configurations. (I'm also hard coding the build configs, but I could parse them them from xcodebuild -list.) Is there a way to get the app if you know the build config name?
(I saw this related question but it doesn't have an answer for me.)
The Xcode environment variables are only defined for child processes of the xcodebuildcommand-line tool.
One solution I used is to have a very simple script as part of my build process (Project->New Build Phase->Add Run Script Build Phase). All this script does is export the necessary variables and call a script in my path somewhere.
That script could be generated by your build script before calling xcodebuild and voilà! you have an external script that has access to Xcode build variables.

Resources