diff --git a/script/README.md b/script/README.md index c886acb..f66206f 100644 --- a/script/README.md +++ b/script/README.md @@ -1,6 +1,66 @@ -These scripts are primarily meant to support the use of -[Janky](https://github.com/github/janky). To use them, read the contents of this -repository into a `script` folder: +# objc-build-scripts + +This project is a collection of scripts created with two goals: + + 1. To standardize how Objective-C projects are bootstrapped after cloning + 1. To easily build Objective-C projects on continuous integration servers + +## Scripts + +Right now, there are two important scripts: [`bootstrap`](#bootstrap) and +[`cibuild`](#cibuild). Both are Bash scripts, to maximize compatibility and +eliminate pesky system configuration issues (like setting up a working Ruby +environment). + +The structure of the scripts on disk is meant to follow that of a typical Ruby +project: + +``` +script/ + bootstrap + cibuild +``` + +### bootstrap + +This script is responsible for bootstrapping (initializing) your project after +it's been checked out. Here, you should install or clone any dependencies that +are required for a working build and development environment. + +By default, the script will verify that [xctool][] is installed, then initialize +and update submodules recursively. If any submodules contain `script/bootstrap`, +that will be run as well. + +To check that other tools are installed, you can set the `REQUIRED_TOOLS` +environment variable before running `script/bootstrap`, or edit it within the +script directly. Note that no installation is performed automatically, though +this can always be added within your specific project. + +### cibuild + +This script is responsible for building the project, as you would want it built +for continuous integration. This is preferable to putting the logic on the CI +server itself, since it ensures that any changes are versioned along with the +source. + +By default, the script will run [`bootstrap`](#bootstrap), look for any Xcode +workspace or project in the working directory, then build all targets/schemes +(as found by `xcodebuild -list`) using [xctool][]. + +You can also specify the schemes to build by passing them into the script: + +```sh +script/cibuild ReactiveCocoa-Mac ReactiveCocoa-iOS +``` + +As with the `bootstrap` script, there are several environment variables that can +be used to customize behavior. They can be set on the command line before +invoking the script, or the defaults changed within the script directly. + +## Getting Started + +To add the scripts to your project, read the contents of this repository into +a `script` folder: ``` $ git remote add objc-build-scripts https://github.com/jspahrsummers/objc-build-scripts.git @@ -8,13 +68,15 @@ $ git fetch objc-build-scripts $ git read-tree --prefix=script/ -u objc-build-scripts/master ``` -Then commit the changes to incorporate the scripts into your own repository's +Then commit the changes, to incorporate the scripts into your own repository's history. You can also freely tweak the scripts for your specific project's needs. -To bring in upstream changes later: +To merge in upstream changes later: ``` $ git fetch -p objc-build-scripts -$ git merge -Xsubtree=script objc-build-scripts/master +$ git merge --ff --squash -Xsubtree=script objc-build-scripts/master ``` + +[xctool]: https://github.com/facebook/xctool diff --git a/script/bootstrap b/script/bootstrap index 46309bd..f840318 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -1,11 +1,73 @@ #!/bin/bash -SCRIPT_DIR=$(dirname "$0") -cd "$SCRIPT_DIR/.." +export SCRIPT_DIR=$(dirname "$0") -set -o errexit +## +## Configuration Variables +## -echo "*** Updating submodules..." -git submodule sync --quiet -git submodule update --init -git submodule foreach --recursive --quiet "git submodule sync --quiet && git submodule update --init" +config () +{ + # A whitespace-separated list of executables that must be present and locatable. + : ${REQUIRED_TOOLS="xctool"} + + export REQUIRED_TOOLS +} + +## +## Bootstrap Process +## + +main () +{ + config + + if [ -n "$REQUIRED_TOOLS" ] + then + echo "*** Checking dependencies..." + check_deps + fi + + local submodules=$(git submodule status 2>/dev/null) + if [ -n "$submodules" ] + then + echo "*** Updating submodules..." + update_submodules + fi +} + +check_deps () +{ + for tool in $REQUIRED_TOOLS + do + which -s "$tool" + if [ "$?" -ne "0" ] + then + echo "*** Error: $tool not found. Please install it and bootstrap again." + exit 1 + fi + done +} + +bootstrap_submodule () +{ + local bootstrap="script/bootstrap" + + if [ -e "$bootstrap" ] + then + echo "*** Bootstrapping $name..." + "$bootstrap" >/dev/null + else + update_submodules + fi +} + +update_submodules () +{ + git submodule sync --quiet && git submodule update --init && git submodule foreach --quiet bootstrap_submodule +} + +export -f bootstrap_submodule +export -f update_submodules + +main diff --git a/script/cibuild b/script/cibuild index 7d02112..e9aa65a 100755 --- a/script/cibuild +++ b/script/cibuild @@ -1,114 +1,142 @@ #!/bin/bash -SCRIPT_DIR=$(dirname "$0") -cd "$SCRIPT_DIR/.." +export SCRIPT_DIR=$(dirname "$0") ## ## Configuration Variables ## -# The build configuration to use. -if [ -z "$XCCONFIGURATION" ] -then - XCCONFIGURATION="Release" -fi +SCHEMES="$@" -# The workspace to build. -# -# If not set and no workspace is found, the -workspace flag will not be passed -# to xcodebuild. -if [ -z "$XCWORKSPACE" ] -then - XCWORKSPACE=$(ls -d *.xcworkspace 2>/dev/null | head -n 1) -fi +config () +{ + # The workspace to build. + # + # If not set and no workspace is found, the -workspace flag will not be passed + # to `xctool`. + # + # Only one of `XCWORKSPACE` and `XCODEPROJ` needs to be set. The former will + # take precedence. + : ${XCWORKSPACE=$(find_pattern "*.xcworkspace")} -# A bootstrap script to run before building. -# -# If this file does not exist, it is not considered an error. -BOOTSTRAP="$SCRIPT_DIR/bootstrap" + # The project to build. + # + # If not set and no project is found, the -project flag will not be passed + # to `xctool`. + # + # Only one of `XCWORKSPACE` and `XCODEPROJ` needs to be set. The former will + # take precedence. + : ${XCODEPROJ=$(find_pattern "*.xcodeproj")} -# A whitespace-separated list of default targets or schemes to build, if none -# are specified on the command line. -# -# Individual names can be quoted to avoid word splitting. -DEFAULT_TARGETS= + # A bootstrap script to run before building. + # + # If this file does not exist, it is not considered an error. + : ${BOOTSTRAP="$SCRIPT_DIR/bootstrap"} -# Extra build settings to pass to xcodebuild. -XCODEBUILD_SETTINGS="TEST_AFTER_BUILD=YES" + # Extra options to pass to xctool. + : ${XCTOOL_OPTIONS="RUN_CLANG_STATIC_ANALYZER=NO"} + + # A whitespace-separated list of default schemes to build. + # + # Individual names can be quoted to avoid word splitting. + : ${SCHEMES:=$(xcodebuild -list 2>/dev/null | awk -f "$SCRIPT_DIR/schemes.awk")} + + export XCWORKSPACE + export XCODEPROJ + export BOOTSTRAP + export XCTOOL_OPTIONS + export SCHEMES +} ## ## Build Process ## -if [ -z "$*" ] -then - # lol recursive shell script - if [ -n "$DEFAULT_TARGETS" ] +main () +{ + config + + if [ -f "$BOOTSTRAP" ] then - echo "$DEFAULT_TARGETS" | xargs "$SCRIPT_DIR/cibuild" - else - xcodebuild -list | awk -f "$SCRIPT_DIR/targets.awk" | xargs "$SCRIPT_DIR/cibuild" + echo "*** Bootstrapping..." + "$BOOTSTRAP" || exit $? fi - exit $? -fi + echo "*** The following schemes will be built:" + echo "$SCHEMES" | xargs -n 1 echo " " + echo -if [ -f "$BOOTSTRAP" ] -then - echo "*** Bootstrapping..." - bash "$BOOTSTRAP" || exit $? -fi + echo "$SCHEMES" | xargs -n 1 | ( + local status=0 -echo "*** The following targets will be built:" + while read scheme + do + build_scheme "$scheme" || status=1 + done -for target in "$@" -do - echo "$target" -done + exit $status + ) +} -echo "*** Cleaning all targets..." -xcodebuild -alltargets clean OBJROOT="$PWD/build" SYMROOT="$PWD/build" $XCODEBUILD_SETTINGS - -run_xcodebuild () +find_pattern () { - local scheme=$1 + ls -d $1 2>/dev/null | head -n 1 +} +run_xctool () +{ if [ -n "$XCWORKSPACE" ] then - xcodebuild -workspace "$XCWORKSPACE" -scheme "$scheme" -configuration "$XCCONFIGURATION" build OBJROOT="$PWD/build" SYMROOT="$PWD/build" $XCODEBUILD_SETTINGS + xctool -workspace "$XCWORKSPACE" $XCTOOL_OPTIONS "$@" 2>&1 + elif [ -n "$XCODEPROJ" ] + then + xctool -project "$XCODEPROJ" $XCTOOL_OPTIONS "$@" 2>&1 else - xcodebuild -scheme "$scheme" -configuration "$XCCONFIGURATION" build OBJROOT="$PWD/build" SYMROOT="$PWD/build" $XCODEBUILD_SETTINGS + echo "*** No workspace or project file found." + exit 1 fi +} - local status=$? - - return $status +parse_build () +{ + awk -f "$SCRIPT_DIR/xctool.awk" 2>&1 >/dev/null } build_scheme () { local scheme=$1 - run_xcodebuild "$scheme" 2>&1 | awk -f "$SCRIPT_DIR/xcodebuild.awk" + echo "*** Cleaning $scheme..." + run_xctool -scheme "$scheme" clean >/dev/null || exit $? + + echo "*** Building and testing $scheme..." + echo + + local sdkflag= + local action=test + + # Determine whether we can run unit tests for this target. + run_xctool -scheme "$scheme" run-tests | parse_build local awkstatus=$? - local xcstatus=${PIPESTATUS[0]} - if [ "$xcstatus" -eq "65" ] + if [ "$awkstatus" -ne "0" ] then - # This probably means that there's no scheme by that name. Give up. - echo "*** Error building scheme $scheme -- perhaps it doesn't exist" - elif [ "$awkstatus" -eq "1" ] - then - return $awkstatus + # Unit tests aren't supported. + action=build fi - return $xcstatus + if [ "$awkstatus" -eq "1" ] + then + # Build for iOS. + sdkflag="-sdk iphonesimulator" + fi + + run_xctool $sdkflag -scheme "$scheme" $action } -echo "*** Building..." +export -f build_scheme +export -f run_xctool +export -f parse_build -for scheme in "$@" -do - build_scheme "$scheme" || exit $? -done +main diff --git a/script/schemes.awk b/script/schemes.awk new file mode 100644 index 0000000..d101b4f --- /dev/null +++ b/script/schemes.awk @@ -0,0 +1,12 @@ +BEGIN { + FS = "\n"; +} + +/Targets:/ { + while (getline && $0 != "") { + if ($0 ~ /Test/) continue; + + sub(/^ +/, ""); + print "'" $0 "'"; + } +} diff --git a/script/xctool.awk b/script/xctool.awk new file mode 100644 index 0000000..f613258 --- /dev/null +++ b/script/xctool.awk @@ -0,0 +1,25 @@ +# Exit statuses: +# +# 0 - No errors found. +# 1 - Wrong SDK. Retry with SDK `iphonesimulator`. +# 2 - Missing target. + +BEGIN { + status = 0; +} + +{ + print; +} + +/Testing with the '(.+)' SDK is not yet supported/ { + status = 1; +} + +/does not contain a target named/ { + status = 2; +} + +END { + exit status; +}