Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix release-tool on macOS and add notarisation #3827

Merged
merged 1 commit into from
Nov 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 125 additions & 27 deletions release-tool
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ BUILD_PLUGINS="all"
INSTALL_PREFIX="/usr/local"
ORIG_BRANCH=""
ORIG_CWD="$(pwd)"
MACOSX_DEPLOYMENT_TARGET=10.12
GREP="grep"

# -----------------------------------------------------------------------
# helper functions
Expand Down Expand Up @@ -140,7 +142,11 @@ Sign binaries with code signing certificates on Windows and macOS

Options:
-f, --files Files to sign (required)
-k, --key Signing Key or Apple Developer ID
-k, --key, -i, --identity
Signing Key or Apple Developer ID (required)
-u, --username Apple username for notarization (required on macOS)
-c, --keychain Apple keychain entry name storing the notarization
app password (default: 'AC_PASSWORD')
-h, --help Show this help
EOF
elif [ "appimage" == "$cmd" ]; then
Expand Down Expand Up @@ -169,6 +175,10 @@ logInfo() {
printf "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1\n"
}

logWarn() {
printf "\e[1m[ \e[33mWARNING\e[39m ]\e[0m $1\n"
}

logError() {
printf "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1\n" >&2
}
Expand Down Expand Up @@ -218,6 +228,16 @@ cmdExists() {
command -v "$1" &> /dev/null
}

checkGrepCompat() {
if ! grep -qPzo test <(echo test) 2> /dev/null; then
if [ -e /usr/local/opt/grep/libexec/gnubin/grep ]; then
GREP="/usr/local/opt/grep/libexec/gnubin/grep"
else
exitError "Incompatible grep implementation! If on macOS, please run 'brew install grep'."
fi
fi
}

checkSourceDirExists() {
if [ ! -d "$SRC_DIR" ]; then
exitError "Source directory '${SRC_DIR}' does not exist!"
Expand All @@ -237,7 +257,7 @@ checkGitRepository() {
}

checkReleaseDoesNotExist() {
git tag | grep -q "^$TAG_NAME$"
git tag | $GREP -q "^$TAG_NAME$"
if [ $? -eq 0 ]; then
exitError "Release '$RELEASE_NAME' (tag: '$TAG_NAME') already exists!"
fi
Expand Down Expand Up @@ -270,17 +290,17 @@ checkVersionInCMake() {
local minor_num="$(echo ${RELEASE_NAME} | cut -f2 -d.)"
local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d. | cut -f1 -d-)"

grep -q "${app_name_upper}_VERSION_MAJOR \"${major_num}\"" CMakeLists.txt
$GREP -q "${app_name_upper}_VERSION_MAJOR \"${major_num}\"" CMakeLists.txt
if [ $? -ne 0 ]; then
exitError "${app_name_upper}_VERSION_MAJOR not updated to '${major_num}' in CMakeLists.txt!"
fi

grep -q "${app_name_upper}_VERSION_MINOR \"${minor_num}\"" CMakeLists.txt
$GREP -q "${app_name_upper}_VERSION_MINOR \"${minor_num}\"" CMakeLists.txt
if [ $? -ne 0 ]; then
exitError "${app_name_upper}_VERSION_MINOR not updated to '${minor_num}' in CMakeLists.txt!"
fi

grep -q "${app_name_upper}_VERSION_PATCH \"${patch_num}\"" CMakeLists.txt
$GREP -q "${app_name_upper}_VERSION_PATCH \"${patch_num}\"" CMakeLists.txt
if [ $? -ne 0 ]; then
exitError "${app_name_upper}_VERSION_PATCH not updated to '${patch_num}' in CMakeLists.txt!"
fi
Expand All @@ -291,7 +311,7 @@ checkChangeLog() {
exitError "No CHANGELOG file found!"
fi

grep -qPzo "## ${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n" CHANGELOG.md
$GREP -qPzo "## ${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n" CHANGELOG.md
if [ $? -ne 0 ]; then
exitError "'CHANGELOG.md' has not been updated to the '${RELEASE_NAME}' release!"
fi
Expand All @@ -302,7 +322,7 @@ checkAppStreamInfo() {
exitError "No AppStream info file found!"
fi

grep -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.KeePassXC.appdata.xml
$GREP -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.KeePassXC.appdata.xml
if [ $? -ne 0 ]; then
exitError "'share/linux/org.keepassxc.KeePassXC.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
fi
Expand All @@ -314,12 +334,12 @@ checkSnapcraft() {
return
fi

grep -qPzo "version: ${RELEASE_NAME}" snapcraft.yaml
$GREP -qPzo "version: ${RELEASE_NAME}" snapcraft.yaml
if [ $? -ne 0 ]; then
exitError "'snapcraft.yaml' has not been updated to the '${RELEASE_NAME}' release!"
fi

grep -qPzo "KEEPASSXC_BUILD_TYPE=Release" snapcraft.yaml
$GREP -qPzo "KEEPASSXC_BUILD_TYPE=Release" snapcraft.yaml
if [ $? -ne 0 ]; then
exitError "'snapcraft.yaml' is not set for a release build!"
fi
Expand All @@ -337,14 +357,23 @@ checkSigntoolCommandExists() {
fi
}

checkCodesignCommandExists() {
if ! cmdExists codesign; then
exitError "codesign command not found on the PATH! Please check that you have correctly installed Xcode."
checkXcodeSetup() {
if ! cmdExists xcrun; then
exitError "xcrun command not found on the PATH! Please check that you have correctly installed Xcode."
fi
if ! xcrun -f codesign > /dev/null 2>&1; then
exitError "codesign command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
fi
if ! xcrun -f altool > /dev/null 2>&1; then
exitError "altool command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
fi
if ! xcrun -f stapler > /dev/null 2>&1; then
exitError "stapler command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
fi
}

checkQt5LUpdateExists() {
if cmdExists lupdate && ! $(lupdate -version | grep -q "lupdate version 5\."); then
if cmdExists lupdate && ! $(lupdate -version | $GREP -q "lupdate version 5\."); then
if ! cmdExists lupdate-qt5; then
exitError "Qt Linguist tool (lupdate-qt5) is not installed! Please install using 'apt install qttools5-dev-tools'"
fi
Expand All @@ -354,6 +383,8 @@ checkQt5LUpdateExists() {
performChecks() {
logInfo "Performing basic checks..."

checkGrepCompat

checkSourceDirExists

logInfo "Changing to source directory..."
Expand Down Expand Up @@ -498,7 +529,7 @@ merge() {
fi
fi

CHANGELOG=$(grep -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n\n)\n?(?:.|\n)+?\n(?=## )" CHANGELOG.md \
CHANGELOG=$($GREP -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n\n)\n?(?:.|\n)+?\n(?=## )" CHANGELOG.md \
| sed 's/^### //' | tr -d \\0)
COMMIT_MSG="Release ${RELEASE_NAME}"

Expand Down Expand Up @@ -664,14 +695,14 @@ EOF

# Find .desktop files, icons, and binaries to deploy
local desktop_file="$(find "$appdir" -name "org.keepassxc.KeePassXC.desktop" | head -n1)"
local icon="$(find "$appdir" -name 'keepassxc.png' | grep -P 'application/256x256/apps/keepassxc.png$' | head -n1)"
local executables="$(IFS=$'\n' find "$appdir" | grep -P '/usr/bin/keepassxc[^/]*$' | xargs -i printf " --executable={}")"
local icon="$(find "$appdir" -name 'keepassxc.png' | $GREP -P 'application/256x256/apps/keepassxc.png$' | head -n1)"
local executables="$(IFS=$'\n' find "$appdir" | $GREP -P '/usr/bin/keepassxc[^/]*$' | xargs -i printf " --executable={}")"

logInfo "Collecting libs and patching binaries..."
if [ "" == "$DOCKER_IMAGE" ]; then
"$linuxdeploy" --verbosity=${verbosity} --plugin=qt --appdir="$appdir" --desktop-file="$desktop_file" \
--custom-apprun="${out_real}/KeePassXC-AppRun" --icon-file="$icon" ${executables} \
--library=$(ldconfig -p | grep x86-64 | grep -oP '/[^\s]+/libgpg-error\.so\.\d+$' | head -n1)
--library=$(ldconfig -p | $GREP x86-64 | $GREP -oP '/[^\s]+/libgpg-error\.so\.\d+$' | head -n1)
else
desktop_file="${desktop_file//${appdir}/\/keepassxc\/AppDir}"
icon="${icon//${appdir}/\/keepassxc\/AppDir}"
Expand Down Expand Up @@ -829,9 +860,10 @@ build() {
RELEASE_NAME="${RELEASE_NAME}-snapshot"
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot -DOVERRIDE_VERSION=${RELEASE_NAME}"
else
checkGrepCompat
checkWorkingTreeClean

if $(echo "$TAG_NAME" | grep -qP "\-(alpha|beta)\\d+\$"); then
if $(echo "$TAG_NAME" | $GREP -qP "\-(alpha|beta)\\d+\$"); then
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=PreRelease"
logInfo "Checking out pre-release tag '${TAG_NAME}'..."
else
Expand Down Expand Up @@ -864,7 +896,12 @@ build() {
rm "${prefix}/.version" "${prefix}/.gitrev"
rmdir "${prefix}" 2> /dev/null

xz -6 "${OUTPUT_DIR}/${tarball_name}"
local xz="xz"
if ! cmdExists xz; then
logWarn "xz not installed. Falling back to bz2..."
xz="bzip2"
fi
$xz -6 "${OUTPUT_DIR}/${tarball_name}"
fi

if ! ${build_snapshot} && [ -e "${OUTPUT_DIR}/build-release" ]; then
Expand All @@ -883,7 +920,7 @@ build() {
for p in ${BUILD_PLUGINS}; do
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
done
if [ "$(uname -o)" == "GNU/Linux" ] && ${build_appimage}; then
if [ "$(uname -o 2> /dev/null)" == "GNU/Linux" ] && ${build_appimage}; then
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_DIST_TYPE=AppImage"
# linuxdeploy requires /usr as install prefix
INSTALL_PREFIX="/usr"
Expand All @@ -901,7 +938,7 @@ build() {
if [ "" == "$DOCKER_IMAGE" ]; then
if [ "$(uname -s)" == "Darwin" ]; then
# Building on macOS
export MACOSX_DEPLOYMENT_TARGET=10.10
export MACOSX_DEPLOYMENT_TARGET

logInfo "Configuring build..."
cmake -DCMAKE_BUILD_TYPE=Release \
Expand Down Expand Up @@ -931,7 +968,7 @@ build() {
# Appsign the executables if desired
if ${build_appsign} && [ -f "${build_key}" ]; then
logInfo "Signing executable files"
appsign "-f" $(find src | grep -P '\.exe$|\.dll$') "-k" "${build_key}"
appsign "-f" $(find src | $GREP -P '\.exe$|\.dll$') "-k" "${build_key}"
fi

# Call cpack directly instead of calling make package.
Expand Down Expand Up @@ -998,7 +1035,7 @@ build() {
logInfo "Build finished, Docker container terminated."
fi

if [ "$(uname -o)" == "GNU/Linux" ] && ${build_appimage}; then
if [ "$(uname -o 2> /dev/null)" == "GNU/Linux" ] && ${build_appimage}; then
local appsign_flag=""
local appsign_key_flag=""
local docker_image_flag=""
Expand Down Expand Up @@ -1084,6 +1121,8 @@ gpgsign() {
appsign() {
local sign_files=()
local key
local ac_username
local ac_keychain="AC_PASSWORD"

while [ $# -ge 1 ]; do
local arg="$1"
Expand All @@ -1098,6 +1137,14 @@ appsign() {
key="$2"
shift ;;

-u|--username)
ac_username="$2"
shift ;;

-c|--keychain)
ac_keychain="$2"
shift ;;

-h|--help)
printUsage "appsign"
exit ;;
Expand Down Expand Up @@ -1129,9 +1176,15 @@ appsign() {
done

if [ "$(uname -s)" == "Darwin" ]; then
checkCodesignCommandExists
if [ "$ac_username" == "" ]; then
exitError "Missing arguments, --username is required!"
fi

checkXcodeSetup
checkGrepCompat

local orig_dir="$(pwd)"
local real_src_dir="$(realpath "${SRC_DIR}")"
for f in "${sign_files[@]}"; do
if [[ ${f: -4} == '.dmg' ]]; then
logInfo "Unpacking disk image '${f}'..."
Expand All @@ -1147,8 +1200,9 @@ appsign() {
exitError "Unpacking failed!"
fi

logInfo "Signing app using codesign..."
codesign --sign "${key}" --verbose --deep --entitlements "${SRC_DIR}/share/macosx/keepassxc.entitlements" ./app/KeePassXC.app
logInfo "Signing app..."
xcrun codesign --sign "${key}" --verbose --deep --entitlements \
"${real_src_dir}/share/macosx/keepassxc.entitlements" ./app/KeePassXC.app

if [ 0 -ne $? ]; then
cd "${orig_dir}"
Expand All @@ -1164,11 +1218,55 @@ appsign() {
-fsargs "-c c=64,a=16,e=16" \
-format UDBZ \
"${tmp_dir}/$(basename "${f}")"

cd "${orig_dir}"
cp -f "${tmp_dir}/$(basename "${f}")" "${f}"
rm -Rf ${tmp_dir}

logInfo "Submitting disk image for notarization..."
local status="$(xcrun altool --notarize-app \
--primary-bundle-id "org.keepassxc.keepassxc" \
--username "${ac_username}" \
--password "@keychain:${ac_keychain}" \
--file "${f}")"

if [ 0 -ne $? ]; then
logError "Submission failed!"
exitError "Error message:\n${status}"
fi

local ticket="$(echo "${status}" | $GREP -oP "[a-f0-9-]+$")"
logInfo "Submission successful. Ticket ID: ${ticket}."

logInfo "Waiting for notarization to finish (this may take a while)..."
while true; do
echo -n "."

status="$(xcrun altool --notarization-info "${ticket}" \
--username "${ac_username}" \
--password "@keychain:${ac_keychain}")"

if echo "$status" | $GREP -q "Status Code: 0"; then
logInfo "\nNotarization successful."
break
elif echo "$status" | $GREP -q "Status Code"; then
logError "\nNotarization failed!"
exitError "Error message:\n${status}"
fi

sleep 5
done

logInfo "Stapling ticket to disk image..."
xcrun stapler staple "${f}"

if [ 0 -ne $? ]; then
exitError "Stapling failed!"
fi

logInfo "Disk image successfully signed and notarized."
else
logInfo "Skipping non-DMG file '${f}'..."
logWarn "Skipping non-DMG file '${f}'..."
fi
done

Expand Down
37 changes: 24 additions & 13 deletions share/macosx/keepassxc.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,31 @@
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>org.keepassx.keepassxc</string>
<string>org.keepassx.keepassxc</string>
<key>com.apple.developer.aps-environment</key>
<string>production</string>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<false/>
<string>production</string>

<key>keychain-access-groups</key>
<array>
<string>org.keepassx.keepassxc</string>
</array>
<array>
<string>org.keepassx.keepassxc</string>
</array>

<!-- Sandbox entitlements stub for future reference.
For whatever reason, we have to set this twice.
Otherwise a signed application crashes on startup -->
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.app-sandbox</key>
<false/>
<!--key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.device.usb</key>
<true/>
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<false/>
<false/-->
</dict>
</plist>
</plist>