I am working on my company's continuous integration server, and the build process is failing because the server does not have access to schemes in an xcode project.
Basically, they are using Cmake to generate xcode projects on the fly to be used for a single build, and then discarded until the next check in.
My research indicates that this problem will be fixed if there is an .xcscheme file with the .xcodeproj file, but for various reasons that can't be generated and checked in ahead of time.
Is there a way to generate this file using xcodebuild or some other command line tool so that we can work it into existing build shell scripts?
The xcodebuild documentation, google, and S.O. are surprisingly lacking on this topic.
I generate the XCode project using the -G Xcode too; I'm using the scan-build plugin ( http://blog.manbolo.com/2014/04/15/automated-static-code-analysis-with-xcode-5.1-and-jenkins ) in jenkins.
It requires the workspace files.
My script to launch & watch xcode looks like that:
($WORKSPACE is set by jenkins)
#!/bin/bash
/Applications/Xcode.app/Contents/MacOS/Xcode "${WORKSPACE}/Build/arangodb.xcodeproj" &
XCODE_PID=$!
# now we wait for xcode to build the workspace:
WAIT_FOR_XCODE=0
while test ${WAIT_FOR_XCODE} -lt 6; do
WAIT_FOR_XCODE=`find "${WORKSPACE}/Build/arangodb.xcodeproj" |wc -l`
sleep 2
COUNT=`ps -p ${XCODE_PID} |wc -l`
if test ${COUNT} -lt 2; then
echo "XCode went away unexpectedly!"
exit -1
fi
done
#ok, we believe its done. kill it, and wait until its realy dead.
kill ${XCODE_PID}
COUNT=2;
while test ${COUNT} -gt 1; do
sleep 1
COUNT=`ps -p ${XCODE_PID} |wc -l`
done
Unfortunately, as of Xcode 5.1.1 there does not exist a mechanism for auto-generating .xcodeproj or .xcworkspace build schemes from the command line as the Xcode UI does. The good news though is that an Xcode project's pbxproj markup is an order of magnitude more complex than the XML markup that describes a build scheme. If you've managed to get CMake to spin up a well-formed Xcode project on-commit, then using a very similar procedure you can build out the 100 or so lines of XML that describe the build-run-test-profile-archive actions of that Xcode project.
If you've not taken a peek at the underlying XML structure of a scheme, create a sample iOS project from the new project wizards and then go poking through the contents of the .xcodeproj or .xcworkspace file for .xcscheme files. The structure is fairly self-documenting and you might even be able to get away without actually specifying the XML markup for those actions you know that will not be run on CI.
Failing that, a less robust approach would be to looking into opening up the Xcode project/workspace file upon the completion of your CMake build process. After a handful of seconds, Xcode's indexer will have had time to identify the projects and auto-generate the schemes for the projects within the master project file itself. Obviously, as you are relying on a UI-layer operation in this approach, you are subject to Xcode's whims, and the indexer may take more than a few seconds to build its index (ex. larger projects will take longer to auto-generate schemes!) ...and there is no trigger advising command-line processes that the indexing and generation has succeeded or failed. You'd wind up having to poll for the existence of a file with an appropriate timeout which can get a bit dicey in an automated build and test environment.
I was actually able to do this by using cmake to generate the project, then using the xcode gui to make the scheme files I need. I used the terminal to extract the xcscheme files from the project and put them in another directory being tracked by source control. As part of the generation process, I just added a bit of shell script to copy the copies I made earlier into the newly generated project, then continue the build process as normal.
The latest version of cmake has added this functionality:
https://blog.kitware.com/cmake-3-9-0-rc3-is-now-ready-for-testing/
The "Xcode" generator learned to create Xcode schema files. This
is an experimental feature and can be activated by setting the
"CMAKE_XCODE_GENERATE_SCHEME" variable to a "TRUE" value.
For CMake based project use XCODE_GENERATE_SCHEME setting for your target.
Setting:
set_target_properties(<your_target> PROPERTIES
XCODE_GENERATE_SCHEME YES
)
will produce following file
your_project.xcodeproj/xcshareddata/xcschemes/your_target.xcscheme
Related
I'm looking into TeamCity and Jenkins, for a CI server.
My goal is this: every time someone commits a change to our repo, the CI server builds all targets in the project as .ipa's - ready for downloading/installing on a device.
I got Teamcity and Jenkins up and running, using a Mac mini as a build slave. That part of it is working fine.
Using Jenkins XCode plugin, I succeeded in building all targets as .ipa's.
I havent had such luck with Teamcity. The XCode plugin doesnt allow building all targets. Rather, you have to specify which targets you want to build, in each build configuration.
I approached the makers of Teamcity, and they gave me some convoluted method involving meta runners and a lot of duplication, in order to accomplish my goal.
Instead of relying on plugins, I'd rather build the .ipa's using shell scripting. However, as I'm not a script ninja, I can't figure out how to go about this.
I can figure out how to build one target via scripting, but it illudes me how make it build them all. Everytime I create a new target in the project, I don't want to have to add it at the CI server. The server should be able to automatically build all targets in the project.
...Maybe someone has a better solution? Any help is much appreciated.
What you should do (codes are bash script snippets, ready to run on OS X, you don't need to install anything except Xcode's CLI/Command Line Tools):
if you want to do this for every Xcode project file you have in your repository you should first search for these (if you have a specific Xcode project you can skip this)
for path in $(find . -type d -name '*.xcodeproj' -or -name '*.xcworkspace')
do
after this you can query all the shared (!) schemes through Xcode's command line tool
if [[ "$project" == *".xcodeproj" ]]; then
xcodebuild_output=($(xcodebuild -list -project "$project"))
else
xcodebuild_output=($(xcodebuild -list -workspace "$project"))
fi
now you have all the schemes so you can simply xcodebuild them one-by-one
Here's a bash script we developed to search for every Xcode project and every scheme configuration in a repository: https://github.com/concretebuilder/steps-cocapods-and-repository-validator/blob/master/find_schemes.sh
Note: you need to mark you schemes as shared to get xcodebuild (the command line interface of Xcode) list them.
I'm using git-svn and I'm trying to embed my revision number into my iOS app. At the moment, I have a build phase which runs the following script:
SVN_REVISION=$(git svn find-rev HEAD)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $SVN_REVISION" "$INFOPLIST_FILE"
The problem with this is that, since the repo always contains the previous revision, the script always makes my Info.plist dirty.
Is it possible for me to dynamically set my app's build number without dirtying my source tree?
1) Add a new target to your project of type "Aggregate", e.g. you may name it "Update Info.plist Prefix Header"; just use that as "Product Name" in the dialog.
2) Add a Run Script build phase to this new target with the following source code:
#!/bin/sh
SVN_REVISION=$(git svn find-rev HEAD)
echo "#define SVN_REVISION $SVN_REVISION" > "$SCRIPT_OUTPUT_FILE_0"
3) Add an output file to your script, name it
$(CONFIGURATION_TEMP_DIR)/InfoPlist.pch
4) Open the Build Phases of your iOS app.
5) Add the aggregate target you created before as dependent target (add it to "Target Dependencies"). This means Xcode will always first build this target before it will build your iOS target.
6) Open the Build Settings of your iOS app.
7) Search for the setting "Info.plist Preprocessor Prefix File" and change it to exactly the same value you used for the output file in step (3).
8) Search for the setting "Preprocess Info.plist File" and make sure it is enabled.
9) Open your current Info.plist file and change the value of CFBundleVersion to SVN_REVISION. Do not use $(SVN_REVISION) or ${SVN_REVISION}; this is no build setting or environment variable replacement, this is a preprocessor replacement, so just use SVN_REVISION.
That's it. Each time you build your iOS app, Xcode first builds the aggregate target, which updates the PCH file, and when it builds your iOS app, it will run the Info.plist file through the C pre-processor (using the PCH file as prefix header) before copying it to your application. The pre-processor will replace SVN_REVISION since it is defined as a macro in your PCH file.
Important Notes
Some people may think it is a better idea to use $(DERIVED_FILE_DIR) instead of $(CONFIGURATION_TEMP_DIR). Well, in theory they are right, yet there is just one problem in practice: The derived file dir is different for every target, while the configuration temp dir is the same (it is only different for every build configuration). When using derived file dir, the PCH file is written to the derived file dir of the aggregate target, yet when building the iOS app, Xcode will search for this file in the derived file dir of the iOS app and thus it won't find the file.
Some people may also think it is a better idea to just add the Run Script phase that updates the prefix header as the first build phase of you iOS app instead of creating a separated target for it (this would also resolve the derived file dir issue mentioned above). Again, nice idea in theory but cannot work in practice: If preprocessing is requested, the Info.plist is preprocessed before the first script phase is even executed, so if the PCH file does not exist already or has not been updated already, either the build terminates with an error or an outdated SVN revision is written to the plist file. That's why you need a separate target for this task that is guaranteed to be build before your actual target is.
Mecki thank you for the excellent answer! I applied the same concept to set a version timestamp and the current git SHA for the build.
FYI I just ran into a small issue. It seems that, at least in Xcode 5, if you specify an output file the script step uses it as a cache, so no matter the changes I made to my actual app code the script reported that it had ran but the values were not the current ones...
I had to move the output file declaration to the script itself to solve the issue, i.e. added
SCRIPT_OUTPUT_FILE_0="$CONFIGURATION_TEMP_DIR/InfoPlist.pch"
to the top of my script.
Additionally the original plist should also be touched in order for the build step to copy the new values in, so I also added
`touch $SCRIPT_INPUT_FILE_0`
after the previous output file declaration. This touch operation does not make git detect the change as commit-able.
Cheers
I'm probably missing something simple here. I am trying to auto increment my build number in XCode 4.4 only when archiving my application (in preparation for a TestFlight deployment). I have a working shell script that runs on the target and successfully updates the info.plist file for each build. My build configuration for archiving is name 'Ad-Hoc'.
Here is the script:
if [ $CONFIGURATION == Ad-Hoc ]; then
echo "Ad-Hoc build. Bumping build#..."
plist=${PROJECT_DIR}/${INFOPLIST_FILE}
buildnum=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${plist}")
if [[ "${buildnum}" == "" ]]; then
echo "No build number in $plist"
exit 2
fi
buildnum=$(expr $buildnum + 1)
/usr/libexec/Plistbuddy -c "Set CFBundleVersion $buildnum" "${plist}"
echo "Bumped build number to $buildnum"
else
echo $CONFIGURATION " build - Not bumping build number."
fi
This script updates the plist file appropriately and is reflected in XCode each time I archive. The problem is that the .ipa file that comes out of the archive process is still showing the previous build number. I have tried the following solutions with no success:
Clean before build
Clean build folder before build
Move Run Script phase to directly after the Target Dependencies step in Build Phases
Adding the script as a Run Script action in my scheme as a pre-action
No matter what I do, when I look at the build log, I see that the info.plist file is being processed as one of the very first steps. It is always prior to my script running and updating the build number, which is, I assume, why the build number is never current in the .ipa file.
Is there a way to force the Run Script phase to run before the info.plist file is processed?
in Xcode 4.4.1 I create new target and add to this target build phase "Run custom script", which update main target Plist. And also, you should add this target to dependencies for main target
The reason that this happens is that by the time your "Run Script" gets run, the XCode build process has already processed the project's plist file to extract the bundle version number, etc.
You can see this (probably in more detail that you want) by going to the Log Navigator in XCode (View/Navigators/Show Log Navigator), and selecting an "Archive" build.
A detailed list of build actions should appear in your main window, and one of the things near the top should be one called Process <projectname>-Info.plist. If you expand this using the icon at the right hand side, you can see the actual build command that was run.
The way that I got around this was to update both the original plist file, and also the processed one. By doing this, you get your updated build version in the current build rather than the next one.
Here's the script that I use to do this (this is Ruby, so you would need to put "/usr/bin/ruby" in the interpreter box to use this, but the concept works the same with a shell script or any other scripting language):
def incrementBundleVersion(file)
oldVersion = `/usr/libexec/Plistbuddy -c "print :CFBundleVersion" #{file}`.strip
components = oldVersion.split('.')
newBuild = components.pop.to_i + 1
version = components.push(newBuild).join('.')
print "Updating version: #{oldVersion} -> #{version} : #{file}\n"
system("/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion #{version}\" #{file}")
end
incrementBundleVersion("#{ENV['PROJECT_DIR']}/#{ENV['INFOPLIST_FILE']}")
incrementBundleVersion("#{ENV['CODESIGNING_FOLDER_PATH']}/Info.plist")
Note that the processed file #{ENV['CODESIGNING_FOLDER_PATH']}/Info.plist is a binary plist file, so you won't be able to process it with simple text tools - using plistbuddy is the easiest way to handle this, and it automatically works with both text and binary plist files.
Mark (et al), I believe I've run into the same problem you are facing, and i will try to describe it in one sentence and then explain:
I think /usr/libexec/PlistBuddy, when run from inside Xcode, works on cached versions of the Info.plist data, and thus what gets finally written for execution on device or simulator is not always what you want.
I had tried writing post Copy Resource Bundle "Run Scripts" in order to change this info in a way that wouldn't cause it to change within my local git repo, only to discover that, whereas the information would work properly when the PlistBuddy commands were executed in a terminal.app window beside Xcode, if not done, the cached values would get written.
I finally resigned myself to running the version-info generation scripts prior to the Copy Bundle Resources phase and just auto-committing the changes in another Run Script, using the same tags for the git message and for the git tag that get auto-created. for the Settings.bundle/Root.plist file, rather than commit this every time, i preferred to just run a finalization script that would perform a 'git checkout -- ${PROJECT}/Resources/Settings.bundle/Root.plist' (which is where mine exists, but may not be where everyone puts their own system settings resource file).
between the checking for changes, running parts of it at install and parts of it every time, and having the finalization scripts at the end, there are 6 scripts for some targets and 7 for another …
… but the important thing to me is that it's finally properly automated … and gets around whatever PlistBuddy is doing to my plist files when processed inside of Xcode.
I have an Xcode project with a large number of targets where I would like to include a settings bundle for apps built under the Ad-hoc and Debug configurations, but not under the Release configuration.
Build Phases don't seem to allow for making themselves conditional on configuration (they can obviously be conditional on target, but doubling the number of targets in the project would make it completely unusable).
That leaves writing a custom Build Rule. My plan is to exclude the Settings.bundle from all targets, and create a build rule that conditionally copies it into the product package, but applicable examples are really hard to find.
The build rule I've started has the Process setting set to "Source files with names matching:" and Settings.bundle as the name. The Using setting is "Custom script:".
My custom script is as follows (with the caveat that my bash scripting is on a cargo cult level):
if [${CONFIGURATION} = 'Debug'] then
cp -r ${INPUT_FILE_PATH} ${DERIVED_FILES_DIR}/.
fi
Finally, I have ${DERIVED_FILES_DIR}/Settings.bundle listed as an output file.
Since I'm here, it should be obvious that it's not working. My first question is whether there is somewhere I can view the output of the build rules as the execute to make sure that 1) it's actually being executed and that 2) I don't have a stupid syntax error somewhere.
Also, what's the proper location (in the form of an environment variable) to copy the output to?
I finally figured it out.
For each target for which you want to conditionally include the settings bundle, choose its Project from the source list, choose the target, and switch to the "Build Phases" tab.
Click the "Add Build Phase" button and choose "Add Run Script".
Then enter the following for the script:
if [ "${CONFIGURATION}" == "Debug" ]; then
cp -r "${PROJECT_DIR}/Settings.bundle" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app"
fi
I know this question has been answered already, and the answer was very helpful to me, but I wanted to throw my own modified solution out there as well.
My requirement was to have different settings bundles for different build configurations, rather than just not including it at release. Assuming a simplistic approach of only Debug and Release configurations, here's how to do it:
Start by adding 2 settings bundles to the project, named Settings-debug.bundle and Settings-release.bundle and then remove these files from the Copy Bundle Resources build phase. Next add a user defined build setting called SETTINGS_BUNDLE, which has different values for each configuration:
Debug ${PROJECT_DIR}/relative/path/to/Settings-debug.bundle
Release ${PROJECT_DIR}/relative/path/to/Settings-release.bundle
Next add a run-script build phase (after Copy Bundle Resources) named Copy Settings Bundle with a modified version of the script in Frank's solution.
cp -r "${SETTINGS_BUNDLE}/" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Settings.bundle"
The difference here is that the copied bundle is always named Settings.bundle regardless of the source name.
You then need to add another build phase script to prevent code signing errors when the only changes are in the settings bundles. It forces the code signing step to occur on every build. This should run before the Compile Source Files build phase. I called mine Force Codesign.
touch "${PROJECT_DIR}/relative/path/to/main.m"
For complied sources, there is a poorly documented user defined build setting that can be added. Files can be both excluded and included from compilation
Go to your target's Build Settings > Tap the + button > Add User-Defined Setting
The key is either INCLUDED_SOURCE_FILE_NAMES or EXCLUDED_SOURCE_FILE_NAMES
The value is a space separated list of file paths
See reference:
http://lists.apple.com/archives/xcode-users/2009/Jun/msg00153.html
(Tested with Xcode 9.3)
I can't find when Xcode included this feature but EXCLUDED_SOURCE_FILE_NAMES is now directly available in Build Settings > Build Options > Excluded Source File Names.
So you no longer need to create a User-Defined Setting.
See below:
It will automatically add this line in your .pbxproj.
Settings.bundle is always copied into destination area no matter whether Release or Debug configuration. So, maybe you need the following code:
if [ ${CONFIGURATION} == "Release" ]; then
rm -rf ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Settings.bundle
fi
I am no shell script expert but I think you need space between the square brackets and the condition. Also, quoting the variables may help:
if [ "${CONFIGURATION}" = "Debug" ] then
cp -r "${INPUT_FILE_PATH}" "${DERIVED_FILES_DIR}"/.
fi
As for the location, I use "$BUILT_PRODUCTS_DIR"/"$FULL_PRODUCT_NAME" for the root of my OS X app bundle.
What I did:
I have a script that
Read some configuration files to generate source code snippets
Find relevant Objective-C source files and
Replace some portions of the source code with the generated code in step 1.
and a Makefile that has a special timestamp file as a make target and the configuration files as target sources:
SRC = $(shell find ../config -iname "*.txt")
STAMP = $(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME).stamp
$(STAMP): $(SRC)
python inject.py
touch $(STAMP)
I added this Makefile as a "Run Script Build Phase" on top of the stack of build phases for the project target.
What happened:
The script build phase was run before compiling the source.
However, since the script modifies source code during its execution, I needed to build twice to get the most recent version of the build product. Here is what I imagine to be happening:
1st run: Xcode collects dependency information ---> no changes
1st run: Xcode runs "Run Script Build Phase" ---> source is changed behind Xcode's back
1st run: Xcode finishes build, thinking nothing needs to be updated
2nd run: Xcode collects dependency information ---> source has changed, needs rebuild!
2nd run: Xcode runs Run Script Build Phase" ---> everything is up-to-date
2nd run: Xcode proceeds to compilation
After reading Xcode documentation on Build Phases, I tried adding a source file which is known to be updated every time the script is run as the output of "Run Script Build Phases", but nothing changed. Since the number of configuration files may vary in my project, I don't want to specify every input and output file.
Question:
How do I make Xcode aware of source file changes made during "Run Script Build Phase"?
Edit:
Added that I placed the script build phase before the other build phases
Every technique mentioned so far is an overkill. Reproducing steve kim's comment for visibility:
In the build phases tab, simply drag the "Run Script" step to a higher location (e.g. before "Compile Sources").
Tested on Xcode 6
This solution is probably outdated. See the higher voted answer instead; I no longer actively use Xcode and am not qualified to vet a solution.
Use "External Target":
Select "Project" > "New Target..." from the menu
Select "Mac OS X" > "Other" > "External Target" and add it to your project
Open its settings and fill in your script setup
Open the "General" tab of the main target's settings and add the new target as it's direct dependency
Now the new "External Target" runs before the main target even starts gathering dependency information, so that any changes made during the script execution should be included in the build.
There is another, slightly simpler option that doesn't require a separate target, but it's only viable if your script tends to modify the same source files every time.
First, here's a brief explanation for anyone who's confused about why Xcode sometimes requires you to build twice (or do a clean build) to see certain changes reflected in your target app. Xcode compiles a source file if the object file it produces is missing, or if the object file's last-modified date is earlier than the source file's last-modified date was at the beginning of the first build phase. If your project runs a script that modifies a source file in a pre-compilation build phase, Xcode won't notice that the source file's last-modified date has changed, so it won't bother to recompile it. It's only when you build the project a second time that Xcode will notice the date change and recompile the file.
Here's a simple solution if your script happens to modify the same source files every time. Just add a Run Script build phase at the end of your build process like this:
touch Classes/FirstModifiedFile.m Classes/SecondModifiedFile.m
exit $?
Running touch on these source files at the end of your build process guarantees that they will always have a later last-modified date than their object files, so Xcode will recompile them every time.
As of Xcode 4, it looks like if you add the generated files to the output section of the build phase, it will respect that setting, and not generate the ... has been modified since the precompiled header was built error messages.
This is a good option if your script is only generating a handful of files each time.
I as well struggled with this for a long time. The answer is to use ento's "External Target" solution. He is WHY this problem occurs and how we use it in practice...
Xcode 4 build steps do not execute until AFTER the plist has been compiled. This is silly, of course, because it means that any pre-build steps that modify the plist won't take effect. But if you think about it, they actually DO take effect...on the NEXT build. That's why some people have talked about "caching" of plist values or "I have to do 2 builds to make it work." What happens is the plist is built, then your script runs. Next time you build, the plist builds using your modified files, hence the second build.
ento's solution is the one way I've found to actually do a true pre-build step. Unfortunately I also found that it does not cause the plist to update without a clean build and I fixed that. Here is how we have data-driven user values in the plist:
Add an External Build System project that points to a python script and passes some arguments
Add user-defined build settings to the build. These are the arguments that you pass to python (more on why we do this later)
The python script reads some input JSON files and builds a plist preprocessor header file AND touches the main app plist
The main project has "preprocess plist files" turned on and points to this preprocessor file
Using touch on the main app plist file causes the main target to generate the plist every time. The reason we pass in build settings as parameters is so our command-line build can override settings:
Add a user-defined variable "foo" to the prebuild project.
In your prebuild you can use $(foo) to pass the value into the python script.
On the command-line you can add foo=test to pass in a new value.
The python script uses base settings files and allows for user-defined settings files to override the defaults. You make a change and immediately it ends up in the plist. We only use this for settings that MUST be in the plist. For anything else it's a waste of effort....generate a json file or something similar instead and load it at run-time :)
I hope this helps...it's been a couple rough days figuring this out.
The External Target solution from #ento no longer works as of Xcode 11.5. The solution is to add all files that will be changed under Output Files in the Run Script.
Another option is to create a subproject framework with your scripts and just add it as a dependency to all targets. The phase scripts of this subproject should now be executed before all targets.