diff --git a/.gitignore b/.gitignore index e27d315..18d371b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ DerivedData/ *.ipa *.dSYM.zip *.dSYM +Archive/* +Product/* ## Playgrounds timeline.xctimeline diff --git a/README.md b/README.md index b38ea7b..581e8da 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,42 @@ Notable design decisions are recorded in [DECISIONS.md](./DECISIONS.md). The App [`xcode-install`](https://github.com/xcpretty/xcode-install) and [fastlane/spaceship](https://github.com/fastlane/fastlane/tree/master/spaceship) both deserve credit for figuring out the hard parts of what makes this possible. +## Releasing a new version + +Follow the steps below to build and release a new version of Xcodes.app. For any of the git steps, you can use your preferred tool, but please sign the tag. + +```sh +# Update the version number in Xcode and commit the change, if necessary + +# Increment the build number +scripts/increment_build_number.sh + +# Commit the change +git add Xcodes/Resources/Info.plist +git commit -asm "Increment build number" + +# Tag the latest commit +# Replace $VERSION and $BUILD below with the latest real values +git tag -asm "v$VERSION.b$BUILD" "v$VERSION.b$BUILD" + +# Push to origin +git push --follow-tags + +# Build the app +scripts/package_release.sh + +# Notarize the app +scripts/notarize.sh "test@example.com" "@keychain:altool" MyOrg Product/Xcodes.zip + +# Go to https://github.com/RobotsAndPencils/XcodesApp/releases +# Edit the latest draft release +# Set its tag to the tag you just pushed +# Set its title to a string with the format "$VERSION ($BUILD)" +# Polish the draft release notes, if necessary +# Attach the zip that was created in the Product directory to the release +# Publish the release +``` + ## Contact diff --git a/Scripts/export_options.plist b/Scripts/export_options.plist new file mode 100644 index 0000000..055f67c --- /dev/null +++ b/Scripts/export_options.plist @@ -0,0 +1,8 @@ + + + + + method + developer-id + + diff --git a/Scripts/increment_build_number.sh b/Scripts/increment_build_number.sh new file mode 100755 index 0000000..f8ac44e --- /dev/null +++ b/Scripts/increment_build_number.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# +# Increment build number +# +# This will get the latest build number from git tags, add 1, then set it in the Info.plist. +# Assumes that build numbers are monotonically increasing positive integers, across version numbers. +# Tags must be named v$version_numberb$build_number, e.g. v1.2.3b456 + +infoplist_file="$(pwd)/Xcodes/Resources/Info.plist" + +# Get latest tag hash matching pattern +hash=$(git rev-list --tags="v" --max-count=1) +# Get latest tag at hash that matches the same pattern as a prefix in order to support commits with multiple tags +last_tag=$(git describe --tags --match "v*" "$hash") +# Get build number from last component of tag name +last_build_number=$(echo "$last_tag" | grep -o "b.*" | cut -c 2-) + +build_number=$(($last_build_number + 1)) + +/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $build_number" "${infoplist_file}" diff --git a/Scripts/notarize.sh b/Scripts/notarize.sh new file mode 100755 index 0000000..89de6d4 --- /dev/null +++ b/Scripts/notarize.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# +# Notarize +# +# Uploads to Apple's notarization service, polls until it completes, staples the ticket to the built app, then creates a new zip. +# +# Requires four arguments: +# - Apple ID username +# - Apple ID app-specific password (store this in your Keychain and use the @keychain:$NAME syntax to prevent your password from being added to your shell history) +# - App Store Connect provider name +# - Path to .app to upload +# +# Assumes that there's a .app beside the .zip with the same name so it can be stapled and re-zipped. +# +# E.g. notarize.sh "test@example.com" "@keychain:altool" MyOrg Xcodes.zip +# +# https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow +# Adapted from https://github.com/keybase/client/blob/46f5df0aa64ff19198ba7b044bbb7cd907c0be9f/packaging/desktop/package_darwin.sh + +username="$1" +password="$2" +asc_provider="$3" +file="$4" + +echo "Uploading to notarization service" + +uuid=$(xcrun altool \ + --notarize-app \ + --primary-bundle-id "com.robotsandpencils.XcodesApp.zip" \ + --username "$username" \ + --password "$password" \ + --asc-provider "$asc_provider" \ + --file "$file" 2>&1 | \ + grep 'RequestUUID' | \ + awk '{ print $3 }') + +echo "Successfully uploaded to notarization service, polling for result: $uuid" + +sleep 15 + while : + do + fullstatus=$(xcrun altool \ + --notarization-info "$uuid" \ + --username "$username" \ + --password "$password" \ + --asc-provider "$asc_provider" 2>&1) + status=$(echo "$fullstatus" | grep 'Status\:' | awk '{ print $2 }') + if [ "$status" = "success" ]; then + echo "Notarization success" + exit 0 + elif [ "$status" = "in" ]; then + echo "Notarization still in progress, sleeping for 15 seconds and trying again" + sleep 15 + else + echo "Notarization failed, full status below" + echo "$fullstatus" + exit 1 + fi + done + +# Remove .zip +rm $file + +# Staple ticket to .app +app_path="$(basename -s ".zip" "$file").app" +xcrun stapler staple "$app_path" + +# Zip the stapled app for distribution +zip -r "$file" "$app_path" diff --git a/Scripts/package_release.sh b/Scripts/package_release.sh new file mode 100755 index 0000000..d4fbb7d --- /dev/null +++ b/Scripts/package_release.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Package release +# +# This will build and archive the app and then compress it in a .zip file at Product/Xcodes.zip +# You must already have all required code signing assets installed on your computer + +PROJECT_NAME=Xcodes +PROJECT_DIR=$(pwd)/$PROJECT_NAME/Resources +SCRIPTS_DIR=$(pwd)/Scripts +INFOPLIST_FILE="Info.plist" + +# Ensure a clean build +rm -rf Archive/* +rm -rf Product/* +xcodebuild clean -project $PROJECT_NAME.xcodeproj -configuration Release -alltargets + +# Archive the app and export for release distribution +xcodebuild archive -project $PROJECT_NAME.xcodeproj -scheme $PROJECT_NAME -archivePath Archive/$PROJECT_NAME.xcarchive +xcodebuild -archivePath Archive/$PROJECT_NAME.xcarchive -exportArchive -exportPath Product/$PROJECT_NAME -exportOptionsPlist "${SCRIPTS_DIR}/export_options.plist" +cp -r "Product/$PROJECT_NAME/$PROJECT_NAME.app" "Product/$PROJECT_NAME.app" + +# Create a ZIP archive suitable for altool. +/usr/bin/ditto -c -k --keepParent "Product/$PROJECT_NAME.app" "Product/$PROJECT_NAME.zip" diff --git a/Xcodes/Resources/Info.plist b/Xcodes/Resources/Info.plist index 0d3bfc9..0a907aa 100644 --- a/Xcodes/Resources/Info.plist +++ b/Xcodes/Resources/Info.plist @@ -20,6 +20,8 @@ 1.0.0 CFBundleVersion 1 + CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT + $(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright @@ -33,9 +35,7 @@ SMPrivilegedExecutables com.robotsandpencils.XcodesApp.Helper - identifier "com.robotsandpencils.XcodesApp.Helper" and info [CFBundleShortVersionString] >= "1.0.0" and anchor apple generic and certificate leaf[subject.OU] = "$(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT)" + identifier "com.robotsandpencils.XcodesApp.Helper" and info [CFBundleShortVersionString] >= "1.0.0" and anchor apple generic and certificate leaf[subject.OU] = "$(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT)" - CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT - $(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT)