How to change $(PRODUCT_BUNDLE_IDENTIFIER) in Xcode? - ios

I am build different flavor of Flutter app with different Firebase environment (development and production). I need set different bundle ID for development and production in Xcode for iOS apps.
I am use schemes to configure the different flavor (in Build Settings I add environment value for every configuration).
But I have big issue with change $(PRODUCT_BUNDLE_IDENTIFIER). I need add suffix .development to normal app id for development app id.
I have try follow this method(use User Defined Settings) and change info.plist to get variable from User Defined Settings but it not work.
Error is:
The operation couldn’t be completed. Application
“$(EXAMPLE_BUNDLE_ID)" is unknown to FrontBoard.
So it seem when pass in User Defined Setting it is not interpolate correct.
I have also try mix method of add default PRODUCT_BUNDLE_IDENTIFIER and User Defined Settings. For example: com.example.app$(EXAMPLE_BUNDLE_ID) where EXAMPLE_BUNDLE_ID = .development
I also try reference User Defined Setting $(EXAMPLE_BUNDLE_ID) by direct add it to Bundle Identifier in Target General tab under ‘Identity’. But this then change to : -- EXAMPLE_BUNDLE_ID-
I have also try in info.plist use $(PRODUCT_BUNDLE_IDENTIFIER)$(EXAMPLE_BUNDLE_ID) for Bundle Identifier value. But this give similar error:
The operation couldn’t be completed. Application
“com.example.app$(EXAMPLE_BUNDLE_ID)" is unknown to FrontBoard.
Again this look like interpolation issue.
Anyone know solution? I have look but cannot find answer.
This easy for android because just use applicationIdSuffix ".development” in productFlavors. But I cannot find way like this for Xcode.

Do you need to have different package name (Android) and bundle id (iOS) because you need to use Firebase Auth plugin?
In this case for iOS project you shold consider using PlistBuddy and you could set it adding a Run Script in your XCode build phases like that
if [ "${CONFIGURATION}" = "Debug" ]; then
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.developmento.appName" "$PROJECT_DIR/Runner/Info.plist"
echo "Changed bundle id for developement $PROJECT_DIR/Runner/Info.plist"
else
echo "Nothing to do"
fi
Anyway if you don't use Firebase Auth, you can have the same bundle id in different firebase projects.
If you need then to differenziate firebase projects file between staging and production, you could have a look here:
How to choose between development and production firebase project based on build flavours?
UPDATE
So following OP chat, knowing that he's following this tutorial to setup flutter flavors I've tryed myself to see where we were stuck.
Starting point is the following:
Two Firebase project
Use of Firebase Auth module (so the need to change the bundle id between projects)
And of course two different GoogleService-Info.plist
I start with Xcode bundle id and GoogleService-Info.plist set to production (just an option)
Then I've save both GoogleServices-Info-staging.plist and GoogleServices-Info-production.plist save in my ios/Runner folder
Then I setup this build script before the script for Compile Sources
# Type a script or drag a script file from your workspace to insert its path.
if [ "${CONFIGURATION}" == "Debug" ] || [ "${CONFIGURATION}" == "Debug-Runner-staging" ]; then
echo "Setting up staging firebase environment"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.staging.flutterAppAuthFlavours" "${PROJECT_DIR}/Runner/Info.plist"
cp -r "${PROJECT_DIR}/Runner/GoogleService-Info-staging.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"
echo "$(date) staging flavour - Configuration: ${CONFIGURATION}" > "${PROJECT_DIR}/environment.txt"
elif [ "${CONFIGURATION}" == "Debug-Runner-production" ]; then
echo "Setting up production firebase environment"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.example.flutterAppAuthFlavours" "${PROJECT_DIR}/Runner/Info.plist"
cp -r "${PROJECT_DIR}/Runner/GoogleService-Info-production.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"
echo "$(date) production flavour - Configuration: ${CONFIGURATION}" > "${PROJECT_DIR}/environment.txt"
fi
And I called it Setup Firebase Environment (you can call it what you want)
This script store also some logs (with timestamp) in a file called environment.txt inside ios folder in order to easy check what xcode build has done
And now about Schemes and Build Configurations:
I've done two Build Configuration that are the exact copy of my Debug Build Configuration and I called them
Debug-Runner-staging
Debug-Runner-production
The rule of thumb is to name the build configurations as 'Debug-<your flavor>' and you need to have a scheme for every flavors you have, so I have these:
Runner-staging whose Run calls Debug-Runner-staging build configuration
Runner-production whose Run calls Debug-Runner-production build configuration
So now if I call flutter run --flavor Debug-staging I have a build that runs on my staging firebase project.
and if I call flutter run --flavor Debug-production I have a build that runs on my production firebase project.
UPDATE 2
Just for completness you could change bundle id also here:
Anyway it seems that there's a strange behavior that once you build a flavour a second time flutter command build correctly the flavor but run the previos build flavor.
As building with XCode and switching with schemes all works as expected (even the run of the right application) I guess that this could be a flutter command issue. So I suggest you trying file an issue here linking also this SO question/answer.
UPDATE 3
After a bit of intel I've found that flutter tools set the applicaiton launching environment before building the project. So when we change CFBundleIdentifier inside Info.plist the first time, the second time we launch flutter run it takes the previous modified value and try launching this bundle id while during build we are changing it because we are building a different variant.
A possible solution could be to launch a script that change the CFBundleIdentifier inside Info.plist before calling fluetter run.
For example starting with a Info.plist with a production bundle id of com.example.flutterAppAuthFlavours we could do something like that
Here I’ve used sed command just to think different, but you could call always our belowed PlistBuddy to make the change before calling flutter run.

Related

Xcode - Setting/Adding environment variables from a bash script during the build phase

I have 2 API keys that are currently used in my code, and are also hardcoded inside the code. These keys need to be removed from the code completely and added in as environment variables instead from outside Xcode during the build phase. So far I have tried having this script
export API_KEY=keyvalue
as a Run Script that occurs before Compile Sources in the build phase, having that script as a Pre Action in the project's scheme, and also building the app from the command line with this but the environment variables for the given keys are always null.
xcodebuild -project MyProject.xcodeproj \
-scheme "My Scheme" \
-sdk iphoneos \
-destination 'platform=iOS,name=<devicename>' \
-derivedDataPath './output' \
API_KEY='KEY' \
SECOND_API_KEY='KEY' \
When manually building the project like this I can see the keys being exported correctly like all the other settings but cannot seem to access them with NSProcessInfo.processInfo.environment objectForKey:#"API_KEY" or with NSUserDefaults. I have also tried editing the scheme in Xcode so that the environment variables are set as API_KEY = $(API_KEY) along with the above but that did not work, as well as setting default values for these keys in Build Settings which also did not work. Is this actually possible to do? Thanks.
If I'm understanding you correctly you are misunderstanding environment variables, if I'm not my apologies.
You cannot access environment variables that were set during the build process when your application runs – unless that build process writes the values into the code or some configuration file.
Environment variables are passed to a process when it is started, your code can access those variables. While the names of the environment variables will need to be embedded in your code the values of those variables is not.
It sounds like you wish to distribute a compiled app without the API keys it needs to run and then supply those when it is run. You could do this by using a small shell script to set environment variables and then launch your app. Or you could use a scheme like those used for serial numbers, so your users have to enter the API keys (just tell them they are serial numbers ;-)) when your app is first launched and then store those in a suitable file (e.g. in your app's preferences or application support folder) obfuscated in whatever way you choose. HTH

How to make deliver (fastlane) download metadata for multiple targets?

I have an Xcode project with six targets, each target is made to build a separate app. I'm trying to setup fastlane to assist me in publication of these apps.
Fastlane docs suggest using .env files in order to handle multiple targets (you can specify app_identifier, team_name, etc. in different .env files, and then, for instance, call fastlane appstore --env ENV_NAME_HERE). However I can't figure out how to set up deliver properly.
deliver init downloads metadata for one target only by default. I need to download metadata for all my targets to different directories (and then use those directories to upload data, obviously).
deliver download_metadata doesn't accept the --env parameter (my Deliverfile depends on env files). I've tried fastlane deliver --env, but it seems to be just a shorthand for deliver, so it doesn't work either.
I guess I could just manually run deliver with different --metadata_path parameters (and all the other parameters since my Deliverfile is invalid, because it depends on env files), and then later specify directories using Deliverfile + .env files. But since I have Deliverfile and .env files already set up (now I use deliver to upload the binary only), I hoped that there is a better way. Is there?
P.S. This is a large legacy project, so splitting it into six different projects would be great, but it's not an option, unfortunately.
I've been struggling with this as well and setting up the submit is easy using the .env files.
But retrieving the initial data is difficult, but not impossible.
To grab the metadata it ran this command:
fastlane deliver download_metadata -m "./Targets/Release/Metadata" -u "itunes#username" -a "com.example.ios"
And for the screenshots:
fastlane deliver download_screenshots -w "./Targets/Release/Screenshots" -u "itunes#username" -a "com.example.ios"
Adding up to #rckoenes answer:
1) Create an .env.yourEnvName file with this info (as an example):
DLV_METADATA_PATH="../Targets/Your_Target/Metadata"
DLV_ITUNESCONNECT_USERNAME="yourItunesUser#something.com"
DLV_BUNDLE_ID="com.yourCompany.yourTarget"
2) Create a lane like this:
desc "Download metadata"
lane :metadata do
sh('fastlane deliver download_metadata -m "$DLV_METADATA_PATH" -u $DLV_ITUNESCONNECT_USERNAME -a $DLV_BUNDLE_ID')
end
3) Call fastlane like this:
fastlane metadata --env yourEnvName
That way it's a little bit cleaner, and you keep the vars in the .env file.
For automating this call for multiple targets, please refer to: https://docs.fastlane.tools/faqs/#multiple-targets-of-the-same-underlying-app
This is a combination of #rckoenes, #Riddick's answer and this fastlane github issue submission.
I was trying #Riddick's answer to have a cleaner workflow but I couldn't make it work to download metadata. For some reason, it only makes the metadata path folder but no metadata downloaded from iTunesConnect. I did some trial and error and found this line of code from the link above this:
ENV["DELIVER_FORCE_OVERWRITE"] = "1"
added it to the lane and worked!
1) Create an .env.yourEnvName file with this info (as an example):
METADATA_PATH="../Targets/Your_Target/Metadata"
APP_IDENTIFIER="com.yourCompany.yourTarget"
2) Create a lane like this:
desc "Download metadata"
lane :metadata do
ENV["DELIVER_FORCE_OVERWRITE"] = "1" # This is the additional line from Riddick's code
sh "fastlane deliver download_metadata --app_identifier #{ENV['APP_IDENTIFIER'] --metadata_path #{ENV['METADATA_PATH']}"
end
3) Call fastlane like this:
fastlane metadata --env yourEnvName
***I did not use the username parameter because I had it on my Deliver File.

Parse Crash Reporting - Script Does not End

I just enabled ParseCrashReporting in my app, and now when I build the app, Xcode stays on "Running 2 of 2 custom shell scripts" (i have another simple script for HockeyApp integration, placing it before that does not change anything).
My script is below:
export PATH=/usr/local/bin:$PATH
cd ~/OneDrive/AppName
parse symbols AppName -p "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
My AppName folder is also where I started my parse cloud repo, it contains the folders cloud, config and public. I tried changing the path to AppName/cloud but no change.
Xcode stays running that script for a long time...i've waited 10 minutes for it before and it doesn't continue beyond that. Once I stop the build, I get an error: Shell script invocation error:
Uploading iOS symbol files...
Command /bin/sh failed with exit code 1
I assume the error just shows because I cancel the task. Why would this be sticking like so? I have looked at several questions on parse crash reporting and have not seen any similar issues.
Just use the following script instead, I just tested it and it works:
echo "Parse Crash Reporting"
export PATH=/usr/local/bin:$PATH
CLOUD_CODE_DIR=${PROJECT_DIR}/helloKittyAdventureTimeCloudCodeFolder
if [ -d ${CLOUD_CODE_DIR} ]; then
cd ${CLOUD_CODE_DIR}
parse symbols YOUR_PARSE_APP_NAME --path="${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
echo "Finished uploading symbol"
else
echo "Unable to upload symbols"
fi
IMPORTANT:
The following line needs to be changed based on your folder name, that's it, keep everything else the same:
CLOUD_CODE_DIR=${PROJECT_DIR}/helloKittyAdventureTimeCloudCodeFolder <===this should be the
name or your own folder!!, so, if your folder is named theAdventuresOfCaptainCookCloudCode,
then you would type this:
CLOUD_CODE_DIR=${PROJECT_DIR}/theAdventuresOfCaptainCookCloudCode
Also, one more thing to note, you don't need the echos and such if your run this as a Run Script in Xcode, but you don't have to take them out either, you can just run it like this and you won't have a build error.
One more thing, make sure to change YOUR_PARSE_APP_NAME to the name or your app, sorry about that, this also needs to be changed

Jenkins iOS build using credentials and developer profile

We are using Jenkins as our CI server for our iOS team with the following setup:
Master server on OSX, not running any job
2 slaves on OSX running our integration jobs + UI Testing
Currently all signing identities and provisioning profiles for the apps are uploaded each slave which makes the administration a tad tedious and adding a new node to the cluster even more painful.
To work around this we've looked into using the credentials plugin with Developer profiles and import the profile as the first build step on all iOS jobs but are faced with to main issues:
The import developer profile seems to work the first time (at least for creating the keychain entries) but the job fails with a "no matching provisioning profile" error, even if the developer profile contains all the provisioning profiles required by the target.
Second run on the same job always fail with a "Keychain already exist" error
We've tried some work arounds for the second issue adding a shell build step removing the particular keychain but are still faced with the first error. If we manually install the profile on the slave the build passes but this defeat the purpose of using the credentials plugin.
What do you guys think?
I think the newest version of the credentials plugin now first removes any existing keychains with a matching name before importing, as seen in the log output below.
$ security delete-keychain jenkins-MyAppsBuildName-iOS
$ security create-keychain -p ******** jenkins-MyAppsBuildName-iOS
$ security unlock-keychain -p ******** jenkins-MyAppsBuildName-iOS
Because of this fact, I don't think you will have an issue anymore with duplicate keychain errors on the second run.
As far as the issue relating to the provisioning profile not being found, add the following line inside the execute shell command and run a build on jenkins.
security list-keychains
Take a look at the console for that specific build and you should see a list of all the keychains that are currently in the scope of the shell.
If you do not see "jenkins-MyAppsBuildName-iOS" as a listed keychain, this is why you are having the signing issue. Because the keychain is not listed, it is never even being searched through to find the proper signing identity/profile.
Solution: Warning: it's hacky
I'm not 100% sure why this is happening, but from other threads it appears to be a permissions issue.
Luckily there is an easy way around this.
In the execute shell command add the following:
security list-keychain -s jenkins-${JOB_NAME}
This will reset the keychain list to include the keychain needed to successfully build the project.
To verify that this now lists the proper keychain, you can add the following lines to the shell command:
security list-keychain
security list-keychain -s jenkins-${JOB_NAME}
security list-keychain
Now compare the output of the first list-keychain command with the second list-keychain command in the console. Make sure that the jenkin's build keychain is listed after the second security list-keychain output.
Warning: This will permanently change the keychain list on the system, so it is probably a good idea to reset the keychain after the build completes. You can accomplish this by settings the default desired keychain values in the xcode configuration inside of Jenkin's System Configuration section. After doing so, make sure to tick the check box "Restore OS X keychains after build process as defined in global configuration" under build environment inside of the Jenkins job's page.
Additional info: In my example I set the keychain-list to only include the keychain generated from Jenkins, but you may decide to also include the standard system and login keychain's by modifying the line as such:
security list-keychain -s jenkins-${JOB_NAME} login.keychain System.keychain
Keywords: Jenkins, iOS, slave, node, Xcode, plugin, credentials, .developerprofile

Xcode Build script after compile

I'm developing for jailbroken devices, and have gotten Xcode building and debugging workin on device with a self signed certificate and some edits to Xcode.
But the app I'm developing requires being able to call setuid(0), thus it needs to have chmod +s in order to run properly.
Apart from this iOS apps that needs to run as root need a bash script to invoke it like such:
#!/bin/bash
dir=$(dirname "$0")
exec "${dir}"/App\ Binary_ "$#"
So, I need this build script to run on building my app:
cd ${BUILT_PRODUCTS_DIR}/My\ App.app/
mv App_Binary App_Binary_
cp /Users/john/Shellscript Binary_App
chmod +s Binary_App_
chmod +x Binary_App
I've tried adding this as a normal build script, and as a part of the scheme as both a Build post-action or a Run Pre-action. Neither which has worked. For example a post-action script on build returns that code signing failed, since it tries to codesign App Binary that is now the shell script. If I do it as a pre-action script on Run it displays "Xcode cannot run using the selected device.Choose a destination with a supported architecture in order to run on this device."
What should I do?
I use a post-action script to build my jailbreak apps. Although they don't need an additional chmod or bash script to run, you could use a script like mine to install your app (as a system app, not a normal App Store app) using ssh, then perform the chmod command and swapping the binary with a bash script on device via the post-action script.
You could try something along these lines (I tried to use the details from your script, but there may be one or two mistakes):
# copy binary
scp -P $PORT -r $BUILT_PRODUCTS_DIR/${WRAPPER_NAME} root#$IPOD://private/var/stash/Applications/${WRAPPER_NAME}/App_Binary_
# copy script
scp -P $PORT /Users/john/Shellscript root#$IPOD://private/var/stash/Applications/${WRAPPER_NAME}/Binary_App
# set special permissions
ssh -p $PORT root#$IPOD "chmod +s /private/var/stash/Applications/${WRAPPER_NAME}/Binary_App_"
ssh -p $PORT root#$IPOD "chmod +x /private/var/stash/Applications/${WRAPPER_NAME}/Binary_App"
Set IPOD and PORT as appropriate. ${WRAPPER_NAME} is the name of the app as saved on disk, with the .app extension.
Actually, this could be done if you need your app to be installed as a normal App Store app as well, you'd just need to find out where it's been installed to and adjust the paths as appropriate.
You'll obviously need to have SSH installed and activated on your device (available on Cydia).

Resources