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

Generate AppImage on GitHub Actions #843

Merged
merged 1 commit into from
Jun 16, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ project( Audacity )
# Load our functions/macros
include( AudacityFunctions )

set_from_env(AUDACITY_ARCH_LABEL) # e.g. x86_64

# Allow user to globally set the library preference
cmd_option( ${_OPT}lib_preference
"Library preference [system (if available), local]"
Expand Down
42 changes: 32 additions & 10 deletions cmake-proxies/cmake-modules/Package.cmake
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

set(CPACK_PACKAGE_VERSION_MAJOR "${AUDACITY_VERSION}") # X
set(CPACK_PACKAGE_VERSION_MINOR "${AUDACITY_RELEASE}") # Y
set(CPACK_PACKAGE_VERSION_PATCH "${AUDACITY_REVISION}") # Z
Expand All @@ -13,30 +12,53 @@ if(NOT AUDACITY_BUILD_LEVEL EQUAL 2)
set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}+${GIT_COMMIT_SHORT}")
endif()

# Audacity-X.Y.Z-alpha-20210615
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
# Custom variables use CPACK_AUDACITY_ prefix. CPACK_ to expose to CPack,
# AUDACITY_ to show it is custom and avoid conflicts with other projects.
set(CPACK_AUDACITY_SOURCE_DIR "${PROJECT_SOURCE_DIR}")

if(CMAKE_SYSTEM_NAME MATCHES "Windows")
set(os "win")
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
set(os "macos")
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(os "linux")
endif()

# audacity-linux-X.Y.Z-alpha-20210615
set(CPACK_PACKAGE_FILE_NAME "audacity-${os}-${CPACK_PACKAGE_VERSION}")
set(zsync_name "audacity-${os}-*") # '*' is wildcard (here it means any version)

if(NOT "$ENV{AUDACITY_ARCH_LABEL}" STREQUAL "")
# Audacity-X.Y.Z-alpha-20210615-x86_64
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-$ENV{AUDACITY_ARCH_LABEL}")
if(DEFINED AUDACITY_ARCH_LABEL)
# audacity-linux-X.Y.Z-alpha-20210615-x86_64
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-${AUDACITY_ARCH_LABEL}")
set(zsync_name "${zsync_name}-${AUDACITY_ARCH_LABEL}")
set(CPACK_AUDACITY_ARCH_LABEL "${AUDACITY_ARCH_LABEL}")
endif()
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}/package")

set(CPACK_GENERATOR ZIP)
set(CPACK_GENERATOR "ZIP")

if( CMAKE_SYSTEM_NAME STREQUAL "Darwin" )
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(CPACK_GENERATOR "External")
set(CPACK_EXTERNAL_ENABLE_STAGING TRUE)
set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${PROJECT_SOURCE_DIR}/linux/package_appimage.cmake")
if(AUDACITY_BUILD_LEVEL EQUAL 2)
# Enable updates. See https://github.com/AppImage/AppImageSpec/blob/master/draft.md#update-information
set(CPACK_AUDACITY_APPIMAGE_UPDATE_INFO "gh-releases-zsync|audacity|audacity|latest|${zsync_name}.AppImage.zsync")
endif()
elseif( CMAKE_SYSTEM_NAME STREQUAL "Darwin" )
set( CPACK_GENERATOR DragNDrop )

set( CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/mac/Resources/Audacity-DMG-background.png")
set( CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_SOURCE_DIR}/scripts/build/macOS/DMGSetup.scpt")

if( ${_OPT}perform_codesign )
set( CPACK_APPLE_CODESIGN_IDENTITY ${APPLE_CODESIGN_IDENTITY} )
set( CPACK_APPLE_NOTARIZATION_USER_NAME ${APPLE_NOTARIZATION_USER_NAME} )
set( CPACK_APPLE_NOTARIZATION_PASSWORD ${APPLE_NOTARIZATION_PASSWORD} )
set( CPACK_APPLE_SIGN_SCRIPTS "${CMAKE_SOURCE_DIR}/scripts/build/macOS" )
set( CPACK_PERFORM_NOTARIZATION ${${_OPT}perform_notarization} )

# CPACK_POST_BUILD_SCRIPTS was added in 3.19, but we only need it on macOS
SET( CPACK_POST_BUILD_SCRIPTS "${CMAKE_SOURCE_DIR}/scripts/build/macOS/DMGSign.cmake" )
endif()
Expand Down
2 changes: 1 addition & 1 deletion help/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ if( NOT CMAKE_SYSTEM_NAME MATCHES "Darwin" )
install( FILES "${_SRCDIR}/audacity.1"
DESTINATION "${_MANDIR}/man1" )
install( FILES "${_SRCDIR}/audacity.appdata.xml"
DESTINATION "${_DATADIR}/appdata" )
DESTINATION "${_DATADIR}/metainfo" )
endif()
endif()
1 change: 0 additions & 1 deletion help/audacity.appdata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
<url type="bugtracker">https://bugzilla.audacityteam.org/</url>
<url type="faq">https://manual.audacityteam.org/man/faq.html</url>
<url type="help">https://manual.audacityteam.org/</url>
<url type="donation">https://www.audacityteam.org/donate/</url>
<url type="translate">https://www.audacityteam.org/community/translators/</url>
<screenshots>
<screenshot type="default">
Expand Down
49 changes: 49 additions & 0 deletions linux/AppRun.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# The AppImage runtime sets some special environment variables. We provide
# default values here in case the user tries to run this script outside an
# AppImage where the variables would otherwise be undefined.
if [[ ! "${APPIMAGE}" || ! "${APPDIR}" ]]; then
export APPIMAGE="$(readlink -f "${0}")"
export APPDIR="$(dirname "${APPIMAGE}")"
fi

export LD_LIBRARY_PATH="${APPDIR}/lib:${LD_LIBRARY_PATH}"

function help()
{
# Normal audacity help
"${APPDIR}/bin/audacity" --help
# Special options handled by this script
cat >&2 <<EOF
--readme display README
--license display LICENSE
--man[ual|page] display audacity(1) manual page
--check-dependencies check library dependency fulfillment (developer tool)

EOF
# Blank line then special options handled by the AppImage runtime
"${APPIMAGE}" --appimage-help
}

# Intercept command line arguments
case "$1" in
-h|--help )
help
;;
--readme )
exec less "${APPDIR}/share/doc/audacity/README.txt"
;;
--license )
exec less "${APPDIR}/share/doc/audacity/LICENSE.txt"
;;
--man|--manual|--manpage )
exec man "${APPDIR}/share/man/man1/audacity.1"
;;
--check-depends|--check-dependencies )
exec bash "${APPDIR}/bin/check_dependencies"
;;
* )
# Other arguments go to Audacity
exec "${APPDIR}/bin/audacity" "$@"
;;
esac
95 changes: 95 additions & 0 deletions linux/check_dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env bash

version="@CPACK_PACKAGE_NAME@ v@CPACK_PACKAGE_VERSION@ @CPACK_AUDACITY_ARCH_LABEL@"

export LC_ALL=C # Using `sort` a lot. Order depends on locale so override it.

# PROBLEM: We want to check dependencies provided by the system, but `ldd`
# looks in the current directory first so will show other package libraries.
# SOLUTION: Copy library to temporary folder and test it there.
function check_file()
{
counter=$(($(cat "$2/.counter")+1))
echo ${counter} > "$2/.counter"
printf >&2 "Done ${counter} of $3.\r"
cp "$1" "$2"
file="$(basename "$1")"
LANG=C LD_LIBRARY_PATH="" "${APPDIR}/bin/ldd_recursive" -uniq "$2/${file}" 2>/dev/null
rm "$2/${file}" # delete library before we test the next one
}
export -f check_file # make function available in Bash subprocesses

function prep_result()
{
sed -n "s|$2||p" "$1" | sort -f | tee "$3" | wc -l
}

if ! which less >/dev/null; then
function less() { cat "$@"; } # use `cat` if `less` is unavailable
fi

tmp="$(mktemp -d)"
trap "rm -rf '${tmp}'" EXIT

cd "${APPDIR}"

find . -executable -type f \! -name "lib*.so*" > "${tmp}/exes.txt"
find . -name "lib*.so*" \! -type l > "${tmp}/libs.txt"

num_exes="$(<"${tmp}/exes.txt" xargs -n1 basename 2>/dev/null | tee "${tmp}/exes2.txt" | wc -l)"
num_libs="$(<"${tmp}/libs.txt" xargs -n1 basename 2>/dev/null | tee "${tmp}/libs2.txt" | wc -l)"

echo >&2 "AppImage contains ${num_exes} executables and ${num_libs} libraries."

echo >&2 "Checking dependencies for executables and libraries..."
include_libs="${tmp}/libs.txt"
num_includes="$((${num_libs}+${num_exes}))"

# Check dependencies against system. See 'check_file' function.
echo 0 > "${tmp}/.counter"
cat "${tmp}/exes.txt" "${include_libs}" | xargs -n1 -I '%%%' bash -c \
'check_file "${0}" "${1}" "${2}"' "%%%" "${tmp}" "${num_includes}" \; \
| sort | uniq > "${tmp}/deps.txt"
echo >&2 "Processing results."

mv "${tmp}/libs2.txt" "${tmp}/libs.txt"
mv "${tmp}/exes2.txt" "${tmp}/exes.txt"

# Have only checked system libraries. Now consider those in package:
<"${tmp}/libs.txt" xargs -n1 -I '%%%' sed -i 's|^%%% => not found$|%%% => package|' "${tmp}/deps.txt"
<"${tmp}/libs.txt" xargs -n1 -I '%%%' sed -i 's|%%%$|%%% => both|' "${tmp}/deps.txt"

# Remaining dependencies must be system:
sed -ri 's/^(.*[^(not found|package|both)])$/\1 => system/' "${tmp}/deps.txt"

num_package=$(prep_result "${tmp}/deps.txt" ' => package$' "${tmp}/package.txt")
num_system=$(prep_result "${tmp}/deps.txt" ' => system$' "${tmp}/system.txt")
num_both=$(prep_result "${tmp}/deps.txt" ' => both$' "${tmp}/both.txt")
num_neither=$(prep_result "${tmp}/deps.txt" ' => not found$' "${tmp}/neither.txt")

# Any libraries included in package that don't appear in 'deps.txt' **might**
# not actually be needed. Careful: they might be needed by a plugin!
num_extra=$(<"${tmp}/libs.txt" xargs -n1 -I '%%%' sh -c \
"grep -q '%%%' \"${tmp}/deps.txt\" || echo '%%%'" \
| sort -f | tee "${tmp}/extra.txt" | wc -l)

less <<EOF
# Package: ${version}
# System: $(uname -srmo)
$(cat /etc/*release*)

# In package only: ${num_package}
$(cat "${tmp}/package.txt")

# System only: ${num_system}
$(cat "${tmp}/system.txt")

# Provided by both: ${num_both}
$(cat "${tmp}/both.txt")

# Provided by neither: ${num_neither}
$(cat "${tmp}/neither.txt")

# Extra: ${num_extra} (In package but unlinked. Possibly needed by plugins.)
$(cat "${tmp}/extra.txt")
EOF
113 changes: 113 additions & 0 deletions linux/create_appimage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env bash

((${BASH_VERSION%%.*} >= 4)) || { echo >&2 "$0: Error: Please upgrade Bash."; exit 1; }

set -euxo pipefail

readonly appdir="$1" # input path (Audacity install directory)
readonly appimage="$2" # output path to use for created AppImage

#============================================================================
# Helper functions
#============================================================================

function download_github_release()
{
local -r repo_slug="$1" release_tag="$2" file="$3"
wget -q --show-progress "https://github.com/${repo_slug}/releases/download/${release_tag}/${file}"
chmod +x "${file}"
}

function extract_appimage()
{
# Extract AppImage so we can run it without having to install FUSE
local -r image="$1" binary_name="$2"
local -r dir="${image%.AppImage}.AppDir"
"./${image}" --appimage-extract >/dev/null # dest folder "squashfs-root"
mv squashfs-root "${dir}" # rename folder to avoid collisions
ln -s "${dir}/AppRun" "${binary_name}" # symlink for convenience
rm -f "${image}"
}

function download_appimage_release()
{
local -r github_repo_slug="$1" binary_name="$2" tag="$3"
local -r image="${binary_name}-x86_64.AppImage"
download_github_release "${github_repo_slug}" "${tag}" "${image}"
extract_appimage "${image}" "${binary_name}"
}

function download_linuxdeploy_component()
{
local -r component="$1" tag="$2"
download_appimage_release "linuxdeploy/$1" "$1" "$2"
}

function create_path()
{
local -r path="$1"
if [[ -d "${path}" ]]; then
return 1 # already exists
fi
mkdir -p "${path}"
}

#============================================================================
# Fetch AppImage packaging tools
#============================================================================

if create_path "appimagetool"; then
(
cd "appimagetool"
download_appimage_release AppImage/AppImageKit appimagetool continuous
)
fi
export PATH="${PWD%/}/appimagetool:${PATH}"
appimagetool --version

if create_path "linuxdeploy"; then
(
cd "linuxdeploy"
download_linuxdeploy_component linuxdeploy continuous
)
fi
export PATH="${PWD%/}/linuxdeploy:${PATH}"
linuxdeploy --list-plugins

#============================================================================
# Create symlinks
#============================================================================

ln -sf --no-dereference . "${appdir}/usr"
ln -sf share/applications/audacity.desktop "${appdir}/audacity.desktop"
ln -sf share/icons/hicolor/scalable/apps/audacity.svg "${appdir}/audacity.svg"
ln -sf share/icons/hicolor/scalable/apps/audacity.svg "${appdir}/.DirIcon"

#============================================================================
# Bundle dependencies
#============================================================================

# HACK: Some wxWidget libraries depend on themselves. Add
# them to LD_LIBRARY_PATH so that linuxdeploy can find them.
export LD_LIBRARY_PATH="${appdir}/usr/lib/audacity:${LD_LIBRARY_PATH-}"

linuxdeploy --appdir "${appdir}" # add all shared library dependencies

#============================================================================
# Build AppImage
#============================================================================

appimagetool_args=(
# none
)

if [[ "${AUDACITY_UPDATE_INFO-}" ]]; then
# Enable updates. See https://github.com/AppImage/AppImageSpec/blob/master/draft.md#update-information
appimagetool_args+=( --updateinformation="${AUDACITY_UPDATE_INFO}" )
else
echo >&2 "$0: Automatic updates disabled"
fi

# Create AppImage
cd "$(dirname "${appimage}")" # otherwise zsync created in wrong directory
appimagetool "${appimagetool_args[@]}" "${appdir}" "${appimage}"