Skip to content

Instantly share code, notes, and snippets.

@acrookston
Last active January 26, 2022 11:05
Show Gist options
  • Save acrookston/55d69a16cd5363426dbf7a3d6a9ee6ce to your computer and use it in GitHub Desktop.
Save acrookston/55d69a16cd5363426dbf7a3d6a9ee6ce to your computer and use it in GitHub Desktop.
Xcode pre-action to build custom Info.plist

Automatic build versions from git in Xcode (and other goodies)

Installation procedure for pre-build actions to automatically populate Xcode Info.plist with dynamic data.

1. Xcode Scheme pre-action

Edit Xcode Scheme and add a pre-action script. Copy the contents of preaction.sh into the pre-action script box.

2. Copy processing file

  1. Copy build_environment.sh to config/environments/build_environment.sh.
  2. Modify it's variables or path to environment_preprocess.h as needed.

3. Edit Xcode Build Settings

  1. For the Project Build Settings add a new User-Defined variables called ENVIRONMENT, for each scheme set the appropriate name eg development or production.
  2. For each Target Build Settings set Preprocess Info.plist file to YES.
  3. Enter environment_preprocess.h (or the output file if you renamed it build_environment.sh) into Info.plist Preprocessor Prefix File

4. Install plist file

Copy development.plist file to config/environments/development.plist. DO NOT include this file in any Target Memberships.

If you have several schemes setup in the previous step (ENVIORMENT) you need to copy this file to each option if you use multiple build schemes and ENVIRONMENT variables.

5. Setup environment variables

This file development.plist (and others) is used to populate data in the build_environment.sh script. Modify contents as needed and don't forget to also modify the build_environment.sh accordingly.

This is really where the magic happens. The rest is just for running this script and processing the output.

TIP 1: You can run pretty much anything in the environment_preprocess.h script if you want to setup link to a local development server you can use the hostname command to get your current build computer's network name. TIP 2: You can use it to use git commit count as the build version/number.

(Both of these are included in the script already.)

6. Using environment variables

  1. Use those generated environment variables in your Info.plist.

7. Run Build!

This will generate the environment_preprocess.h file and make it available for inclusion in your Info.plist It's advisable to exclude the generated file from any version control system.

#!/bin/sh
# build_environment.sh
#
# Created by Andrew C on 7/27/15.
# Released under MIT LICENSE
# Copyright (c) 2015 Andrew Crookston.
cd "${PROJECT_DIR}"
dir=${PROJECT_DIR}/config/environments
echo "Starting environment ${ENVIRONMENT} configuration for ${PROJECT_NAME}/${PROJECT_FOLDER} with config in " $dir
echo "Host: " `hostname`
# environment variable from value passed in to xcodebuild.
# if not specified, we default to DEV
env=${ENVIRONMENT}
if [ -z "$env" ]
then
env="development"
fi
echo "Using $env environment"
# copy the environment-specific file
cp $dir/$env.plist $dir/environment.plist
# Date and time that we are running this build
buildDate=`date "+%F %H:%M:%S"`
todaysDay=`date "+%d"`
# app settings
appName=`/usr/libexec/PlistBuddy -c "Print :AppName" "$dir/environment.plist"`
appGroup=`/usr/libexec/PlistBuddy -c "Print :AppGroup" "$dir/environment.plist"`
version=`/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$dir/environment.plist"`
hostname=`/usr/libexec/PlistBuddy -c "Print :HostName" "$dir/environment.plist"`
hostscheme=`/usr/libexec/PlistBuddy -c "Print :HostScheme" "$dir/environment.plist"`
bundle=`git rev-list master |wc -l | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'`
if [ "$env" == "development" ]
then
hostname=`hostname`
# need to dynamically append today's day for non-prod
appName="$appName-$todaysDay"
# Last hash from the current branch
#version=`git log --pretty=format:"%h" -1`
fi
buildhost=`hostname`
# Build the preprocess file
cd "${PROJECT_DIR}"
preprocessFile="environment_preprocess.h"
echo "Creating header file ${PROJECT_DIR}/${preprocessFile}"
echo "//------------------------------------------" > $preprocessFile
echo "// Auto generated file. Don't edit manually." >> $preprocessFile
echo "// See build_environment script for details." >> $preprocessFile
echo "// Created $buildDate" >> $preprocessFile
echo "//------------------------------------------" >> $preprocessFile
echo "" >> $preprocessFile
echo "#define ENV $env" >> $preprocessFile
echo "#define ENV_APP_NAME $appName" >> $preprocessFile
echo "#define ENV_APP_GROUP $appGroup" >> $preprocessFile
echo "#define ENV_APP_VERSION $version" >> $preprocessFile
echo "#define ENV_HOST_NAME $hostname" >> $preprocessFile
echo "#define ENV_BUILD_HOST $buildhost" >> $preprocessFile
echo "#define ENV_BUNDLE_VERSION $bundle" >> $preprocessFile
echo "#define ENV_HOST_SCHEME $hostscheme" >> $preprocessFile
# dump out file to build log
cat $preprocessFile
# Force the system to process the plist file
echo "Touching all plists at: ${PROJECT_DIR}/**/Info.plist"
touch ${PROJECT_DIR}/**/Info.plist
# done
echo "Done."
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>README</key>
<string>Copy this file to config/environments/development.plist. DO NOT add it to the targets! It gets copied to environment.plist during build. See build_environment script for more.</string>
<key>AppGroup</key>
<string>your.app.group</string>
<key>HostName</key>
<string>localhost</string>
<key>AppName</key>
<string>Your app name</string>
<key>CFBundleShortVersionString</key>
<string>1.1</string>
<key>RandomKey</key>
<string>whatever you want</string>
</dict>
</plist>
#!/bin/sh
cd $PROJECT_DIR
exec > $PROJECT_DIR/pre_action.log 2>&1
source $PROJECT_DIR/config/environments/build_environment.sh
@lolgear
Copy link

lolgear commented Oct 3, 2017

@acrookston
Hi!
I use similar technique in my project.

Do you use one workspace with several xcodeproj ( several frameworks and apps )?
I have issue with it where Framework could not be found in case of wrong paths.
( Custom build configuration broke everything. Frameworks could not be embedded well into app ).
It happens because app has custom build configurations ( PublicBeta, for example ), but frameworks only have standard configurations as Debug and Release.

I hope you solve this somehow.

Thanks!

@lolgear
Copy link

lolgear commented Oct 3, 2017

@acrookston

I look at your example.
You use info.plist extensively.
However, some variables could be copied/located in different plist, for example, DevelopmentVariables.plist.
In this case you don't need to corrupt fragile structure of Info.plist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment