Xcode 8 update build number for multiple target before building - ios

I have an app with 3 targets:
app target
message app
app extension
I'm trying to update the build number for each build, this number must be equal for all the target or App Store will invalid the build.
I've tried different approach, also this one, but I don't like it because I must hardcode the plist file names into the script, while I would prefer to keep it as general as possible.
Searching for different solutions, I've found this tool from Apple called avgtool.
I was able to create a script that updates the build number at the same value for all the target, but this seems to work only if I add it to the build phases at the top of the list. The issue is that this script must be launched before building.
In order to do that it must be added to the Edit Scheme->Build->Pre-Actions but when I write it here it doesn't have any effect.
#!/bin/bash
buildNumber=$(date +%s)
echo "$buildNumber"
xcrun agvtool new-version -all "$buildNumber"

Related

Apple Watch pre-build action to change storyboard customModule references

I currently have a project with 3 different versions of the same app (different branding and such), which is working just fine. I've since then added 3 new Apple Watch targets (1 for each app "version"), where which 2 of them reference the files in the "master" Apple Watch target.
Everything works fine as long as i remember to change the module reference for each storyboard view, so that it maps to the correct interface controller in the corresponding watchkit extension target.
However, remembering to switch this every time i need to run/build a app version/target is a pain and not really a long term solution.
I've created the following command which I want to be run at the correct time, such that it changes the storyboard references before it is compiled.
perl -pi.bak -e 's/customModule=\"(.*?)\">/customModule=\"watchMyTarget_Extension\">/g' ${SRCROOT}/watch/Base.lproj/Interface.storyboard
I also concluded that I would probably want to reset the change after the app was compiled, since I don't want to have a file change for git to complain about. Which is why the aforementioned script creates a .bak file. So after the compilation is done and packed/run on device or whatever, I want to run
mv ${SRCROOT}/watch/Base.lproj/Interface.storyboard.bak ${SRCROOT}/watch/Base.lproj/Interface.storyboard
I've tried placing the scripts in the target's (watchTarget, not appTarget) build scheme, Build->Pre/Post Actions which didn't seem to have any effect. And I tried putting it in Run->Pre/Post Actions which worked to some degree, but it seemed like the post action kicked in before the app was pushed to the simulator and thus crashing the application ("could not run see device logs" or something like that).
So where on earth do I need to put these scripts to have them run at the correct time in the build process?
you should use add New Run Script Phase in your target's Build Phases, place it before the Compile Sources
Steps: (from Apple)
In the project editor, select the target to which you want to add a
run script build phase.
Click Build Phases at the top of the project editor.
Choose Editor > Add Build Phase > Add Run Script Build Phase.
Disclose the Run Script section in the project editor.
Configure the script in the Run Script template.
My solution is to go the Build settings of each watch extension target, setting the Product module name to the same value, for example, xxx_watch_extension. Then we should be able to choose this module for custom classes on the storyboard.
It works fine for me.

When does an iOS program run `Run Script` and load Info.plist

Before I run my App, I would like to run a external shell to update my Info.plist.
With Build Phases->Run Script, I can run my external shell. And here is my question, I saw Info.plist update the data when the program was building, but when I ran into my app, I found that the data read from plist was the former old data.
I also found another funny thing.
One side, if I just built the project(command + B), I saw plist update and then I ran the program, the data showed in UI was new data.
Another side,if I used the Run Button in Xcode, I saw the Info.plist update when Xcode show build successed, however, the data showed in UI was old data.
So, what happens? Could you help me to solve it to let my data be always the newest.
=============
Now, I have moved my 'Run Script' to run external shell before 'Compile Source(swift files)', and it works. However, I still can't understand, since it runs during build time, why the running program can't get the right data from Info.plist if I runs my 'Run Script' after 'Compile Source(swift files)'? does the program load in the data during its run time or compile time?
Building an app involves many different phases. Compiling sources is just one of them, there's also linking, copying resources, signing, etc. Some of them are explicitly listed in the "Build Phases" tab, others are not.
So it's really a matter of running the script at the appropriate time in the build process so that the Info.plist is modified before it is actually used by the build process.
If you click on the "Report Navigator" icon (last icon in the list at the top of the left-side panel), then on the last build, you'll see the report of that build which lists all the steps that were performed during the build process, including running scripts and processing the Info.plist.

One different key between multiple Info.plist files per scheme?

My application's Info.plist file has around 20/30 keys inside. An external SDK we're implementing requires its app key to be set in the Info.plist, but requires separate keys for debug, enterprise distribution, and release schemes.
Is there a way I can create conditional additions to the Info.plist without having to maintain three duplicates of the file (and duplicate all of the other keys, which are identical across all targets)?
Basically what I'd like is the base plist exactly as it is now, then additional new -Debug, -Distribution and Release ones, which just contain this new key. What I'm trying to avoid is repetition of all keys, since it will make adding new ones in future a hassle.
Is this possible?
There are a few different things you might try.
Xcode run script executed before app build
This is a script that is automatically executed before you build. Let's say the 3 duplicate plist files are called InfoA.plist, InfoB.plist and InfoC.plist which are all exact duplicate. When you build the project the contents of InfoA.plist are duplicated to InfoB.plist and InfoC.plist. This would require some knowledge of writing a shell script to do that, but the script is fairly simple.
You could duplicate and rename the files in the script. Or you could use command line tools to copy the contents of the main file to the duplicate files. Two command line utilities you can use to modify the contents of plist files are plutil and https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/PlistBuddy.8.html. These links might help: Add files to an Xcode project from a script?,
Modify scheme and add pre-action
Your schemes also allow you to add pre-actions to your build and you can do the same as above, but have it configured as a pre-action instead of a build phase script.
Here are a few links that might help: how to make xcode run a script before dependencies?, https://www.objc.io/issues/6-build-tools/build-process/, Xcode: Running a script before every build that modifies source code directly but you could also google for “xcode run script before build” for more links.

iOS extensions with multiple targets

In iOS 8, when we create an app extension, we have to decide which target it is attached to. The extension will have the same bundle ID's prefix as the target.
Is there any way to change the target afterward?
If my project contains 2 (or more) targets (for example one for debug/simulator, one for production/device), what's the best way to work with extensions? Do I need to create another extension and duplicate the code (very bothersome to keep the same code for both targets)?
To share one widget with others targets, you only need to add widget.appex target
to Embedded Binaries for every parent target in General configuration tab
Then you'll get Embed App Extensions area at Build Phases automatically
This is my setup: I have 3 targets (production, staging, local) and an extension target that I don't want to duplicate 3 times.
Just to clarify Neo Chen's answer, edit each of your parent targets' schemes:
Build > Pre-actions > New Run Script Action > Provide build settings from (parent scheme).
Paste this for each extension:
#!/bin/bash
buildID=${PRODUCT_BUNDLE_IDENTIFIER}
extId="notification-service"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $buildID.$extId" "${SRCROOT}/${extId}/Info.plist"
Seems to work on first build.
It seems like you should be able to just duplicate the Extension target with its own Info.plist, but not anything else.
However, when you create an Extension, Xcode adds "Embed App Extensions" to the Build Phases of the app's target, as seen below, and there's no UI to do that yet.
Still, you can create the extension for the second target, then delete all the files except the .plist, and fix what needs to be fixed. Here's a step-by-step:
Create "Extension 1" for "Target 1"
Create "Extension 2" for "Target 2"
Delete all files created for "Extension 2", except its Info.plist
Make the "Build Phases" for "Extension 2" target the same as the build phases for "Extension 1". Usually that's adding the necessary .m files to the "Compile Sources" phase, and resources to the "Copy Bundle Resources" phase
I have create a Run Script to support this requirement
#!/bin/sh
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE")
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${SRCROOT}/ImagePush/Info.plist"
buildVersion=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "$INFOPLIST_FILE")
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $buildVersion" "${SRCROOT}/ImagePush/Info.plist"
buildID=${PRODUCT_BUNDLE_IDENTIFIER}
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $buildID.ImagePush" "${SRCROOT}/ImagePush/Info.plist"
ImagePush is my Extension
add to the target which one you need and add make sure this script run before your extension setting in Build Phases, then you just need to do the build action twice (PS: first time it will fail, will try to improve) and it will support multiple target
In my project I need to build a bit different versions of apps (differ in details, e.g. each application is branded with a different logo).
Let's say there is about 10 "app" targets, I can't imagine adding Notification Content and Notification Service extensions per each main target (in this case I would maintaining 30 targets in total - madness).
I run a script (https://gist.github.com/damian-rzeszot/0b23ad87e5ab5d52aa15c095cbf43c59) after "Embed App Extensions" phase,
that overrides bundle id in app extension plists and entitlements, app version, changes the provisioning profile and re-signs the bundle.
Xcconfigs are a great way to modify plist entries and in code variable based on an Xcode scheme change.
If you are wanting to change the Bundle identifier for example of a given extension based on scheme:
Create an Xcconfig file for your extension. My iMessageExtension-Debug.xcconfig has this entry:
PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).iMessageExtension
From the Xcode file inspector click on your project > in the details pane click on your project > Info tab > Add configuration
I created a debug config.
In your newly create config drill to your extension > Select the config file
Create a new Scheme: in the run tab of that new scheme you can select your newly created config. This same process can be done for release extra.
The Xcconfig option we created can be used directly inside of the iMessageExtension > Info.plist:
Bundle identifier : $(PRODUCT_BUNDLE_IDENTIFIER)
If you need to reference a variable in code based on an xcconfig variable:
For example I wanted to change my application initial screen based on the Xcode scheme that was selected:
Xcconfig:
INITIAL_SCREEN = tabBarHome
Plist:
initialScreen : $(INITIAL_SCREEN)
Swift Code:
var initialScreen = object(forInfoDictionaryKey: "initialScreen") as? String
EDIT
2 solutions:
Use XCConfig. See answer mentioned here: https://stackoverflow.com/a/63583849/5175709 and here. Also search for XCConfig on this SO question. You might find some comments.
Change everything through scripts. My answer is doing this.
To be honest I think the XCConfig solution is much much more elegant. You just swap things in and out rather than trying to override everything in a very specific order...
Every other answer had a piece of what was necessary. With some important modifications to this post I was able to get things right.
You need to do three things:
Change bundleId of appex (app extension)
re-sign appex after its bundle is changed. While the app runs on simulator correctly it won't work on real device without this step.
set appropriate provisioning profiles. (excluded from this answer as I don't know how to do that yet)
Note: You can't sign the appex until it's embedded. So the 're-sign appex' step needs to happen after the 'embed App extension' step. Similarly the appex can't be embedded if the bundleId isn't prefixed with the parent app's bundleId.
The final order should look like this:
change bundleId of appex
plutil's sytanx is like this:
-replace keypath -type value
So just do:
plutil -replace \
CFBundleIdentifier -string \
$PRODUCT_BUNDLE_IDENTIFIER.contentExt \
"$BUILT_PRODUCTS_DIR/contentExt.appex/Info.plist"
In case you wanted to learn more about plutil (see here and here for more). PlistBuddy is kinda old.
Note: ContentExtension is the target name I have. Make sure you use yours correctly
re-sign appex
/usr/bin/codesign \
--force \
--sign $EXPANDED_CODE_SIGN_IDENTITY \
--entitlements $CONFIGURATION_TEMP_DIR/ContentExtension.build/ContentExtension.appex.xcent \
--timestamp=none \
"$BUILT_PRODUCTS_DIR/$FULL_PRODUCT_NAME/$BUNDLE_PLUGINS_FOLDER_PATH/ContentExtension.appex"
Note: ContentExtension is the target name I have. Make sure you use yours correctly
The end result is like this:
Don't forget to repeat the steps for every target. The best way to make sure that you have it right is to set the appex's bundleId to something totally wrong and then test all your targets on a real device. If you test it on the sim then you won't be able to validate if the code-signing works correctly or not.
FWIW it's usually a good idea to dump all your shells in one directory like and then reference them from there. But for the sake of simplicity of this post I'm not doing that.
Also make sure you see the original gist it's much smarter if you use that to change all your appexes. You just have to pass the name of the appex and then it would figure out the rest...
You need to create multiple extensions for each ID, but you can create dynamic framework and just link it with each extension. Then you will not need to duplicate your code.

How to automatically add build number to Xcode ios archive

The other day, I was going through my xcode archive looking for a specific build, and I saw that they all looked the same! The question is pretty simple, but I haven't been able to find any way to do it. Basically, I want to add the build number to either the name description or comment (it doesnt really matter which one) of the archive so I can identify it later. Keep in mind that I don't want the version number which shows up after you click the archive. That wont work because I have incremental builds that are distributed on an ad-hoc network with testflight with the same version number, but different build numbers. For example, 2.0.8.10 and 2.0.8.11 both show up as 2.0.8. Any ideas?
What are you using to build your archives? Are you using the Xcode GUI via Product -> Archive? Or are you using some kind of build script that calls xcodebuild? If you're using the Xcode GUI, I'm not aware of any way to automate the name--you'll just have to copy in the full build number when you make the build.
If you're using a build script, check out PlistBuddy (ships with OSX, but by default isn't in your PATH, usually it's at /usr/libexec/PlistBuddy). You can use PlistBuddy to read the build number from your target's Info.plist and then insert that value into the name/description of the archive you're building.

Resources