-
-
Save jellybeansoup/db7b24fb4c7ed44030f4 to your computer and use it in GitHub Desktop.
| #!/bin/bash | |
| # Link: <https://gist.github.com/jellybeansoup/db7b24fb4c7ed44030f4> | |
| # | |
| # A command-line script for incrementing build numbers for all known targets in an Xcode project. | |
| # | |
| # This script has two main goals: firstly, to ensure that all the targets in a project have the | |
| # same CFBundleVersion and CFBundleShortVersionString values. This is because mismatched values | |
| # can cause a warning when submitting to the App Store. Secondly, to ensure that the build number | |
| # is incremented appropriately when git has changes. | |
| # | |
| # If not using git, you are a braver soul than I. | |
| ## | |
| # The xcodeproj. This is usually found by the script, but you may need to specify its location | |
| # if it's not in the same folder as the script is called from (the project root if called as a | |
| # build phase run script). | |
| # | |
| # This value can also be provided (or overridden) using "--xcodeproj=<path>" | |
| # | |
| #xcodeproj="Project.xcodeproj" | |
| ## | |
| # We have to define an Info.plist as the source of truth. This is typically the one for the main | |
| # target. If not set, the script will try to guess the correct file from the list it gathers from | |
| # the xcodeproj file, but this can be overriden by setting the path here. | |
| # | |
| # This value can also be provided (or overridden) using "--plist=<path>" | |
| # | |
| #plist="Project/Info.plist" | |
| ## | |
| # By default, the script ensures that the build number is incremented when changes are declared | |
| # based on git's records. Alternatively the number of commits on the current branch can be used | |
| # by toggling the "reflect_commits" variable to true. If not on "master", the current branch name | |
| # will be used to ensure no version collisions across branches, i.e. "497-develop". | |
| # | |
| # This setting can also be enabled using "--reflect-commits" | |
| # | |
| #reflect_commits=true | |
| ## | |
| # If you would like to iterate the build number only when a specific branch is checked out | |
| # (i.e. "master"), you can specify the branch name. The current version will still be replicated | |
| # across all Info.plist files (to ensure consistency) if they don't match the source of truth. | |
| # | |
| # This setting can be enabled for multiple branches can be enabled by using comma separated names | |
| # (i.e. "master,develop"). No spacing is permitted. | |
| # | |
| # This setting can also be enabled using "--branch" | |
| # | |
| #enable_for_branch="master" | |
| ## | |
| # Released under the BSD License | |
| # | |
| # Copyright © 2017 Daniel Farrelly | |
| # | |
| # Redistribution and use in source and binary forms, with or without modification, | |
| # are permitted provided that the following conditions are met: | |
| # | |
| # * Redistributions of source code must retain the above copyright notice, this list | |
| # of conditions and the following disclaimer. | |
| # * Redistributions in binary form must reproduce the above copyright notice, this | |
| # list of conditions and the following disclaimer in the documentation and/or | |
| # other materials provided with the distribution. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
| # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |
| # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
| # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
| # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |
| # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
| # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| # We use PlistBuddy to handle the Info.plist values. Here we define where it lives. | |
| plistBuddy="/usr/libexec/PlistBuddy" | |
| # Parse input variables and update settings. | |
| for i in "$@"; do | |
| case $i in | |
| -h|--help) | |
| echo "usage: sh version-update.sh [options...]\n" | |
| echo "Options: (when provided via the CLI, these will override options set within the script itself)" | |
| echo "-b, --branch=<name[,name...]> Only allow the script to run on the branch with the given name(s)." | |
| echo " --build=<number> Apply the given value to the build number (CFBundleVersion) for the project." | |
| echo "-i, --ignore-changes Ignore git status when iterating build number (doesn't apply to manual values or --reflect-commits)." | |
| echo "-p, --plist=<path> Use the specified plist file as the source of truth for version details." | |
| echo " --reflect-commits Reflect the number of commits in the current branch when preparing build numbers." | |
| echo " --version=<number> Apply the given value to the marketing version (CFBundleShortVersionString) for the project." | |
| echo "-x, --xcodeproj=<path> Use the specified Xcode project file to gather plist names." | |
| echo "\nFor more detailed information on the use of these variables, see the script source." | |
| exit 1 | |
| ;; | |
| --reflect-commits) | |
| reflect_commits=true | |
| shift | |
| ;; | |
| -x=*|--xcodeproj=*) | |
| xcodeproj="${i#*=}" | |
| shift | |
| ;; | |
| -p=*|--plist=*) | |
| plist="${i#*=}" | |
| shift | |
| ;; | |
| -b=*|--branch=*) | |
| enable_for_branch="${i#*=}" | |
| shift | |
| ;; | |
| --build=*) | |
| specified_build="${i#*=}" | |
| shift | |
| ;; | |
| --version=*) | |
| specified_version="${i#*=}" | |
| shift | |
| ;; | |
| -i|--ignore-changes) | |
| ignore_git_status=true | |
| shift | |
| ;; | |
| *) | |
| ;; | |
| esac | |
| done | |
| # Locate the xcodeproj. | |
| # If we've specified a xcodeproj above, we'll simply use that instead. | |
| if [[ -z ${xcodeproj} ]]; then | |
| xcodeproj=$(find . -depth 1 -name "*.xcodeproj" | sed -e 's/^\.\///g') | |
| fi | |
| # Check that the xcodeproj file we've located is valid, and warn if it isn't. | |
| # This could also indicate an issue with the code used to automatically locate the xcodeproj file. | |
| # If you're encountering this and the file exists, ensure that ${xcodeproj} contains the correct | |
| # path, or use the "--xcodeproj" variable to provide an accurate location. | |
| if [[ ! -f "${xcodeproj}/project.pbxproj" ]]; then | |
| echo "${BASH_SOURCE}:${LINENO}: error: Could not locate the xcodeproj file \"${xcodeproj}\"." | |
| exit 1 | |
| else | |
| echo "Xcode Project: \"${xcodeproj}\"" | |
| fi | |
| # Find unique references to Info.plist files in the project | |
| projectFile="${xcodeproj}/project.pbxproj" | |
| plists=$(grep "^\s*INFOPLIST_FILE.*$" "${projectFile}" | sed -Ee 's/^[[:space:]]+INFOPLIST_FILE[[:space:]*=[[:space:]]*["]?([^"]+)["]?;$/\1/g' | sort | uniq) | |
| # Attempt to guess the plist based on the list we have. | |
| # If we've specified a plist above, we'll simply use that instead. | |
| if [[ -z ${plist} ]]; then | |
| read -r plist <<< "${plists}" | |
| fi | |
| # Check that the plist file we've located is valid, and warn if it isn't. | |
| # This could also indicate an issue with the code used to match plist files in the xcodeproj file. | |
| # If you're encountering this and the file exists, ensure that ${plists} contains _ONLY_ filenames. | |
| if [[ ! -f ${plist} ]]; then | |
| echo "${BASH_SOURCE}:${LINENO}: error: Could not locate the plist file \"${plist}\"." | |
| exit 1 | |
| else | |
| echo "Source Info.plist: \"${plist}\"" | |
| fi | |
| # Find the current build number in the main Info.plist | |
| mainBundleVersion=$("${plistBuddy}" -c "Print CFBundleVersion" "${plist}") | |
| mainBundleShortVersionString=$("${plistBuddy}" -c "Print CFBundleShortVersionString" "${plist}") | |
| echo "Current project version is ${mainBundleShortVersionString} (${mainBundleVersion})." | |
| # If the user specified a marketing version (via "--version"), we overwrite the version from the source of truth. | |
| if [[ ! -z ${specified_version} ]]; then | |
| mainBundleShortVersionString=${specified_version} | |
| echo "Applying specified marketing version (${specified_version})..." | |
| fi | |
| # Increment the build number if git says things have changed. Note that we also check the main | |
| # Info.plist file, and if it has already been modified, we don't increment the build number. | |
| # Alternatively, if the script has been called using "--reflect-commits", we just update to the | |
| # current number of commits. We can also specify a build number to use with "--build". | |
| git=$(sh /etc/profile; which git) | |
| branchName=$("${git}" rev-parse --abbrev-ref HEAD) | |
| if [[ -z ${enable_for_branch} ]] || [[ ",${enable_for_branch}," == *",${branchName},"* ]]; then | |
| if [[ ! -z ${specified_build} ]]; then | |
| mainBundleVersion=${specified_build} | |
| echo "Applying specified build number (${specified_build})..." | |
| elif [[ ! -z ${reflect_commits} ]] && [[ ${reflect_commits} ]]; then | |
| currentBundleVersion=${mainBundleVersion} | |
| mainBundleVersion=$("${git}" rev-list --count HEAD) | |
| if [[ ${branchName} != "master" ]]; then | |
| mainBundleVersion="${mainBundleVersion}-${branchName}" | |
| fi | |
| if [[ ${currentBundleVersion} != ${mainBundleVersion} ]]; then | |
| echo "Branch \"${branchName}\" has ${mainBundleVersion} commit(s). Updating build number..." | |
| else | |
| echo "Branch \"${branchName}\" has ${mainBundleVersion} commit(s). Version is stable." | |
| fi | |
| elif [[ ! -z ${ignore_git_status} ]] && [[ ${ignore_git_status} ]]; then | |
| echo "Iterating build number (forced)..." | |
| mainBundleVersion=$((${mainBundleVersion} + 1)) | |
| else | |
| status=$("${git}" status --porcelain) | |
| if [[ ${#status} == 0 ]]; then | |
| echo "Repository does not have any changes. Version is stable." | |
| elif [[ ${status} == *"M ${plist}"* ]] || [[ ${status} == *"M \"${plist}\""* ]]; then | |
| echo "The source Info.plist has been modified. Version is assumed to be stable. Use --ignore-changes to override." | |
| else | |
| echo "Repository is dirty. Iterating build number..." | |
| mainBundleVersion=$((${mainBundleVersion} + 1)) | |
| fi | |
| fi | |
| else | |
| echo "${xcodeproj}:0: warning: Version number updates are disabled for the current git branch (${branchName})." | |
| fi | |
| # Update all of the Info.plist files we discovered | |
| while read -r thisPlist; do | |
| # Find out the current version | |
| thisBundleVersion=$("${plistBuddy}" -c "Print CFBundleVersion" "${thisPlist}") | |
| thisBundleShortVersionString=$("${plistBuddy}" -c "Print CFBundleShortVersionString" "${thisPlist}") | |
| # Update the CFBundleVersion if needed | |
| if [[ ${thisBundleVersion} != ${mainBundleVersion} ]]; then | |
| echo "Updating \"${thisPlist}\" with build ${mainBundleVersion}..." | |
| "${plistBuddy}" -c "Set :CFBundleVersion ${mainBundleVersion}" "${thisPlist}" | |
| fi | |
| # Update the CFBundleShortVersionString if needed | |
| if [[ ${thisBundleShortVersionString} != ${mainBundleShortVersionString} ]]; then | |
| echo "Updating \"${thisPlist}\" with marketing version ${mainBundleShortVersionString}..." | |
| "${plistBuddy}" -c "Set :CFBundleShortVersionString ${mainBundleShortVersionString}" "${thisPlist}" | |
| fi | |
| done <<< "${plists}" |
Does not do anything for me. Whenever I run ./update-version.sh or ./update-version.sh --reflect-commits in the xcodeproj directory I only get the output
Xcode Project: "xyz.xcodeproj"
Source Info.plist: "xyz/Info.plist"
Current project version is 0.1.2 (125).
@kamalmarhubi i typically use the 2-clause bsd license, but i honestly don't care much as long as you're not distributing the source. for the purposes of inclusion in OSS projects, i've added the license to the file.
@rph8 the --reflect-commits uses git rev-list --count HEAD to determine the number of commits to the current branch. without it the script will determine changes based on git status --porcelain and will NOT run unless there ARE changes and the source info.plist file has NOT been modified. i've updated the script to include an --ignore-changes flag that will ALWAYS iterate the build number (unless --reflect-commits or --build is specified, or if on an ignored branch). use it at your own peril.
is there a way to update only the main bundle when compiling in Debug, then update all plists when compiling Release or Archive?
I have a project with several targets, and would like to update only the main plist, otherwise every commit shows several files modified for only a small change. The script seems to differentiate a main plist, but I don't understand it fully to modify it.
Thanks for any info
I've solved it :D
but i honestly don't care much
Probably you should consider to move to my favorite license – WTFPL. Using it for all my personal projects.
p.s. thanks a lot for the script!
@jellybeansoup Sorry for noob question. This script can be used from Archive Pre-Actions?
@inekipelov Theoretically, yes. I've only ever run it outside of Xcode, or as a Run Script Build Phase, so YMMV.
@jellybeansoup could you add a license to this script? It's really useful, but I want to know if I can use it in my project! :-)