I am starting up a new project that will have multiple developers working in Xcode using Git. I want to setup auto-incrementing build numbers, and I have found a few good answers on how to do this, but my concern is with the multiple developers and Git.
I know in Xcode there are some files, like the project file, that are very sensitive and hard to merge. The best techniques I have seen for auto-incrementing build numbers would be susceptible to the same multiple Git merge issue.
Is there any guidance here as to how I can use an auto-incrementing build technique and still keep the merging issues to a minimum? Maybe the answer is that there is no good answer, but I want to find out what others are doing.
So first a thanks to harald for his answer and comments, which helped me to better think through what I really wanted to accomplish.
Let me sum up what my thought was, what I realized I wanted to do, and how I did it.
I wanted a way to populate the build number (CFBundleVersion) in an automated way. This value is stored in the main project -Info.plist file, which is source controlled. I was concerned about following guides to automate this via "Build Phase" scripts in that it would modify this file for possibly multiple developers and cause a situation where constant merging would be needed.
The reason I wanted this is so that I could easily track down what code was in play for a particular build. While one certainly can manually do this with tagging and proper documentation, I wanted a more automated and flexible way since I think I will be done a lot of builds out to TestFlight.
My original line of thought was to use a simple incremental build number for this value, but that would really be trouble with the merging. Harald's suggestion of using the commit SHA on the Git repo sparked a better line of thinking for me.
So the first part of my solution is to use the first 9 characters from the commit SHA (the SHA would be too long). I use the first 9 characters since I am running GitLab HQ (a great open source project, by the way) and they use the first 9 in their display of the running stream of commits. The command to get me this is as follows:
/usr/bin/git rev-parse --short=9 HEAD
In order to avoid having the Git merge issue, I thought of first changing the build number (CFBundleVersion) value in the projects -Info.plist file, allowing the build to run, and then at the last step change the value back to a default 1 so that it would not appear changed in source control. I tried every which way to do this in the "Build Phases" flow using a "Run Script", but it seems that even when putting the code in to revert the value in the final step, it was affecting the running app.
After digging a bit more I came across the schemas and the pre-actions and post-actions. This seemed to be my route.
Thus, in the schema, for the "build" plan, I created a pre-action that would set the CFBundleVersion value to my current commit Id (9 characters), and in the post-action I would revert this value back to the default (1). This seems to work as I need it to.
Here is my pre-action code:
buildPlist="$SRCROOT/$INFOPLIST_FILE"
echo "The build plist file $buildPlist"
CFBundleVersion="$(cd $SRCROOT;/usr/bin/git rev-parse --short=9 HEAD)"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $CFBundleVersion" $buildPlist
Here is my post-action code:
buildPlist=$"$SRCROOT/$INFOPLIST_FILE"
CFBundleVersion=1
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $CFBundleVersion" $buildPlist
Something to note with this code that differs from what I was using in the "Build Phase" scripts is the requirement to use the $SRCROOT to set the directory. Initially I was under the impression that you would get the same build settings as you do in the "Build Phase", it appears you do not. There is an option in the "Run Script" window named "Provide build settings from :" and it lets you select a target. Maybe that is working correct and regardless given the pre-action you must set your full directory path. It took me a little bit to figure that out so I thought I would mentioned it.
In summary, I appreciate the information I received and it helped me think through what I was looking to do and ultimately get to the goal I wanted.
While I don't really see the point of having linearly incrementing build numbers trying to track a nonlinear development model like git, there's ways to acheive it. It depends on what you're after though. If you just want a build number for "official" builds, you can quite simply generate this by the build-scripts when building. Then have the build script tag the commit being built with the generated number. Something like this:
git checkout build-branch
generate_build_num > buildnum.txt
make
git tag `cat buildnum.txt`
The responsibillity for generating a unique, incrementing build number is with the generate_build_numscript, and you can always find back to the corresponding commit by using the tag.
If on the other hand you want every commit in the git tree to correspond to a linear build number, you're in for a lot more work.
In my opinion, there's not much to gain in this compared to using the sha1 directly.
I use a slightly different approach for avoiding merge conflicts associated with touching the Info.plist on every build. It's similar to magnusMTB's strategy of temporarily setting (and then resetting) the build number, but instead of using pre- and post-actions I just leave a placeholder value in the project's Info.plist, and then add a "run script" build phase to modify only the product's build number without touching the Xcode project's copy.
The script looks like this:
# Grab some environment variables from Xcode
BUILD_NUMBER_FILE="${SOURCE_ROOT}/BuildNumber"
PLIST_FILE="${TARGET_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/Info.plist"
# Create a build string
# Anything goes, but this uses my employer's App_vX_YYMMDD.HHMM format
majorVersion=`/usr/libexec/PlistBuddy -c "PRINT:CFBundleShortVersionString" "$PLIST_FILE"`
dateTime=`date "+%y%m%d.%H%M"`
buildNumber=App\_v$majorVersion\_$dateTime
# Write the build string to a local file under version control
echo $buildNumber > $BUILD_NUMBER_FILE
# Set the build number in the product's plist (Not the Xcode project's plist)
echo Writing generated build number \"$buildNumber\" to \"$PLIST_FILE\"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$PLIST_FILE"
In this way, the Info.plist under version control remains untouched, but the build number inside the app used for testing or deployment is the "correct" dynamically generated value. The build numbers remain in version control through a single clean-merging file that lives apart from Info.plist.
Caveat: I'm not sure if the build number is baked into the binary somewhere outside Info.plist. This could create issues. However, for what it's worth, the app I'm testing with self-reports the correct (script-generated) build number from the Info.plist in its bundle.
Related
Using TeamCity version 2017.2.3 (build 51047).
I have a SMB Upload build step and would like to upload the builds from the default branch to a different location than all other builds.
I seen the following variable that will tell me if its a deafult build %teamcity.build.branch.is_default% however im not too sure how or even if its possible to specify conditional Target URL for the SMB Upload step.
Either with some form on IF block, or ternary statement inline.
Non of this is done using PowerShell. All through the UI, i would prefer to keep it that way if possible. Our old TeamCity install saws essentially just a glorified PowerShell script runner and grew into this un maintainable monolith, besides PowerShell is a rather terrible language.
Essentially what i would like would be builds on any branch going to
//DataStore/builds/my-api-%build.number%.zip
Whilst builds on the default branch go to
//DataStore/builds/default/my-api-%build.number%.zip
Any help would be appreciated thanks.
In general, this is not possible. The SMB Upload runner doesn't let you specify a condition anywhere in it.
If conditional steps were possible, you could create two steps: Upload from default and Upload from non-default, each with a different Target URL. It turns out that conditional build steps are the most voted-for feature in TeamCity, see this ticket, yet JetBrains are quite opposed to the idea. You may want to vote for the ticket, or at least monitor it.
There is one thing that you can do, other than Powershell. The Target URL field expands variables. (You can tell this by typing a percent sign in the text field: TeamCity immediately starts suggesting variable names. Compare this with the Step name text field above: that has no variable expansion.) Thus, you could enter a Target URL in this form:
//DataStore/builds/%teamcity.build.branch.is_default%/my-api-%build.number%.zip
That way, you'll end up with files being uploaded as
//DataStore/builds/true/my-api-1234.zip
//DataStore/builds/false/my-api-1235.zip
Now that's kind-of ugly. You can improve it in two ways:
1) create symlinks or junctions on your file server (on the directory/filesystem level), so that the above are accessible to the clients as
//DataStore/builds/default/my-api-1234.zip
//DataStore/builds/my-api-1235.zip
2) even better, you can set up a variable that will either contain the value "/default" or "". Then you can change your Target URL to //DataStore/builds%myCleverVariable%/my-api-%build.number%.zip. To do that, you'll need an extra step before this one, a Powershell runner, that will test the value of %teamcity.build.branch.is_default% and set %myCleverVariable% accordingly, using TeamCity service messages.
The conditional build step feature has been implemented in TeamCity 2020.1
I'm programming in xcode (actually Phone Gap in conjunction with xcode) with git. I'm having a strange issue. When I create two identical branches and try to check out back and fourth between them with out making any changes git is telling me that I need to commit because a change has been made (this same thing is also resulting in merge conflicts). It says the changed file is:
platforms/ios/Butterfli.xcodeproj/project.xcworkspace/xcuserdata/benpearce.xcuserdatad/UserInterfaceState.xcuserstate
Can anyone explain what's going on and how to deal with it.
Yes, .xcworkspaces are simply files that Xcode uses to describe the workspace or projects.
IMHO, There's no need to check these files in at all, unless you share settings with other folks in your projects (and in your case, I suppose other machines that have a user named "benpearce").
In fact, you can safely add them to your .gitignore file.
More info can be seen here
I have a Heroku custom buildpack. I'd like to include the commit ID of the current release in the asset build so that, if the front end reports any errors, we know which version of the code base to check against.
Is there a way to discover the ID of the commit being pushed during the compile stage of a heroku build?
What I did recently in my PHP build pack, is that I'm adding a simple date/time string
as an environment variable to the .profile.d script. This isn't strongly unique, but should be good enough, because there isn't any real deploy that takes less than a second.
Here's how I generate the .profile.d/php.sh:
cat > ".profile.d/php.sh" <<SH
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/app/bin:/app/vendor/nginx/sbin:/app/vendor/php/sbin:/app/vendor/php/bin:/app/vendor/bin:\$PATH
export HEROKU_BUILD_TIME=$(date +%Y%m%d%H%M%S)
SH
source ".profile.d/php.sh"
Edit: Sorry, this doesn't solve the problem with having the commit id for debugging. I thought of having a unique string for versioning.
I have a curios issue.
I have a project that I've worked on and always built from the XCode IDE, and it worked fine. Now I'm setting up Bamboo to build the project and as such am building it from the command line.
The issue is, if I check my code out of GIT and then use xcodebuild to build it it says that the scheme cannot be found, but if I open the project, it builds and if I then try to build it again from the command line with the same command, it works.
What magic is XCode doing when I open the project or am I doing something dumb, maybe excluding a file in my .gitignore that I shouldn't?
You are definitely on the right track with respect to the .xcscheme file -- I had this problem appear while setting up my own projects!
For posterity, or at least anyone getting here from a search, here are two versions of things -- the "I'm busy, so just the facts please" version and a more involved discussion and rationale. Both of these versions assume you are trying to build from a Workspace file; if you aren't then my apologies as this mostly applicable to workspace-based projects.
Condensed 'Fix-it' Version
The root cause is that the default behavior of Schemes is to keep schemes 'private' until they are specifically marked as shared. In the case of a command-line initiated build, the Xcode UI never runs and the xcoderun tool doesn't have its own cache of Schemes to work with. The goal is to generate, share, and commit the scheme you want Bamboo to run:
On a clean working copy of the code, open your Project's workspace.
Choose Scheme > Manage Schemes... from the Product Menu.
The list of Schemes defined for the project appears.
Locate the Scheme Bamboo is trying to run
Ensure the 'Shared' box is checked for that scheme and that the 'Container' setting is set to the Workspace and not the project file itself.
Click 'OK' to dismiss the Manage Schemes sheet.
A new .xcscheme file has been created in your project at WorkspaceName.xcworkspace/xcshareddata/xcschemes.
Commit this file to your repository and run a Bamboo build.
Deeper Discussion and Rationale
Xcode 4 introduced Workspaces and Schemes as a way to help try and tame some of the chaos that is inherent to dealing with the mechanics of wiring related Xcode projects, build targets, and build configurations together. The workspace itself has its own set of configuration data that describes each of the smaller 'boxes' of data it contains and acts as a skeleton for attaching .xcodeproj files and a set of shared configuration data that gets mirrored to each developer machine or CI system. This is both the power and pitfall of Workspaces -- there are 1) lots of ways in which one can get things configured 100% correctly, but put into the wrong container or 2) put into the correct container, but configured improperly thus rendering data inaccessible by other parts of the system!
The default behavior of Xcode 4 schemes is to automatically generate new schemes as projects are added to the Workspace file. Those of you that have added several .xcodeproj files may have noticed that your scheme list quickly becomes unruly especially as project files are added, then removed, and then readded to the same workspace. All schemes, autogenerated or manually created, default to being 'private' schemes visible only to the current user even when .xcuserdata files are committed with the project's data and configuration. This is the root cause of that cryptic build error Bamboo reports from xcodebuild -- Because Bamboo operates the build through the command line and not the Xcode UI, it doesn't have an opportunity for Schemes to get automatically generated and relies only on those that are defined in the workspace itself. Assuming you've configured Bamboo to build from a workspace using a command like this:
xcodebuild -workspace MyWorkspace.xcworkspace -scheme MyApplication -configuration Debug
xcodebuild goes looking for file <'scheme' Parameter Value>.xcscheme existing at <'workspace' Parameter Value>/xcshareddata/xcschemes.
Obviously there are bunches of ways in which one could configure both Bamboo and a workspace, so keep in mind that your unique configuration may not map 100% to what is presented here. The key takeaways:
Certain automated tasks the Xcode UI magically takes care of are not available via the Xcodebuild CLI.
You can attach scheme and build configuration data to many places in the 'container hierarchy' -- Make sure your data winds up in the right container (Workspace, Project, and/or Build Target)
Consider where in the container hierarchy the xcodebuild tool may be looking for configuration data; a great indicator of where it will start looking is based on the use of '-workspace' or '-project' arguments.
The 'Shared' box is already checked...now what?
I encountered this same issue on my own Bamboo instance; it turned out that the scheme that was committed in my repository was outdated and the latest version of the command line tools wasn't handling it gracefully. Since this existed previously, I took a look through the settings to make sure there wasn't anything glaringly custom about the scheme, deleted and recreated the scheme ensuring that I marked it as 'Shared', and recommitting the new .xcscheme file to the repository.
If everything looks good and rebuilding it doesn't solve the issue, double check that container setting -- it is really easy to get that scheme attached to the wrong container in the hierarchy!
Debug the issue like this:
xcodebuild -list
or if you are using a workspace (e.g. with pods)
xcodebuild -workspace MyProject.xcworkspace -list
If you scheme is not listed fix like so:
Most of the answers would suggest you to make your scheme shared using Xcode, then commit changes to repo. That works, of course, but only if you have access to source code and have rights to commit changes, and couple of other assumptions.
But there's a number of "what ifs" to consider
What if you just can't modify the Xcode project for some reason?
What if you create a new scheme automatically on CI server? This actually happens quite often. If you use test automation framework, like Calabash, you'll normally end up duplicating an existing target, which automatically duplicates a scheme as well, and the new scheme is not shared, even if the original scheme was.
Ruby & xcodeproj gem
I would recommend using xcodeproj Ruby gem.
This is a really cool open source tool that can help you to automate tons of Xcode-related tasks.
Btw, this is the gem used by CocoaPods to mess around with your Xcode projects and workspaces.
So install it
sudo gem install xcodeproj
Then write a simple Ruby script to re-share all the schemes, the gem has recreate_user_schemes method for that purpose
#!/usr/bin/env ruby
require 'xcodeproj'
xcproj = Xcodeproj::Project.open("MyProject.xcodeproj")
xcproj.recreate_user_schemes
xcproj.save
It doesn't just copy scheme files form user's folder to xcshareddata/xcschemes, it also creates those files first by parsing the pbxproj file.
Ok I know its 2 minutes later but I found another stack overflow that says the scheme has to be set to shared... Where does Xcode 4 store Scheme Data?
One common reason for the scheme to be missing is forgetting to push the commits to the origin. If you get a missing scheme message, you should first verify the scheme is shared, then verify you have committed the changes AND pushed them to the origin server.
I had this error while implementing CI.The Question above is identical to my problems except I am using Gitlab's own CI tool.You can check if there is any such file in Bamboo.
I solved it by making some changes to gitlab-ci.yml file.
After you hav made your scheme availabe by sharing. In Xcode Go to Products>Scheme>Manage Scheme and check share to share.
Changes
Set absolute path everywhere.
eg.xcodebuild clean archive -archivePath /path/to/your/project/build/testDemo -scheme testDemo | xcpretty
here you need to change /path/to/your/project/ with your path and testDemo with your project name.
I faced this issue and even if some of the answers here actually provide the solution, I didn't find it very clear. So I will just add one more. In a nutshell how to share a schema from excode.
Navigate to Product > Scheme > Manage Schemes
You will then be shown a list of schemes, with each denoted as being shared or not. Just check the ones that you want to share (it may be different ones for dev and prod builds)
Images taken from this article https://developer.nevercode.io/docs/sharing-ios-project-schemes
Got the same problem but during building with xcode as subproject of main one. Built subproject in xcode standalone - after that this error disappeared.
I want to add solution for my case related to this thread. This one is for you who clone existing project, with all the schemes you need are already being shared:
, with fastlane lanes correctly display all your lanes including all your schemes:
, but fastlane gym only show main schemes (not dev and test schemes):
The solution is to uncheck the shared option for schemes that not listed by fastlane gym and then check it again. It will generates .xcscheme for the schemes:
Now, if you check with fastlane gym, all the schemes will be listed:
Then you should commit those .xcshemes file to the repository, so other developer who clone the project will get the files.
For anyone with Xcode 11.4 trying to find "Shared" button on scheme, it's now moved into the individual scheme.
Select the scheme you want
Press "Edit"
Check the "Shared" box
I'm researching the best approach to automating our build process. I've got my own ideas (through experience on a previous non-iOS project) but need good arguments for and against various possibilities.
Objective: A single xcode project with a single target (think white-label) needs to be built in 1..N different flavours (concrete brandings) with minimum user interaction and minimum technical knowledge. For AdHoc and/or AppStore.
Essentially, that will mean specifying per build; a folder containing Icons + Splashscreen, a bundle containing brand specific resources and (presumably?) the Info.plist, specifying appname, bundle-id, etc.
Issues that need to be respected or clarified;
Manual build of a single brand via Idiot-Proof GUI (choose a git
branch/tag, specify a certain brand, configure the app e.g.
IAP-enabled, server-domainname, etc - will be written to the
info.plist)
In previous manual tests, setting the executable name in
the plist didn't work? Sorry, have forgotten the exact problem..
perhaps was only an Xcode Debug buildconfig problem, not relevant to
a distribution build?
Code-Signing?!? Can the profile be specified
on-the-fly? Some brands need to be built with the customer's own
profile.
My personal feeling: Hudson or CruiseControl + Xcode plugin.
There seems to be plenty of documentation around for an Xcode solution and I've seen this in action on a Flex project I worked on, with almost exactly the same white-label/branding requirements. Of course that was using Ant script though and there was NO behavioral config to respect. That's my only uncertainty here... I suspect it would have to be hardcoded somewhere, but that's not the answer that's going to please some people. There is a wish to be able to specify the various app-config settings (server url, is function Foo supported, is the view X displayed, etc, etc) via a GUI form, when building manually. I'm not sure how easy it would be to shoehorn that into a typical Hudson or CC config?
And hence one suggestion that has been made is to write an OSX app for building our clients. The theory being, nice clean non-tech UI for entering all the necessary meta data & app setting and a big shiny green button labelled "Build". But personally I'm skeptical that this approach is any more flexible or easier to implement than a classic CI solution.
So the question is basically, what's preferable; a classic server based, version control integrated, CI approach or a custom OSX utility?
Whichever we go for it'll almost certainly be a requirement to get it up and running in 2 or 3 days (definately less than one week).
IMHO you can resolve all issues using different targets of XCode.
Every target will share the code but it could:
be signing with diferent profiles
use diferent plist: this implies having different names..
use diferent brand images. You only have to name the image with the same name and select the correct target in file inspector.
Build with one click in XCode.
I hope this helps
An extremely later reply, but the approach I would take would be to create the white label IPA, and then create a script to:
1. Unzip it (change the .ipa file extension to .zip).
2. Change assets.
Update the info.plist (using Plistbuddy command)
Zip it again.
Resign the code.
See this script as a starting point: https://gist.github.com/catmac/1682965
Very late answer. But I would go with different .xcconfig files and multiple schemes. The scheme names could be a combination of target/brand.