Skip to content

Commit

Permalink
Automate the release process
Browse files Browse the repository at this point in the history
  • Loading branch information
cathyjf committed Nov 9, 2024
1 parent 9529932 commit a2fbaa8
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 14 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
.DS_Store
.homebrew
.vscode
bin
arm64
build
objects
universal

testing/*.log
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,22 @@ add_executable(pinentry-wrapper
"src/pinentry-wrapper.cpp")
target_codesign(pinentry-wrapper)

###############################################################################
# dependency-sources.zip

add_custom_command(
OUTPUT
"dependency-sources.zip"
"dependency-sources.zip.txt"
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/src/meta/download-source.sh" ARGS
${CMAKE_BINARY_DIR}
VERBATIM
USES_TERMINAL
)

add_custom_target("dependency-sources"
DEPENDS "dependency-sources.zip" "dependency-sources.zip.txt")

###############################################################################
# keychain-interpose.app

Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ notarize : universal/keychain-interpose.app

release : universal/keychain-interpose.app
@make notarize
src/meta/download-source.sh
@cmake --build "$(<D)" --target dependency-sources
/usr/bin/ditto -ck --keepParent "$<" "$<.zip"

upload : release
src/meta/upload-to-github.sh

clean clean-all :
rm -Rf arm64 build universal x86

Expand All @@ -31,4 +34,4 @@ shellcheck :

.DELETE_ON_ERROR :

.PHONY : all clean clean-all default release test shellcheck notarize
.PHONY : all clean clean-all default notarize release shellcheck test upload
24 changes: 16 additions & 8 deletions src/meta/download-source.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
#!/bin/bash -efu
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright 2023 Cathy J. Fitzpatrick <[email protected]>
# SPDX-License-Identifier: GPL-3.0-or-later
set -o pipefail
set -efuC -o pipefail

fastfail() {
"$@" || kill -- "-$$"
}

source_dir=$(dirname "$(fastfail readlink -f "$0")")
base_dir="${source_dir}/../.."
pkg_info_dir="${base_dir}/universal/keychain-interpose.app/Contents/Resources/pkg-info"
if [[ -d "${1:-}" ]]; then
base_dir=${1}
else
source_dir=$(dirname "$(fastfail readlink -f "$0")")
base_dir="${source_dir}/../../universal"
fi

pkg_info_dir="${base_dir}/keychain-interpose.app/Contents/Resources/pkg-info"
readonly source_dir base_dir pkg_info_dir

[[ -d ${pkg_info_dir} ]] || exit 1
Expand All @@ -19,7 +24,7 @@ while IFS= read -r -d $'\0' pkg; do
packages+=( "$(basename "${pkg}")" )
done < <(fastfail find -L "${pkg_info_dir}" -mindepth 1 -type directory -print0)

target_dir="${base_dir}/universal/sources"
target_dir="${base_dir}/sources"
[[ -d ${target_dir} ]] && rm -R "${target_dir}"
mkdir -p "${target_dir}"

Expand All @@ -39,11 +44,14 @@ done < <(
fastfail brew info --json "${packages[@]}" | \
fastfail yq '.[] | (.name + ":" + .versions.stable + ":" + .urls.stable.url)')

# Remove the final newline from ${release_message}.
release_message="${release_message::-1}"

echo "Creating archive of dependency source code:"
zip_basename='dependency-sources.zip'
zip_path="${base_dir}/universal/${zip_basename}"
zip_path="${base_dir}/${zip_basename}"
/usr/bin/ditto -ckV --keepParent "${target_dir}" "${zip_path}"
rm -R "${target_dir}"
du -sh "$(fastfail readlink -f "${zip_path}")"
echo "The ${zip_basename} file contains the source code of the following packages:"
sort <(echo -n "${release_message}")
echo -n "${release_message}" | sort | tee "${zip_path}.txt"
10 changes: 10 additions & 0 deletions src/meta/github-release.md.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
changequote(`[', `]')dnl
All releases of `keychain-interpose` so far, including this one, are primarily intended for my own use.

The release binaries are available in the `keychain-interpose-RELEASE_VERSION.zip` file.

This release contains binary versions of the following dependencies, signed with my developer key:

RELEASE_DEPENDENCY_LIST

The source code of the dependencies is available in the `keychain-interpose-RELEASE_VERSION-dependency-sources.zip` file.
4 changes: 2 additions & 2 deletions src/meta/make-universal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,5 @@ chmod -R go-rwx arm64/keychain-interpose.app
"${IDENTITY:?}" "--entitlements arm64/migrate-keys-entitlements.plist"

rm -Rf universal x64
mv arm64 universal
echo "Moved \`arm64\` to \`universal\`."
ln -f -s arm64 universal
echo "Symlinked \`arm64\` to \`universal\`."
185 changes: 185 additions & 0 deletions src/meta/upload-to-github.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright 2023 Cathy J. Fitzpatrick <[email protected]>
# SPDX-License-Identifier: GPL-3.0-or-later
set -efuC -o pipefail
shopt -s inherit_errexit

fastfail() {
"${@}" || {
/bin/kill -- "-$(/bin/ps -o pgid= "${$}")" "${$}" > /dev/null 2>&1
}
}

prompt_yn() {
local __yes='y' __no='n' __default="${2:-}"
if [[ ${__default,,} == 'y' ]]; then
__yes='Y'
elif [[ ${__default,,} == 'n' ]]; then
__no='N'
elif [[ -n "${__default}" ]]; then
echo "prompt_yn: Unknown default: ${__default,,}" 1>&2
return 2
fi
echo -n "${1:?} [${__yes}/${__no}] "
local __prompt_yn=
while [[ (${__prompt_yn,,} != 'y') && (${__prompt_yn,,} != 'n') ]]; do
read -r -s -N 1 __prompt_yn
if [[ (-n "${__default}") && (${__prompt_yn} == $'\n') ]]; then
__prompt_yn=${__default,,}
fi
done
echo "${__prompt_yn,,}"
local __status=0
[[ ${__prompt_yn,,} == 'y' ]] || __status=1
return "${__status}"
}

__is_overwrite_required() {
local -a refs
mapfile -t refs < <(
fastfail git show-ref -s -d -- \
"refs/heads/${__branch}" "refs/tags/${1:?}" | \
fastfail cut -d ' ' -f 1
)
local __status=0
[[ ${refs[0]} != "${refs[2]}" ]] || __status=1
return "${__status}"
}

declare __force_push_required __skip_tag_creation
__inner_prompt_version() {
version=
while [[ -z "${version}" ]]; do
__force_push_required=0 __skip_tag_creation=0
IFS= read -r -p 'Enter new version to release: ' version
if [[ ${version:0:1} != 'v' ]]; then
version="v${version}"
fi
# shellcheck disable=SC2310
if [[ ! ${version} =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
echo 'Supplied version does not match the required pattern (X.Y(.Z)?). Try again.'
version=
elif [[ -n "$(fastfail git tag -l "${version}")" ]]; then
# shellcheck disable=SC2310
if __is_overwrite_required "${version}"; then
local warning1 warning2
warning1=$(
echo -n $'\u2757'' Warning: Supplied version ('"${version}"') exists '
echo -n 'and is not the same revision. '$'\u2757'
)
warning2=$'\u2757'' It will be overwritten if you proceed.'
printf "%s\n%s%$(( ${#warning1} - ${#warning2} - 1 ))s%s\n" \
"${warning1}" "${warning2}" '' $'\u2757'
__force_push_required=1
else
__skip_tag_creation=1
fi
fi
done
}

prompt_version() {
local __confirmed_version=
while [[ -z "${__confirmed_version}" ]]; do
__inner_prompt_version
local message='Use this version ('"${version}"')?'
local default='y'
if [[ ${__force_push_required} -eq 1 ]]; then
message="${message::-1} even though it will overwrite existing version?"
default='n'
fi
# shellcheck disable=SC2310
if prompt_yn "${message}" "${default}"; then
# shellcheck disable=SC2016
message='This will require `git push -f`. Are you sure?'
if [[ ${__force_push_required} -eq 0 ]] || prompt_yn "${message}" 'n'; then
__confirmed_version=1
fi
fi
done
}

declare __branch
__branch=$(git branch --show-current)
[[ -n "${__branch}" ]] || {
echo 'Failed to determine current git branch. Aborting.'
exit 1
}

declare build_directory="${1:-universal}"
# shellcheck disable=SC2310
prompt_yn 'Build directory "'"${build_directory}"'" will be used. Is this okay?' 'y' || {
echo 'Aborting.'
exit 1
}

echo 'Existing versions: '
git tag -ln

declare version release_message
prompt_version
release_message=$(
m4 "src/meta/github-release.md.m4" -E \
-D "RELEASE_VERSION=${version}" \
-D "RELEASE_DEPENDENCY_LIST=$(< "${build_directory}/dependency-sources.zip.txt")"
)

if [[ ${__skip_tag_creation} -ne 1 ]]; then
declare remote
remote=$(git config get "branch.${__branch}.remote")
[[ -n "${remote}" ]] || {
echo 'Failed to determine default git remote. Aborting.'
exit 1
}
declare -a git_args=()
if [[ ${__force_push_required} -eq 1 ]]; then
git_args+=( -f )
fi
echo 'Signing a tag for the release...'
git tag "${git_args[@]}" -s "${version}" -m "version ${version:1}"
echo 'Pushing the signed tag...'
git push "${git_args[@]}" "${remote}" "${version}"
fi

declare releases
releases=$(
gh release list --json tagName,isDraft \
-q '.[] | select(.isDraft == true and .tagName == "'"${version}"'") | .tagName'
)
if [[ -n "${releases}" ]]; then
echo 'One or more GitHub draft releases already exist for this version ('"${version}"').'
declare num_releases=$(( $(wc -l <<< "${releases}") ))
echo 'You can delete all '"${num_releases}"' of the drafts if you want.'
# shellcheck disable=SC2310
if prompt_yn $'\u2757'' Delete all drafts for version '"${version}"'?'; then
declare i
for ((i = 0; i < num_releases; ++i)); do
gh release delete -y "${version}"
done
fi
fi

declare -A artifacts=(
["keychain-interpose.app.zip"]="keychain-interpose-${version}.zip"
["dependency-sources.zip"]="keychain-interpose-${version}-dependency-sources.zip"
)

declare i
for i in "${!artifacts[@]}"; do
artifacts[${i}]="${build_directory}/${artifacts[${i}]}"
ln -f "${build_directory}/${i}" "${artifacts[${i}]}"
done

echo 'Uploading artifacts...'
declare uri
uri=$(
echo -n "${release_message}" |
gh release create "${version}" --title "${version}" --notes-file - \
--draft --verify-tag "${artifacts[@]}"
)
echo 'You can finish publishing the release at this URI:'
echo ' '"${uri}"
# shellcheck disable=SC2310
if prompt_yn 'Open this in your browser now?' 'y'; then
open "${uri}"
fi

0 comments on commit a2fbaa8

Please sign in to comment.