mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
970 lines
30 KiB
Bash
Executable File
970 lines
30 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# KeePassXC Release Preparation Helper
|
|
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 or (at your option)
|
|
# version 3 of the License.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
printf "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper\n"
|
|
printf "Copyright (C) 2017 KeePassXC Team <https://keepassxc.org/>\n\n"
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# global default values
|
|
# -----------------------------------------------------------------------
|
|
RELEASE_NAME=""
|
|
APP_NAME="KeePassXC"
|
|
SRC_DIR="."
|
|
GPG_KEY="CFB4C2166397D0D2"
|
|
GPG_GIT_KEY=""
|
|
OUTPUT_DIR="release"
|
|
SOURCE_BRANCH=""
|
|
TARGET_BRANCH="master"
|
|
TAG_NAME=""
|
|
DOCKER_IMAGE=""
|
|
DOCKER_CONTAINER_NAME="keepassxc-build-container"
|
|
CMAKE_OPTIONS=""
|
|
COMPILER="g++"
|
|
MAKE_OPTIONS="-j8"
|
|
BUILD_PLUGINS="all"
|
|
INSTALL_PREFIX="/usr/local"
|
|
BUILD_SOURCE_TARBALL=true
|
|
BUILD_SNAPSHOT=false
|
|
ORIG_BRANCH=""
|
|
ORIG_CWD="$(pwd)"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# helper functions
|
|
# -----------------------------------------------------------------------
|
|
printUsage() {
|
|
local cmd
|
|
if [ "" == "$1" ] || [ "help" == "$1" ]; then
|
|
cmd="COMMAND"
|
|
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ]; then
|
|
cmd="$1"
|
|
else
|
|
logError "Unknown command: '$1'\n"
|
|
cmd="COMMAND"
|
|
fi
|
|
|
|
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n"
|
|
|
|
if [ "COMMAND" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Commands:
|
|
check Perform a dry-run check, nothing is changed
|
|
merge Merge release branch into main branch and create release tags
|
|
build Build and package binary release from sources
|
|
gpgsign Sign previously compiled release packages with GPG
|
|
appsign Sign binaries with code signing certificates on Windows and macOS
|
|
help Show help for the given command
|
|
EOF
|
|
elif [ "merge" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Merge release branch into main branch and create release tags
|
|
|
|
Options:
|
|
-v, --version Release version number or name (required)
|
|
-a, --app-name Application name (default: '${APP_NAME}')
|
|
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
|
-g, --gpg-key GPG key used to sign the merge commit and release tag,
|
|
leave empty to let Git choose your default key
|
|
(default: '${GPG_GIT_KEY}')
|
|
-r, --release-branch Source release branch to merge from (default: 'release/VERSION')
|
|
--target-branch Target branch to merge to (default: '${TARGET_BRANCH}')
|
|
-t, --tag-name Override release tag name (defaults to version number)
|
|
-h, --help Show this help
|
|
EOF
|
|
elif [ "build" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Build and package binary release from sources
|
|
|
|
Options:
|
|
-v, --version Release version number or name (required)
|
|
-a, --app-name Application name (default: '${APP_NAME}')
|
|
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
|
-o, --output-dir Output directory where to build the release
|
|
(default: '${OUTPUT_DIR}')
|
|
-t, --tag-name Release tag to check out (defaults to version number)
|
|
-b, --build Build sources after exporting release
|
|
-d, --docker-image Use the specified Docker image to compile the application.
|
|
The image must have all required build dependencies installed.
|
|
This option has no effect if --build is not set.
|
|
--container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}')
|
|
The container must not exist already
|
|
-c, --cmake-options Additional CMake options for compiling the sources
|
|
--compiler Compiler to use (default: '${COMPILER}')
|
|
-m, --make-options Make options for compiling sources (default: '${MAKE_OPTIONS}')
|
|
-i, --install-prefix Install prefix (default: '${INSTALL_PREFIX}')
|
|
-p, --plugins Space-separated list of plugins to build
|
|
(default: ${BUILD_PLUGINS})
|
|
--snapshot Don't checkout the release tag
|
|
-n, --no-source-tarball Don't build source tarball
|
|
-h, --help Show this help
|
|
EOF
|
|
elif [ "gpgsign" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Sign previously compiled release packages with GPG
|
|
|
|
Options:
|
|
-f, --files Files to sign (required)
|
|
-g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}')
|
|
-h, --help Show this help
|
|
EOF
|
|
elif [ "appsign" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Sign binaries with code signing certificates on Windows and macOS
|
|
|
|
Options:
|
|
-f, --files Files to sign (required)
|
|
-k, --signtool-key Key to be used with signtool (required for Windows EXE)
|
|
-i, --identity Apple Developer ID to be used with codesign (required for macOS APP and DMG)
|
|
-h, --help Show this help
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
logInfo() {
|
|
printf "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1\n"
|
|
}
|
|
|
|
logError() {
|
|
printf "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1\n" >&2
|
|
}
|
|
|
|
init() {
|
|
if [ "" == "$RELEASE_NAME" ]; then
|
|
logError "Missing arguments, --version is required!\n"
|
|
printUsage "check"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "" == "$TAG_NAME" ]; then
|
|
TAG_NAME="$RELEASE_NAME"
|
|
fi
|
|
|
|
if [ "" == "$SOURCE_BRANCH" ]; then
|
|
SOURCE_BRANCH="release/${RELEASE_NAME}"
|
|
fi
|
|
|
|
ORIG_CWD="$(pwd)"
|
|
SRC_DIR="$(realpath "$SRC_DIR")"
|
|
cd "$SRC_DIR" > /dev/null 2>&1
|
|
ORIG_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)"
|
|
cd "$ORIG_CWD"
|
|
}
|
|
|
|
cleanup() {
|
|
logInfo "Checking out original branch..."
|
|
if [ "" != "$ORIG_BRANCH" ]; then
|
|
git checkout "$ORIG_BRANCH" > /dev/null 2>&1
|
|
fi
|
|
logInfo "Leaving source directory..."
|
|
cd "$ORIG_CWD"
|
|
}
|
|
|
|
exitError() {
|
|
logError "$1"
|
|
cleanup
|
|
exit 1
|
|
}
|
|
|
|
exitTrap() {
|
|
exitError "Existing upon user request..."
|
|
}
|
|
|
|
checkSourceDirExists() {
|
|
if [ ! -d "$SRC_DIR" ]; then
|
|
exitError "Source directory '${SRC_DIR}' does not exist!"
|
|
fi
|
|
}
|
|
|
|
checkOutputDirDoesNotExist() {
|
|
if [ -e "$OUTPUT_DIR" ]; then
|
|
exitError "Output directory '$OUTPUT_DIR' already exists. Please choose a different location!"
|
|
fi
|
|
}
|
|
|
|
checkGitRepository() {
|
|
if [ ! -d .git ] || [ ! -f CHANGELOG ]; then
|
|
exitError "Source directory is not a valid Git repository!"
|
|
fi
|
|
}
|
|
|
|
checkTagExists() {
|
|
git tag | grep -q "$TAG_NAME"
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Tag '${TAG_NAME}' does not exist!"
|
|
fi
|
|
}
|
|
|
|
checkReleaseDoesNotExist() {
|
|
git tag | grep -q "$TAG_NAME"
|
|
if [ $? -eq 0 ]; then
|
|
exitError "Release '$RELEASE_NAME' (tag: '$TAG_NAME') already exists!"
|
|
fi
|
|
}
|
|
|
|
checkWorkingTreeClean() {
|
|
git diff-index --quiet HEAD --
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Current working tree is not clean! Please commit or unstage any changes."
|
|
fi
|
|
}
|
|
|
|
checkSourceBranchExists() {
|
|
git rev-parse "$SOURCE_BRANCH" > /dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Source branch '$SOURCE_BRANCH' does not exist!"
|
|
fi
|
|
}
|
|
|
|
checkTargetBranchExists() {
|
|
git rev-parse "$TARGET_BRANCH" > /dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Target branch '$TARGET_BRANCH' does not exist!"
|
|
fi
|
|
}
|
|
|
|
checkVersionInCMake() {
|
|
local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')"
|
|
local major_num="$(echo ${RELEASE_NAME} | cut -f1 -d.)"
|
|
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
|
|
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
|
|
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
|
|
if [ $? -ne 0 ]; then
|
|
exitError "${app_name_upper}_VERSION_PATCH not updated to '${patch_num}' in CMakeLists.txt!"
|
|
fi
|
|
}
|
|
|
|
checkChangeLog() {
|
|
if [ ! -f CHANGELOG ]; then
|
|
exitError "No CHANGELOG file found!"
|
|
fi
|
|
|
|
grep -qPzo "${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n=+\n" CHANGELOG
|
|
if [ $? -ne 0 ]; then
|
|
exitError "'CHANGELOG' has not been updated to the '${RELEASE_NAME}' release!"
|
|
fi
|
|
}
|
|
|
|
checkAppStreamInfo() {
|
|
if [ ! -f share/linux/org.keepassxc.KeePassXC.appdata.xml ]; then
|
|
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
|
|
if [ $? -ne 0 ]; then
|
|
exitError "'share/linux/org.keepassxc.KeePassXC.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
|
|
fi
|
|
}
|
|
|
|
checkSnapcraft() {
|
|
if [ ! -f snapcraft.yaml ]; then
|
|
echo "No snapcraft file found!"
|
|
return
|
|
fi
|
|
|
|
grep -qPzo "version: ${RELEASE_NAME}" snapcraft.yaml
|
|
if [ $? -ne 0 ]; then
|
|
exitError "'snapcraft.yaml' has not been updated to the '${RELEASE_NAME}' release!"
|
|
fi
|
|
}
|
|
|
|
checkTransifexCommandExists() {
|
|
command -v tx > /dev/null
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'."
|
|
fi
|
|
}
|
|
|
|
checkOsslsigncodeCommandExists() {
|
|
command -v osslsigncode > /dev/null
|
|
if [ 0 -ne $? ]; then
|
|
exitError "osslsigncode command not found on the PATH! Please install it using 'pacman -S mingw-w64-osslsigncode'."
|
|
fi
|
|
}
|
|
|
|
checkSigntoolCommandExists() {
|
|
command -v signtool > /dev/null
|
|
if [ 0 -ne $? ]; then
|
|
exitError "signtool command not found on the PATH! Add the Windows SDK binary folder to your PATH."
|
|
fi
|
|
}
|
|
|
|
checkCodesignCommandExists() {
|
|
command -v codesign > /dev/null
|
|
if [ 0 -ne $? ]; then
|
|
exitError "codesign command not found on the PATH! Please check that you have correctly installed Xcode."
|
|
fi
|
|
}
|
|
|
|
checkQt5LUpdateExists() {
|
|
command -v lupdate > /dev/null
|
|
if [ 0 -eq $? ] && ! $(lupdate -version | grep -q "lupdate version 5\."); then
|
|
command -v lupdate-qt5 > /dev/null
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Qt Linguist tool (lupdate-qt5) is not installed! Please install using 'apt install qttools5-dev-tools'"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
performChecks() {
|
|
logInfo "Performing basic checks..."
|
|
|
|
checkSourceDirExists
|
|
|
|
logInfo "Changing to source directory..."
|
|
cd "${SRC_DIR}"
|
|
|
|
logInfo "Validating toolset and repository..."
|
|
|
|
checkTransifexCommandExists
|
|
checkQt5LUpdateExists
|
|
checkGitRepository
|
|
checkReleaseDoesNotExist
|
|
checkWorkingTreeClean
|
|
checkSourceBranchExists
|
|
checkTargetBranchExists
|
|
|
|
logInfo "Checking out '${SOURCE_BRANCH}'..."
|
|
git checkout "$SOURCE_BRANCH"
|
|
|
|
logInfo "Attempting to find '${RELEASE_NAME}' in various files..."
|
|
|
|
checkVersionInCMake
|
|
checkChangeLog
|
|
checkAppStreamInfo
|
|
checkSnapcraft
|
|
|
|
logInfo "\e[1m\e[32mAll checks passed!\e[0m"
|
|
}
|
|
|
|
# re-implement realpath for OS X (thanks mschrag)
|
|
# https://superuser.com/questions/205127/
|
|
if ! $(command -v realpath > /dev/null); then
|
|
realpath() {
|
|
pushd . > /dev/null
|
|
if [ -d "$1" ]; then
|
|
cd "$1"
|
|
dirs -l +0
|
|
else
|
|
cd "$(dirname "$1")"
|
|
cur_dir=$(dirs -l +0)
|
|
|
|
if [ "$cur_dir" == "/" ]; then
|
|
echo "$cur_dir$(basename "$1")"
|
|
else
|
|
echo "$cur_dir/$(basename "$1")"
|
|
fi
|
|
fi
|
|
popd > /dev/null
|
|
}
|
|
fi
|
|
|
|
|
|
trap exitTrap SIGINT SIGTERM
|
|
|
|
# -----------------------------------------------------------------------
|
|
# check command
|
|
# -----------------------------------------------------------------------
|
|
check() {
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-v|--version)
|
|
RELEASE_NAME="$2"
|
|
shift ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
init
|
|
|
|
performChecks
|
|
|
|
cleanup
|
|
|
|
logInfo "Congrats! You can successfully merge, build, and sign KeepassXC."
|
|
}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# merge command
|
|
# -----------------------------------------------------------------------
|
|
merge() {
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-v|--version)
|
|
RELEASE_NAME="$2"
|
|
shift ;;
|
|
|
|
-a|--app-name)
|
|
APP_NAME="$2"
|
|
shift ;;
|
|
|
|
-s|--source-dir)
|
|
SRC_DIR="$2"
|
|
shift ;;
|
|
|
|
-g|--gpg-key)
|
|
GPG_GIT_KEY="$2"
|
|
shift ;;
|
|
|
|
-r|--release-branch)
|
|
SOURCE_BRANCH="$2"
|
|
shift ;;
|
|
|
|
--target-branch)
|
|
TARGET_BRANCH="$2"
|
|
shift ;;
|
|
|
|
-t|--tag-name)
|
|
TAG_NAME="$2"
|
|
shift ;;
|
|
|
|
-h|--help)
|
|
printUsage "merge"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "merge"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
init
|
|
|
|
performChecks
|
|
|
|
logInfo "Updating language files..."
|
|
./share/translations/update.sh update
|
|
./share/translations/update.sh pull
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Updating translations failed!"
|
|
fi
|
|
git diff-index --quiet HEAD --
|
|
if [ $? -ne 0 ]; then
|
|
git add ./share/translations/*
|
|
logInfo "Committing changes..."
|
|
if [ "" == "$GPG_GIT_KEY" ]; then
|
|
git commit -m "Update translations"
|
|
else
|
|
git commit -m "Update translations" -S"$GPG_GIT_KEY"
|
|
fi
|
|
fi
|
|
|
|
CHANGELOG=$(grep -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n)=+\n\n?(?:.|\n)+?\n(?=\n)" \
|
|
CHANGELOG | grep -Pzo '(?<=\n\n)(.|\n)+' | tr -d \\0)
|
|
COMMIT_MSG="Release ${RELEASE_NAME}"
|
|
|
|
logInfo "Checking out target branch '${TARGET_BRANCH}'..."
|
|
git checkout "$TARGET_BRANCH"
|
|
|
|
logInfo "Merging '${SOURCE_BRANCH}' into '${TARGET_BRANCH}'..."
|
|
|
|
git merge "$SOURCE_BRANCH" --no-ff -m "$COMMIT_MSG" -m "${CHANGELOG}" "$SOURCE_BRANCH" -S"$GPG_GIT_KEY"
|
|
|
|
logInfo "Creating tag '${TAG_NAME}'..."
|
|
if [ "" == "$GPG_GIT_KEY" ]; then
|
|
git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s
|
|
else
|
|
git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s -u "$GPG_GIT_KEY"
|
|
fi
|
|
|
|
cleanup
|
|
|
|
logInfo "All done!"
|
|
logInfo "Please merge the release branch back into the develop branch now and then push your changes."
|
|
logInfo "Don't forget to also push the tags using \e[1mgit push --tags\e[0m."
|
|
}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# build command
|
|
# -----------------------------------------------------------------------
|
|
build() {
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-v|--version)
|
|
RELEASE_NAME="$2"
|
|
shift ;;
|
|
|
|
-a|--app-name)
|
|
APP_NAME="$2"
|
|
shift ;;
|
|
|
|
-s|--source-dir)
|
|
SRC_DIR="$2"
|
|
shift ;;
|
|
|
|
-o|--output-dir)
|
|
OUTPUT_DIR="$2"
|
|
shift ;;
|
|
|
|
-t|--tag-name)
|
|
TAG_NAME="$2"
|
|
shift ;;
|
|
|
|
-d|--docker-image)
|
|
DOCKER_IMAGE="$2"
|
|
shift ;;
|
|
|
|
--container-name)
|
|
DOCKER_CONTAINER_NAME="$2"
|
|
shift ;;
|
|
|
|
-c|--cmake-options)
|
|
CMAKE_OPTIONS="$2"
|
|
shift ;;
|
|
|
|
--compiler)
|
|
COMPILER="$2"
|
|
shift ;;
|
|
|
|
-m|--make-options)
|
|
MAKE_OPTIONS="$2"
|
|
shift ;;
|
|
|
|
-i|--install-prefix)
|
|
INSTALL_PREFIX="$2"
|
|
shift ;;
|
|
|
|
-p|--plugins)
|
|
BUILD_PLUGINS="$2"
|
|
shift ;;
|
|
|
|
-n|--no-source-tarball)
|
|
BUILD_SOURCE_TARBALL=false ;;
|
|
|
|
--snapshot)
|
|
BUILD_SNAPSHOT=true ;;
|
|
|
|
-h|--help)
|
|
printUsage "build"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "build"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
init
|
|
|
|
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
|
|
|
|
if ${BUILD_SNAPSHOT}; then
|
|
TAG_NAME="HEAD"
|
|
local branch=`git rev-parse --abbrev-ref HEAD`
|
|
logInfo "Using current branch ${branch} to build..."
|
|
RELEASE_NAME="${RELEASE_NAME}-snapshot"
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot"
|
|
else
|
|
checkWorkingTreeClean
|
|
|
|
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
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Release"
|
|
logInfo "Checking out release tag '${TAG_NAME}'..."
|
|
fi
|
|
git checkout "$TAG_NAME"
|
|
fi
|
|
|
|
logInfo "Creating output directory..."
|
|
mkdir -p "$OUTPUT_DIR"
|
|
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Failed to create output directory!"
|
|
fi
|
|
|
|
if ${BUILD_SOURCE_TARBALL}; then
|
|
logInfo "Creating source tarball..."
|
|
local app_name_lower="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')"
|
|
local prefix="${app_name_lower}-${RELEASE_NAME}"
|
|
local tarball_name="${prefix}-src.tar"
|
|
|
|
git archive --format=tar "$TAG_NAME" --prefix="${prefix}/" --output="${OUTPUT_DIR}/${tarball_name}"
|
|
|
|
# add .version file to tar
|
|
mkdir "${prefix}"
|
|
echo -n ${RELEASE_NAME} > "${prefix}/.version"
|
|
tar --append --file="${OUTPUT_DIR}/${tarball_name}" "${prefix}/.version"
|
|
rm "${prefix}/.version"
|
|
rmdir "${prefix}" 2> /dev/null
|
|
|
|
xz -6 "${OUTPUT_DIR}/${tarball_name}"
|
|
fi
|
|
|
|
if [ -e "${OUTPUT_DIR}/build-release" ]; then
|
|
logInfo "Cleaning existing build directory..."
|
|
rm -r "${OUTPUT_DIR}/build-release" 2> /dev/null
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Failed to clean existing build directory, please do it manually."
|
|
fi
|
|
fi
|
|
|
|
logInfo "Creating build directory..."
|
|
mkdir -p "${OUTPUT_DIR}/build-release"
|
|
cd "${OUTPUT_DIR}/build-release"
|
|
|
|
logInfo "Configuring sources..."
|
|
for p in ${BUILD_PLUGINS}; do
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
|
|
done
|
|
|
|
if [ "$COMPILER" == "g++" ]; then
|
|
export CC=gcc
|
|
elif [ "$COMPILER" == "clang++" ]; then
|
|
export CC=clang
|
|
fi
|
|
export CXX="$COMPILER"
|
|
|
|
if [ "" == "$DOCKER_IMAGE" ]; then
|
|
if [ "$(uname -s)" == "Darwin" ]; then
|
|
# Building on macOS
|
|
local qt_vers="$(ls /usr/local/Cellar/qt 2> /dev/null | sort -r | head -n1)"
|
|
if [ "" == "$qt_vers" ]; then
|
|
exitError "Couldn't find Qt5! Please make sure it is available in '/usr/local/Cellar/qt'."
|
|
fi
|
|
|
|
export MACOSX_DEPLOYMENT_TARGET=10.10
|
|
|
|
logInfo "Configuring build..."
|
|
cmake -DCMAKE_BUILD_TYPE=Release \
|
|
-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
|
|
-DCMAKE_PREFIX_PATH="/usr/local/Cellar/qt/${qt_vers}/lib/cmake" \
|
|
${CMAKE_OPTIONS} "$SRC_DIR"
|
|
|
|
logInfo "Compiling and packaging sources..."
|
|
make ${MAKE_OPTIONS} package
|
|
|
|
mv "./${APP_NAME}-${RELEASE_NAME}.dmg" ../
|
|
elif [ "$(uname -o)" == "Msys" ]; then
|
|
# Building on Windows with Msys2
|
|
logInfo "Configuring build..."
|
|
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off -G"MSYS Makefiles" \
|
|
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" $CMAKE_OPTIONS "$SRC_DIR"
|
|
|
|
logInfo "Compiling and packaging sources..."
|
|
mingw32-make ${MAKE_OPTIONS} preinstall
|
|
# Call cpack directly instead of calling make package.
|
|
# This is important because we want to build the MSI when making a
|
|
# release.
|
|
cpack -G "NSIS;ZIP;${CPACK_GENERATORS}"
|
|
|
|
mv "./${APP_NAME}-"*.* ../
|
|
else
|
|
mkdir -p "${OUTPUT_DIR}/bin-release"
|
|
|
|
# Building on Linux without Docker container
|
|
logInfo "Configuring build..."
|
|
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
|
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
|
|
-DKEEPASSXC_DIST_TYPE=AppImage "$SRC_DIR"
|
|
|
|
logInfo "Compiling sources..."
|
|
make $MAKE_OPTIONS
|
|
|
|
logInfo "Installing to bin dir..."
|
|
make DESTDIR="${OUTPUT_DIR}/bin-release" install/strip
|
|
|
|
logInfo "Creating AppImage..."
|
|
${SRC_DIR}/AppImage-Recipe.sh "$APP_NAME" "$RELEASE_NAME"
|
|
fi
|
|
else
|
|
mkdir -p "${OUTPUT_DIR}/bin-release"
|
|
|
|
logInfo "Launching Docker container to compile sources..."
|
|
|
|
docker run --name "$DOCKER_CONTAINER_NAME" --rm \
|
|
--cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \
|
|
-e "CC=${CC}" -e "CXX=${CXX}" \
|
|
-v "$(realpath "$SRC_DIR"):/keepassxc/src:ro" \
|
|
-v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
|
|
"$DOCKER_IMAGE" \
|
|
bash -c "cd /keepassxc/out/build-release && \
|
|
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
|
-DCMAKE_INSTALL_PREFIX=\"${INSTALL_PREFIX}\" \
|
|
-DKEEPASSXC_DIST_TYPE=AppImage /keepassxc/src && \
|
|
make $MAKE_OPTIONS && make DESTDIR=/keepassxc/out/bin-release install/strip && \
|
|
/keepassxc/src/AppImage-Recipe.sh "$APP_NAME" "$RELEASE_NAME""
|
|
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Docker build failed!"
|
|
fi
|
|
|
|
logInfo "Build finished, Docker container terminated."
|
|
fi
|
|
|
|
cleanup
|
|
|
|
logInfo "All done!"
|
|
}
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# gpgsign command
|
|
# -----------------------------------------------------------------------
|
|
gpgsign() {
|
|
local sign_files=()
|
|
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-f|--files)
|
|
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
|
sign_files+=("$2")
|
|
shift
|
|
done ;;
|
|
|
|
-g|--gpg-key)
|
|
GPG_KEY="$2"
|
|
shift ;;
|
|
|
|
-h|--help)
|
|
printUsage "gpgsign"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "gpgsign"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ -z "${sign_files}" ]; then
|
|
logError "Missing arguments, --files is required!\n"
|
|
printUsage "gpgsign"
|
|
exit 1
|
|
fi
|
|
|
|
for f in "${sign_files[@]}"; do
|
|
if [ ! -f "$f" ]; then
|
|
exitError "File '${f}' does not exist or is not a file!"
|
|
fi
|
|
|
|
logInfo "Signing file '${f}' using release key..."
|
|
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
|
|
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Signing failed!"
|
|
fi
|
|
|
|
logInfo "Creating digest for file '${f}'..."
|
|
local rp="$(realpath "$f")"
|
|
local bname="$(basename "$f")"
|
|
(cd "$(dirname "$rp")"; sha256sum "$bname" > "${bname}.DIGEST")
|
|
done
|
|
|
|
logInfo "All done!"
|
|
}
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# appsign command
|
|
# -----------------------------------------------------------------------
|
|
appsign() {
|
|
local sign_files=()
|
|
local signtool_key
|
|
local codesign_identity
|
|
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-f|--files)
|
|
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
|
sign_files+=("$2")
|
|
shift
|
|
done ;;
|
|
|
|
-k|--signtool-key)
|
|
signtool_key="$2"
|
|
shift ;;
|
|
|
|
-i|--identity)
|
|
codesign_identity="$2"
|
|
shift ;;
|
|
|
|
-h|--help)
|
|
printUsage "appsign"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "appsign"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ -z "${sign_files}" ]; then
|
|
logError "Missing arguments, --files is required!\n"
|
|
printUsage "appsign"
|
|
exit 1
|
|
fi
|
|
|
|
for f in "${sign_files[@]}"; do
|
|
if [ ! -f "${f}" ]; then
|
|
exitError "File '${f}' does not exist or is not a file!"
|
|
fi
|
|
done
|
|
|
|
if [ "$(uname -s)" == "Darwin" ]; then
|
|
if [ -z "${codesign_identity}" ]; then
|
|
logError "Missing arguments, --identity is required on macOS!\n"
|
|
printUsage "appsign"
|
|
exit 1
|
|
fi
|
|
|
|
checkCodesignCommandExists
|
|
|
|
local orig_dir="$(pwd)"
|
|
for f in "${sign_files[@]}"; do
|
|
if [[ ${f: -4} == '.dmg' ]]; then
|
|
logInfo "Unpacking disk image '${f}'..."
|
|
local tmp_dir="/tmp/KeePassXC_${RANDOM}"
|
|
mkdir -p ${tmp_dir}/mnt
|
|
hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"
|
|
cd ${tmp_dir}
|
|
cp -a ./mnt ./app
|
|
hdiutil detach -quiet ${tmp_dir}/mnt
|
|
|
|
if [ ! -d ./app/KeePassXC.app ]; then
|
|
cd "${orig_dir}"
|
|
exitError "Unpacking failed!"
|
|
fi
|
|
|
|
logInfo "Signing app using codesign..."
|
|
codesign --sign "${codesign_identity}" --verbose --deep ./app/KeePassXC.app
|
|
|
|
if [ 0 -ne $? ]; then
|
|
cd "${orig_dir}"
|
|
exitError "Signing failed!"
|
|
fi
|
|
|
|
logInfo "Repacking disk image..."
|
|
hdiutil create \
|
|
-volname "KeePassXC" \
|
|
-size $((1000 * ($(du -sk ./app | cut -f1) + 5000))) \
|
|
-srcfolder ./app \
|
|
-fs HFS+ \
|
|
-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}
|
|
else
|
|
logInfo "Skipping non-DMG file '${f}'..."
|
|
fi
|
|
done
|
|
|
|
elif [ "$(uname -o)" == "Msys" ]; then
|
|
if [ -z "${signtool_key}" ]; then
|
|
logError "Missing arguments, --signtool-key is required on Windows!\n"
|
|
printUsage "appsign"
|
|
exit 1
|
|
fi
|
|
|
|
checkOsslsigncodeCommandExists
|
|
|
|
if [[ ! -f "${signtool_key}" ]]; then
|
|
exitError "Key file was not found!"
|
|
fi
|
|
|
|
read -s -p "Key password: " password
|
|
echo
|
|
|
|
for f in "${sign_files[@]}"; do
|
|
if [[ ${f: -4} == ".exe" ]]; then
|
|
logInfo "Signing file '${f}' using osslsigncode..."
|
|
# output a signed exe; we have to use a different name due to osslsigntool limitations
|
|
osslsigncode sign -pkcs12 "${signtool_key}" -pass "${password}" -n "KeePassXC" \
|
|
-t "http://timestamp.comodoca.com/authenticode" -in "${f}" -out "${f}.signed"
|
|
|
|
if [ 0 -ne $? ]; then
|
|
rm -f "${f}.signed"
|
|
exitError "Signing failed!"
|
|
fi
|
|
|
|
# overwrite the original exe with the signed exe
|
|
mv -f "${f}.signed" "${f}"
|
|
elif [[ ${f: -4} == ".msi" ]]; then
|
|
# Make sure we can find the signtool
|
|
checkSigntoolCommandExists
|
|
|
|
# osslsigncode does not succeed at signing MSI files at this time...
|
|
logInfo "Signing file '${f}' using Microsoft signtool..."
|
|
signtool sign -f "${signtool_key}" -p "${password}" -d "KeePassXC" \
|
|
-t "http://timestamp.comodoca.com/authenticode" "${f}"
|
|
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Signing failed!"
|
|
fi
|
|
else
|
|
logInfo "Skipping non-executable file '${f}'..."
|
|
fi
|
|
done
|
|
|
|
else
|
|
exitError "Unsupported platform for code signing!\n"
|
|
fi
|
|
|
|
logInfo "All done!"
|
|
}
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# parse global command line
|
|
# -----------------------------------------------------------------------
|
|
MODE="$1"
|
|
shift
|
|
if [ "" == "$MODE" ]; then
|
|
logError "Missing arguments!\n"
|
|
printUsage
|
|
exit 1
|
|
elif [ "help" == "$MODE" ]; then
|
|
printUsage "$1"
|
|
exit
|
|
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ]; then
|
|
${MODE} "$@"
|
|
else
|
|
printUsage "$MODE"
|
|
fi
|