diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9c3e6560f8b..293f5dab687 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,8 +5,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - linux: - name: linux + linux-x86_64: + name: linux-x86_64 runs-on: ubuntu-latest container: ghcr.io/lmms/linux.gcc:20.04 env: @@ -39,7 +39,6 @@ jobs: source /opt/qt5*/bin/qt5*-env.sh || true cmake -S . \ -B build \ - -DCMAKE_INSTALL_PREFIX=./install \ $CMAKE_OPTS - name: Build run: cmake --build build @@ -49,8 +48,7 @@ jobs: ctest --output-on-failure -j2 - name: Package run: | - cmake --build build --target install - cmake --build build --target appimage + cmake --build build --target package - name: Upload artifacts uses: actions/upload-artifact@v4 with: @@ -65,6 +63,71 @@ jobs: ccache --show-stats env: CCACHE_MAXSIZE: 500M + linux-arm64: + name: linux-arm64 + runs-on: ubuntu-24.04-arm + env: + CMAKE_OPTS: >- + -DUSE_WERROR=ON + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DUSE_COMPILE_CACHE=ON + -DWANT_DEBUG_CPACK=ON + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 + MAKEFLAGS: -j2 + DEBIAN_FRONTEND: noninteractive + steps: + - name: Configure git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Check out + uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: recursive + - name: Install system packages + run: | + sudo apt-get update -y + sudo apt-get install -y --no-install-recommends \ + $(xargs < .github/workflows/deps-ubuntu-24.04-gcc.txt) + - name: Cache ccache data + uses: actions/cache@v3 + with: + key: ccache-${{ github.job }}-${{ github.ref }}-${{ github.run_id }} + restore-keys: | + ccache-${{ github.job }}-${{ github.ref }}- + ccache-${{ github.job }}- + path: ~/.ccache + - name: Configure + run: | + ccache --zero-stats + cmake -S . \ + -B build \ + -DWANT_VST_32=OFF \ + -DWANT_VST_64=OFF \ + $CMAKE_OPTS + - name: Build + run: cmake --build build + - name: Run tests + run: | + cd build/tests + ctest --output-on-failure -j2 + - name: Package + run: | + cmake --build build --target package + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: linux-arm64 + path: build/lmms-*.AppImage + - name: Trim ccache and print statistics + run: | + ccache --cleanup + echo "[ccache config]" + ccache --show-config + echo "[ccache stats]" + ccache --show-stats + env: + CCACHE_MAXSIZE: 500M macos: strategy: fail-fast: false @@ -129,7 +192,6 @@ jobs: mkdir build cmake -S . \ -B build \ - -DCMAKE_INSTALL_PREFIX="../target" \ -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5)" \ -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \ $CMAKE_OPTS \ @@ -142,8 +204,7 @@ jobs: ctest --output-on-failure -j3 - name: Package run: | - cmake --build build --target install - cmake --build build --target dmg + cmake --build build --target package - name: Upload artifacts uses: actions/upload-artifact@v4 with: @@ -208,7 +269,6 @@ jobs: ccache --zero-stats cmake -S . \ -B build \ - -DCMAKE_INSTALL_PREFIX=./install \ -DCMAKE_TOOLCHAIN_FILE="./cmake/toolchains/MinGW-W64-64.cmake" \ $CMAKE_OPTS - name: Build diff --git a/.github/workflows/deps-ubuntu-24.04-gcc.txt b/.github/workflows/deps-ubuntu-24.04-gcc.txt new file mode 100644 index 00000000000..f1b705eeb4c --- /dev/null +++ b/.github/workflows/deps-ubuntu-24.04-gcc.txt @@ -0,0 +1,52 @@ +binutils +ca-certificates +ccache +cmake +file +fluid +gcc +git +gpg +g++ +libasound2-dev +libc6-dev +libfftw3-dev +libfltk1.3-dev +libfluidsynth-dev +libgig-dev +libgtk2.0-0 +libjack-jackd2-dev +liblilv-dev +liblist-moreutils-perl +libmp3lame-dev +libogg-dev +libqt5svg5-dev +libqt5x11extras5-dev +libsamplerate0-dev +libsdl2-dev +libsndfile1-dev +libsoundio-dev +libstk-dev +libsuil-dev +libvorbis-dev +libx11-xcb-dev +libxcb-keysyms1-dev +libxcb-util0-dev +libxft-dev +libxinerama-dev +libxml2-utils +libxml-perl +lsb-release +lv2-dev +make +perl +portaudio19-dev +qt5-qmake +qtbase5-dev +qtbase5-dev-tools +qtbase5-private-dev +qttools5-dev-tools +software-properties-common +ssh-client +stk +wget diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bad6de0ac5..c612332f8f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,8 @@ option(WANT_DEBUG_MSAN "Enable MemorySanitizer" OFF) option(WANT_DEBUG_UBSAN "Enable UndefinedBehaviorSanitizer" OFF) option(WANT_DEBUG_GPROF "Enable gprof profiler" OFF) OPTION(BUNDLE_QT_TRANSLATIONS "Install Qt translation files for LMMS" OFF) +option(WANT_DEBUG_CPACK "Show detailed logs for packaging commands" OFF) +option(WANT_CPACK_TARBALL "Request CPack to create a tarball instead of an installer" OFF) IF(LMMS_BUILD_APPLE) @@ -267,6 +269,7 @@ IF(WANT_SUIL) IF(SUIL_FOUND) SET(LMMS_HAVE_SUIL TRUE) SET(STATUS_SUIL "OK") + find_package(SuilModules) ELSE() SET(STATUS_SUIL "not found, install it or set PKG_CONFIG_PATH appropriately") ENDIF() @@ -605,6 +608,17 @@ ELSE() SET (STATUS_DEBUG_FPE "Disabled") ENDIF(WANT_DEBUG_FPE) +if(WANT_DEBUG_CPACK) + if((LMMS_BUILD_WIN32 AND CMAKE_VERSION VERSION_LESS "3.19") OR WANT_CPACK_TARBALL) + set(STATUS_DEBUG_CPACK "Wanted but disabled due to unsupported configuration") + else() + set(CPACK_DEBUG TRUE) + set(STATUS_DEBUG_CPACK "Enabled") + endif() +else() + set(STATUS_DEBUG_CPACK "Disabled") +endif() + # check for libsamplerate FIND_PACKAGE(Samplerate 0.1.8 MODULE REQUIRED) @@ -678,7 +692,7 @@ if(WANT_DEBUG_GPROF) add_link_options(-pg) set(STATUS_GPROF "OK") else() - set(STATUS_GPROF "DISABLED ${STATUS_GPROF}") + set(STATUS_GPROF "Disabled ${STATUS_GPROF}") endif() # add enabled sanitizers @@ -744,7 +758,7 @@ IF(LMMS_BUILD_LINUX) "${CMAKE_BINARY_DIR}/lmmsconfig.h" "${CMAKE_BINARY_DIR}/lmmsversion.h" "${CMAKE_SOURCE_DIR}/src/gui/embed.cpp" - DESTINATION "${CMAKE_INSTALL_PREFIX}/include/lmms/") + DESTINATION "include/lmms/") ENDIF(LMMS_BUILD_LINUX) # @@ -848,6 +862,7 @@ MESSAGE( "* Debug using ThreadSanitizer : ${STATUS_DEBUG_TSAN}\n" "* Debug using MemorySanitizer : ${STATUS_DEBUG_MSAN}\n" "* Debug using UBSanitizer : ${STATUS_DEBUG_UBSAN}\n" +"* Debug packaging commands : ${STATUS_DEBUG_CPACK}\n" "* Profile using GNU profiler : ${STATUS_GPROF}\n" ) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 833fad5819f..439a68852b4 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -14,10 +14,14 @@ ENDIF() SET(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME_UCASE}") SET(CPACK_SOURCE_GENERATOR "TBZ2") SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${VERSION}") -IF(NOT DEFINED WIN32) - SET(CPACK_STRIP_FILES "bin/${CMAKE_PROJECT_NAME};${PLUGIN_DIR}/*.so") - SET(CPACK_PACKAGE_EXECUTABLES "${CMAKE_PROJECT_NAME}" "${PROJECT_NAME_UCASE} binary") -ENDIF() +SET(CPACK_PACKAGE_EXECUTABLES "${CMAKE_PROJECT_NAME}" "${PROJECT_NAME_UCASE} binary") + +# Disable strip for Debug|RelWithDebInfo +if(CMAKE_BUILD_TYPE MATCHES "Deb") + unset(CPACK_STRIP_FILES) +else() + set(CPACK_STRIP_FILES TRUE) +endif() IF(LMMS_BUILD_WIN32) ADD_SUBDIRECTORY(nsis) diff --git a/cmake/apple/CMakeLists.txt b/cmake/apple/CMakeLists.txt index 3fd0a4da457..71be84f8183 100644 --- a/cmake/apple/CMakeLists.txt +++ b/cmake/apple/CMakeLists.txt @@ -1,24 +1,3 @@ -SET(MACOSX_BUNDLE_ICON_FILE "icon.icns") -SET(MACOSX_BUNDLE_GUI_IDENTIFIER "${PROJECT_NAME_UCASE}") -SET(MACOSX_BUNDLE_LONG_VERSION_STRING "${VERSION}") -SET(MACOSX_BUNDLE_BUNDLE_NAME "${PROJECT_NAME_UCASE}") -SET(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VERSION}") -SET(MACOSX_BUNDLE_BUNDLE_VERSION "${VERSION}") -SET(MACOSX_BUNDLE_COPYRIGHT "${PROJECT_COPYRIGHT}") -SET(MACOSX_BUNDLE_MIMETYPE "application/x-lmms-project") -SET(MACOSX_BUNDLE_MIMETYPE_ICON "project.icns") -SET(MACOSX_BUNDLE_MIMETYPE_ID "io.lmms") -SET(MACOSX_BUNDLE_PROJECT_URL "${PROJECT_URL}") -SET(MACOSX_BUNDLE_DMG_TITLE "${MACOSX_BUNDLE_BUNDLE_NAME} ${MACOSX_BUNDLE_LONG_VERSION_STRING}") - -# FIXME: appdmg won't allow volume names > 27 char -# See also https://github.com/LinusU/node-appdmg/issues/48 -STRING(SUBSTRING "${MACOSX_BUNDLE_DMG_TITLE}" 0 27 MACOSX_BUNDLE_DMG_TITLE) - -CONFIGURE_FILE("lmms.plist.in" "${CMAKE_BINARY_DIR}/Info.plist") -CONFIGURE_FILE("install_apple.sh.in" "${CMAKE_BINARY_DIR}/install_apple.sh" @ONLY) -CONFIGURE_FILE("package_apple.json.in" "${CMAKE_BINARY_DIR}/package_apple.json" @ONLY) - IF(CMAKE_OSX_ARCHITECTURES) SET(DMG_ARCH "${CMAKE_OSX_ARCHITECTURES}") ELSEIF(IS_ARM64) @@ -29,18 +8,31 @@ ELSE() SET(DMG_ARCH "x86_64") ENDIF() -# DMG creation target -SET(DMG_FILE "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}-${VERSION}-mac${APPLE_OS_VER}-${DMG_ARCH}.dmg") - -FILE(REMOVE "${DMG_FILE}") -ADD_CUSTOM_TARGET(removedmg - COMMAND touch "\"${DMG_FILE}\"" && rm "\"${DMG_FILE}\"" - COMMENT "Removing old DMG") -ADD_CUSTOM_TARGET(dmg - COMMAND appdmg "\"${CMAKE_BINARY_DIR}/package_apple.json\"" "\"${DMG_FILE}\"" - DEPENDS "${CMAKE_BINARY_DIR}/package_apple.json" - COMMENT "Generating DMG") -ADD_DEPENDENCIES(dmg removedmg) +# Standard CPack options +set(CPACK_GENERATOR "External" PARENT_SCOPE) +set(CPACK_EXTERNAL_ENABLE_STAGING TRUE PARENT_SCOPE) +set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${VERSION}-mac${APPLE_OS_VER}-${DMG_ARCH}") +set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}" PARENT_SCOPE) +set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_SOURCE_DIR}/MacDeployQt.cmake" PARENT_SCOPE) +# disable cpack's strip: causes missing symbols on macOS +set(CPACK_STRIP_FILES_ORIG "${CPACK_STRIP_FILES}" PARENT_SCOPE) +set(CPACK_STRIP_FILES FALSE PARENT_SCOPE) -# see also ../postinstall/CMakeLists.txt +# Custom vars to expose to Cpack +# must be prefixed with "CPACK_" per https://stackoverflow.com/a/46526757/3196753) +set(CPACK_CURRENT_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" PARENT_SCOPE) +set(CPACK_CURRENT_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE) +set(CPACK_BINARY_DIR "${CMAKE_BINARY_DIR}" PARENT_SCOPE) +set(CPACK_SOURCE_DIR "${CMAKE_SOURCE_DIR}" PARENT_SCOPE) +set(CPACK_QMAKE_EXECUTABLE "${QT_QMAKE_EXECUTABLE}" PARENT_SCOPE) +set(CPACK_CARLA_LIBRARIES "${CARLA_LIBRARIES}" PARENT_SCOPE) +set(CPACK_PROJECT_NAME "${PROJECT_NAME}" PARENT_SCOPE) +set(CPACK_PROJECT_VERSION "${VERSION}" PARENT_SCOPE) +set(CPACK_PROJECT_NAME_UCASE "${PROJECT_NAME_UCASE}" PARENT_SCOPE) +set(CPACK_PROJECT_URL "${PROJECT_URL}" PARENT_SCOPE) +set(CPACK_SUIL_MODULES "${Suil_MODULES}" PARENT_SCOPE) +set(CPACK_SUIL_MODULES_PREFIX "${Suil_MODULES_PREFIX}" PARENT_SCOPE) +if(CMAKE_VERSION VERSION_LESS "3.19") + message(WARNING "DMG creation requires at least CMake 3.19") +endif() diff --git a/cmake/apple/MacDeployQt.cmake b/cmake/apple/MacDeployQt.cmake new file mode 100644 index 00000000000..bee22cf42d9 --- /dev/null +++ b/cmake/apple/MacDeployQt.cmake @@ -0,0 +1,172 @@ +# Create a macOS .dmg desktop installer using macdeployqt +# +# Copyright (c) 2025, Tres Finocchiaro, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# Variables must be prefixed with "CPACK_" to be visible here +set(lmms "${CPACK_PROJECT_NAME}") +set(APP "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${CPACK_PROJECT_NAME_UCASE}.app") + +# Toggle command echoing & verbosity +# 0 = no output, 1 = error/warning, 2 = normal, 3 = debug +if(NOT CPACK_DEBUG) + set(VERBOSITY 1) + set(COMMAND_ECHO NONE) +else() + set(VERBOSITY 2) + set(COMMAND_ECHO STDOUT) +endif() + +# Detect release|debug build +if(NOT CPACK_STRIP_FILES_ORIG) + # -use-debug-libs implies -no-strip + if(CPACK_QMAKE_EXECUTABLE MATCHES "/homebrew/|/usr/local") + message(STATUS "Homebrew does not provide debug qt libraries, replacing \"-use-debug-libs\" with \"-no-strip\" instead") + set(USE_DEBUG_LIBS -no-strip) + else() + set(USE_DEBUG_LIBS -use-debug-libs) + endif() +endif() + +# Cleanup CPack "External" json, txt files, old DMG files +file(GLOB cleanup "${CPACK_BINARY_DIR}/${lmms}-*.json" + "${CPACK_BINARY_DIR}/${lmms}-*.dmg" + "${CPACK_BINARY_DIR}/install_manifest.txt") +list(SORT cleanup) +file(REMOVE ${cleanup}) + +# Create bundle structure +file(MAKE_DIRECTORY "${APP}/Contents/MacOS") +file(MAKE_DIRECTORY "${APP}/Contents/Frameworks") +file(MAKE_DIRECTORY "${APP}/Contents/Resources") + +# Fix layout +file(RENAME "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/lib" "${APP}/Contents/lib") +file(RENAME "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/share" "${APP}/Contents/share") +file(RENAME "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/bin" "${APP}/Contents/bin") + +# Move binaries into Contents/MacOS +file(RENAME "${APP}/Contents/bin/${lmms}" "${APP}/Contents/MacOS/${lmms}") +file(RENAME "${APP}/Contents/lib/${lmms}/RemoteZynAddSubFx" "${APP}/Contents/MacOS/RemoteZynAddSubFx") +file(REMOVE_RECURSE "${APP}/Contents/bin") +file(REMOVE_RECURSE "${APP}/Contents/share/man1") +file(REMOVE_RECURSE "${APP}/Contents/include") + +# Copy missing files +# Convert https://lmms.io to io.lmms +string(REPLACE "." ";" mime_parts "${CPACK_PROJECT_URL}") +string(REPLACE ":" ";" mime_parts "${mime_parts}") +string(REPLACE "/" "" mime_parts "${mime_parts}") +list(REMOVE_AT mime_parts 0) +list(REVERSE mime_parts) +list(JOIN mime_parts "." MACOS_MIMETYPE_ID) +configure_file("${CPACK_CURRENT_SOURCE_DIR}/lmms.plist.in" "${APP}/Contents/Info.plist" @ONLY) +file(COPY "${CPACK_CURRENT_SOURCE_DIR}/project.icns" DESTINATION "${APP}/Contents/Resources") +file(COPY "${CPACK_CURRENT_SOURCE_DIR}/icon.icns" DESTINATION "${APP}/Contents/Resources") +file(RENAME "${APP}/Contents/Resources/icon.icns" "${APP}/Contents/Resources/${lmms}.icns") + +# Copy Suil modules +if(CPACK_SUIL_MODULES) + set(SUIL_MODULES_TARGET "${APP}/Contents/Frameworks/${CPACK_SUIL_MODULES_PREFIX}") + file(MAKE_DIRECTORY "${SUIL_MODULES_TARGET}") + file(COPY ${CPACK_SUIL_MODULES} DESTINATION "${SUIL_MODULES_TARGET}") +endif() + +# Make all libraries writable for macdeployqt +file(CHMOD_RECURSE "${APP}/Contents" PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_WRITE GROUP_READ + WORLD_READ) + +# Qt6: Fix @rpath bug https://github.com/orgs/Homebrew/discussions/2823 +include(CreateSymlink) +get_filename_component(QTBIN "${CPACK_QMAKE_EXECUTABLE}" DIRECTORY) +get_filename_component(QTDIR "${QTBIN}" DIRECTORY) +create_symlink("${QTDIR}/lib" "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/lib") + +# Replace @rpath with @loader_path for Carla +execute_process(COMMAND install_name_tool -change + "@rpath/libcarlabase.dylib" + "@loader_path/libcarlabase.dylib" + "${APP}/Contents/lib/${lmms}/libcarlapatchbay.so" + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) +execute_process(COMMAND install_name_tool -change + "@rpath/libcarlabase.dylib" + "@loader_path/libcarlabase.dylib" + "${APP}/Contents/lib/${lmms}/libcarlarack.so" + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) + +# Build list of executables to inform macdeployqt about +# e.g. -executable=foo.dylib -executable=bar.dylib +file(GLOB LIBS "${APP}/Contents/lib/${lmms}/*.so") + +# Inform macdeployqt about LADSPA plugins; may depend on bundled fftw3f, etc. +file(GLOB LADSPA "${APP}/Contents/lib/${lmms}/ladspa/*.so") + +# Inform linuxdeploy about remote plugins +list(APPEND REMOTE_PLUGINS "${APP}/Contents/MacOS/*Remote*") + +# Collect, sort and dedupe all libraries +list(APPEND LIBS ${LADSPA}) +list(APPEND LIBS ${REMOTE_PLUGINS}) +list(APPEND LIBS ${CPACK_SUIL_MODULES}) +list(REMOVE_DUPLICATES LIBS) +list(SORT LIBS) + +# Construct macdeployqt parameters +foreach(_lib IN LISTS LIBS) + if(EXISTS "${_lib}") + list(APPEND EXECUTABLES "-executable=${_lib}") + endif() +endforeach() + +# Call macdeployqt +get_filename_component(QTBIN "${CPACK_QMAKE_EXECUTABLE}" DIRECTORY) +message(STATUS "Calling ${QTBIN}/macdeployqt ${APP} [... executables]") +execute_process(COMMAND "${QTBIN}/macdeployqt" "${APP}" ${EXECUTABLES} + -verbose=${VERBOSITY} + ${USE_DEBUG_LIBS} + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) + +# Cleanup symlink +file(REMOVE "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/lib") + +# Remove dummy carla libs, relink to a sane location (e.g. /Applications/Carla.app/...) +# (must be done after calling macdeployqt) +file(GLOB CARLALIBS "${APP}/Contents/lib/${lmms}/libcarla*") +foreach(_carlalib IN LISTS CARLALIBS) + foreach(_lib "${CPACK_CARLA_LIBRARIES}") + set(_oldpath "../../Frameworks/lib${_lib}.dylib") + set(_newpath "Carla.app/Contents/MacOS/lib${_lib}.dylib") + execute_process(COMMAND install_name_tool -change + "@loader_path/${_oldpath}" + "@executable_path/../../../${_newpath}" + "${_carlalib}" + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) + file(REMOVE "${APP}/Contents/Frameworks/lib${_lib}.dylib") + endforeach() +endforeach() + +# Call ad-hoc codesign manually (CMake offers this as well) +execute_process(COMMAND codesign --force --deep --sign - "${APP}" + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) + +# Create DMG +# appdmg won't allow volume names > 27 char https://github.com/LinusU/node-alias/issues/7 +find_program(APPDMG_BIN appdmg REQUIRED) +string(SUBSTRING "${CPACK_PROJECT_NAME_UCASE} ${CPACK_PROJECT_VERSION}" 0 27 APPDMG_VOLUME_NAME) +# We'll configure this file twice (again in MacDeployQt.cmake once we know CPACK_TEMPORARY_INSTALL_DIRECTORY) +configure_file("${CPACK_CURRENT_SOURCE_DIR}/appdmg.json.in" "${CPACK_CURRENT_BINARY_DIR}/appdmg.json" @ONLY) + +execute_process(COMMAND "${APPDMG_BIN}" + "${CPACK_CURRENT_BINARY_DIR}/appdmg.json" + "${CPACK_BINARY_DIR}/${CPACK_PACKAGE_FILE_NAME}.dmg" + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) \ No newline at end of file diff --git a/cmake/apple/appdmg.json.in b/cmake/apple/appdmg.json.in new file mode 100644 index 00000000000..6e4f048a6e5 --- /dev/null +++ b/cmake/apple/appdmg.json.in @@ -0,0 +1,9 @@ +{ + "title": "@APPDMG_VOLUME_NAME@", + "background": "@CPACK_SOURCE_DIR@/cmake/apple/background.png", + "icon-size": 128, + "contents": [ + { "x": 139, "y": 200, "type": "file", "path": "@CPACK_TEMPORARY_INSTALL_DIRECTORY@/@CPACK_PROJECT_NAME_UCASE@.app" }, + { "x": 568, "y": 200, "type": "link", "path": "/Applications" } + ] +} diff --git a/cmake/apple/dmg_branding.png b/cmake/apple/background.png similarity index 100% rename from cmake/apple/dmg_branding.png rename to cmake/apple/background.png diff --git a/cmake/apple/background@2x.png b/cmake/apple/background@2x.png new file mode 100644 index 00000000000..da44ceb8b63 Binary files /dev/null and b/cmake/apple/background@2x.png differ diff --git a/cmake/apple/dmg_branding@2x.png b/cmake/apple/dmg_branding@2x.png deleted file mode 100644 index 7af39fecd47..00000000000 Binary files a/cmake/apple/dmg_branding@2x.png and /dev/null differ diff --git a/cmake/apple/install_apple.sh.in b/cmake/apple/install_apple.sh.in deleted file mode 100644 index fc27d78b30a..00000000000 --- a/cmake/apple/install_apple.sh.in +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash -# Creates Apple ".app" bundle for @PROJECT_NAME_UCASE@ -# Note: -# Examine linkings using `otool -L somelib.so` -# Debug the loading of dynamic libraries using `export DYLD_PRINT_LIBRARIES=1` - -set -e - -# Place to create ".app" bundle -APP="@CMAKE_BINARY_DIR@/@PROJECT_NAME_UCASE@.app" - -MSG_COLOR='\x1B[1;36m' -COLOR_RESET='\x1B[0m' -echo -e "$MSG_COLOR\n\nCreating App Bundle \"$APP\"...$COLOR_RESET" - -qtpath="$(dirname "@QT_QMAKE_EXECUTABLE@")" -export PATH="$PATH:$qtpath" - -# Remove any old .app bundles -rm -Rf "$APP" - -# Copy/overwrite Info.plist -command cp "@CMAKE_BINARY_DIR@/Info.plist" "@CMAKE_INSTALL_PREFIX@/" - -# Create .app bundle containing contents from CMAKE_INSTALL_PREFIX -mkdir -p "$APP/Contents/MacOS" -mkdir -p "$APP/Contents/Frameworks" -mkdir -p "$APP/Contents/Resources" -cd "@CMAKE_INSTALL_PREFIX@" -cp -R ./* "$APP/Contents" -cp "@CMAKE_SOURCE_DIR@/cmake/apple/"*.icns "$APP/Contents/Resources/" - -# Make all libraries writable for macdeployqt -cd "$APP" -find . -type f -print0 | xargs -0 chmod u+w - -lmmsbin="MacOS/@CMAKE_PROJECT_NAME@" -zynbin="MacOS/RemoteZynAddSubFx" - -# Move lmms binary -mv "$APP/Contents/bin/@CMAKE_PROJECT_NAME@" "$APP/Contents/$lmmsbin" - -# Fix zyn linking -mv "$APP/Contents/lib/lmms/RemoteZynAddSubFx" "$APP/Contents/$zynbin" - -# Replace @rpath with @loader_path for Carla -# See also plugins/CarlaBase/CMakeLists.txt -# This MUST be done BEFORE calling macdeployqt -install_name_tool -change @rpath/libcarlabase.dylib \ - @loader_path/libcarlabase.dylib \ - "$APP/Contents/lib/lmms/libcarlapatchbay.so" - -install_name_tool -change @rpath/libcarlabase.dylib \ - @loader_path/libcarlabase.dylib \ - "$APP/Contents/lib/lmms/libcarlarack.so" - -# Link lmms binary -_executables="${_executables} -executable=$APP/Contents/$zynbin" - -# Build a list of shared objects in target/lib/lmms -for file in "$APP/Contents/lib/lmms/"*.so; do - _thisfile="$APP/Contents/lib/lmms/${file##*/}" - _executables="${_executables} -executable=$_thisfile" -done - -# Build a list of shared objects in target/lib/lmms/ladspa -for file in "$APP/Contents/lib/lmms/ladspa/"*.so; do - _thisfile="$APP/Contents/lib/lmms/ladspa/${file##*/}" - _executables="${_executables} -executable=$_thisfile" -done - -# Finalize .app -# shellcheck disable=SC2086 -macdeployqt "$APP" $_executables - -# Carla is a standalone plugin. Remove library, look for it side-by-side LMMS.app -# This MUST be done AFTER calling macdeployqt -# -# For example: -# /Applications/LMMS.app -# /Applications/Carla.app -carlalibs=$(echo "@CARLA_LIBRARIES@"|tr ";" "\n") - -# Loop over all libcarlas, fix linking -for file in "$APP/Contents/lib/lmms/"libcarla*; do - _thisfile="$APP/Contents/lib/lmms/${file##*/}" - for lib in $carlalibs; do - _oldpath="../../Frameworks/lib${lib}.dylib" - _newpath="Carla.app/Contents/MacOS/lib${lib}.dylib" - # shellcheck disable=SC2086 - install_name_tool -change @loader_path/$_oldpath \ - @executable_path/../../../$_newpath \ - "$_thisfile" - rm -f "$APP/Contents/Frameworks/lib${lib}.dylib" - done -done - -# Cleanup -rm -rf "$APP/Contents/bin" - -# Codesign -codesign --force --deep --sign - "$APP" - -echo -e "\nFinished.\n\n" diff --git a/cmake/apple/lmms.plist.in b/cmake/apple/lmms.plist.in index 88fe0b0bf06..dd656a1d5af 100644 --- a/cmake/apple/lmms.plist.in +++ b/cmake/apple/lmms.plist.in @@ -7,13 +7,13 @@ English CFBundleIconFile - @MACOSX_BUNDLE_ICON_FILE@ + @CPACK_PROJECT_NAME@ CFBundlePackageType APPL CFBundleGetInfoString - @MACOSX_BUNDLE_GUI_IDENTIFIER@ @MACOSX_BUNDLE_LONG_VERSION_STRING@ + @CPACK_PROJECT_NAME_UCASE@ @CPACK_PROJECT_VERSION@ CFBundleSignature - @MACOSX_BUNDLE_GUI_IDENTIFIER@ + @CPACK_PROJECT_NAME_UCASE@ CFBundleExecutable - @MACOSX_BUNDLE_GUI_IDENTIFIER@ + @CPACK_PROJECT_NAME@ CFBundleVersion - @MACOSX_BUNDLE_LONG_VERSION_STRING@ + @CPACK_PROJECT_VERSION@ CFBundleShortVersionString - @MACOSX_BUNDLE_LONG_VERSION_STRING@ + @VERSIONCPACK_PROJECT_VERSION@ CFBundleName - @MACOSX_BUNDLE_BUNDLE_NAME@ + @CPACK_PROJECT_NAME_UCASE@ CFBundleInfoDictionaryVersion 6.0 CFBundleIdentifier - @MACOSX_BUNDLE_MIMETYPE_ID@ + @MACOS_MIMETYPE_ID@ CFBundleDocumentTypes - + CFBundleTypeExtensions mmpz CFBundleTypeIconFile - @MACOSX_BUNDLE_MIMETYPE_ICON@ + project CFBundleTypeName - @MACOSX_BUNDLE_GUI_IDENTIFIER@ Project + @CPACK_PROJECT_NAME_UCASE@ Project CFBundleTypeOSTypes mmpz @@ -69,19 +69,19 @@ Editor CFBundleTypeMIMETypes - @MACOSX_BUNDLE_MIMETYPE@ + application/x-@CPACK_PROJECT_NAME@-project - + CFBundleTypeExtensions mmp CFBundleTypeIconFile - @MACOSX_BUNDLE_MIMETYPE_ICON@ + project CFBundleTypeName - @MACOSX_BUNDLE_GUI_IDENTIFIER@ Project (uncompressed) + @CPACK_PROJECT_NAME_UCASE@ Project (uncompressed) CFBundleTypeOSTypes mmp @@ -90,23 +90,23 @@ Editor CFBundleTypeMIMETypes - @MACOSX_BUNDLE_MIMETYPE@ + application/x-@CPACK_PROJECT_NAME@-project - + UTExportedTypeDeclarations UTTypeIdentifier - @MACOSX_BUNDLE_MIMETYPE_ID@.mmpz + @MACOS_MIMETYPE_ID@.mmpz UTTypeReferenceURL - @MACOSX_BUNDLE_PROJECT_URL@ + @CPACK_PROJECT_URL@ UTTypeDescription - @MACOSX_BUNDLE_GUI_IDENTIFIER@ Project + @CPACK_PROJECT_VERSIONPROJECT_NAME_UCASE@ Project UTTypeIconFile - @MACOSX_BUNDLE_MIMETYPE_ICON@ + project UTTypeConformsTo public.data @@ -122,13 +122,13 @@ UTTypeIdentifier - @MACOSX_BUNDLE_MIMETYPE_ID@.mmp + @MACOS_MIMETYPE_ID@.mmp UTTypeReferenceURL - @MACOSX_BUNDLE_PROJECT_URL@ + @CPACK_PROJECT_URL@ UTTypeDescription - @MACOSX_BUNDLE_GUI_IDENTIFIER@ Project (uncompressed) + @CPACK_PROJECT_NAME_UCASE@ Project (uncompressed) UTTypeIconFile - @MACOSX_BUNDLE_MIMETYPE_ICON@ + project UTTypeConformsTo public.xml diff --git a/cmake/apple/package_apple.json.in b/cmake/apple/package_apple.json.in deleted file mode 100644 index f6660b345bc..00000000000 --- a/cmake/apple/package_apple.json.in +++ /dev/null @@ -1,9 +0,0 @@ -{ - "title": "@MACOSX_BUNDLE_DMG_TITLE@", - "background": "@CMAKE_SOURCE_DIR@/cmake/apple/dmg_branding.png", - "icon-size": 128, - "contents": [ - { "x": 139, "y": 200, "type": "file", "path": "@CMAKE_BINARY_DIR@/@MACOSX_BUNDLE_BUNDLE_NAME@.app" }, - { "x": 568, "y": 200, "type": "link", "path": "/Applications" } - ] -} diff --git a/cmake/install/CMakeLists.txt b/cmake/install/CMakeLists.txt index 3f6e3624ecb..b39c205aea8 100644 --- a/cmake/install/CMakeLists.txt +++ b/cmake/install/CMakeLists.txt @@ -40,17 +40,10 @@ ENDIF() if(LMMS_HAVE_STK AND (LMMS_BUILD_WIN32 OR LMMS_BUILD_APPLE)) if(STK_RAWWAVE_ROOT) file(GLOB RAWWAVES "${STK_RAWWAVE_ROOT}/*.raw") + list(SORT RAWWAVES) install(FILES ${RAWWAVES} DESTINATION "${DATA_DIR}/stk/rawwaves") else() message(WARNING "Can't find STK rawwave root!") endif() endif() -IF(LMMS_BUILD_APPLE) - INSTALL(CODE "EXECUTE_PROCESS(COMMAND chmod u+x ${CMAKE_BINARY_DIR}/install_apple.sh)") - INSTALL(CODE "EXECUTE_PROCESS(COMMAND ${CMAKE_BINARY_DIR}/install_apple.sh RESULT_VARIABLE EXIT_CODE) - IF(NOT EXIT_CODE EQUAL 0) - MESSAGE(FATAL_ERROR \"Execution of install_apple.sh failed\") - ENDIF() - ") -ENDIF() diff --git a/cmake/linux/CMakeLists.txt b/cmake/linux/CMakeLists.txt index 27c6655ebd9..afdb57dd297 100644 --- a/cmake/linux/CMakeLists.txt +++ b/cmake/linux/CMakeLists.txt @@ -1,19 +1,53 @@ -INSTALL(DIRECTORY icons/ DESTINATION "${DATA_DIR}/icons/hicolor") -INSTALL(FILES lmms.desktop DESTINATION "${DATA_DIR}/applications") -INSTALL(FILES lmms.xml DESTINATION "${DATA_DIR}/mime/packages") +install(DIRECTORY icons/ DESTINATION "${DATA_DIR}/icons/hicolor") +install(FILES lmms.desktop DESTINATION "${DATA_DIR}/applications") +install(FILES lmms.xml DESTINATION "${DATA_DIR}/mime/packages") -# AppImage creation target -SET(APPIMAGE_FILE "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}-${VERSION}-linux-${CMAKE_SYSTEM_PROCESSOR}.AppImage") +# Preserve old CPack behavior +if(WANT_CPACK_TARBALL) + message(STATUS "Skipping AppImage creation") + return() +endif() -CONFIGURE_FILE("package_linux.sh.in" "${CMAKE_BINARY_DIR}/package_linux.sh" @ONLY) +install(FILES launch_lmms.sh DESTINATION bin) -FILE(REMOVE "${APPIMAGE_FILE}") -ADD_CUSTOM_TARGET(removeappimage - COMMAND rm -f "${APPIMAGE_FILE}" - COMMENT "Removing old AppImage") -ADD_CUSTOM_TARGET(appimage - COMMAND chmod +x "${CMAKE_BINARY_DIR}/package_linux.sh" - COMMAND "${CMAKE_BINARY_DIR}/package_linux.sh" - COMMENT "Generating AppImage" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") -ADD_DEPENDENCIES(appimage removeappimage) +# Standard CPack options +set(CPACK_GENERATOR "External" PARENT_SCOPE) +set(CPACK_EXTERNAL_ENABLE_STAGING true PARENT_SCOPE) +set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${VERSION}-linux-${CMAKE_SYSTEM_PROCESSOR}") +set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}" PARENT_SCOPE) +set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_SOURCE_DIR}/LinuxDeploy.cmake" PARENT_SCOPE) + +# Custom vars to expose to Cpack +# must be prefixed with "CPACK_" per https://stackoverflow.com/a/46526757/3196753) +set(CPACK_CURRENT_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" PARENT_SCOPE) +set(CPACK_CURRENT_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE) +set(CPACK_BINARY_DIR "${CMAKE_BINARY_DIR}" PARENT_SCOPE) +set(CPACK_SOURCE_DIR "${CMAKE_SOURCE_DIR}" PARENT_SCOPE) +set(CPACK_QMAKE_EXECUTABLE "${QT_QMAKE_EXECUTABLE}" PARENT_SCOPE) +set(CPACK_CARLA_LIBRARIES "${CARLA_LIBRARIES}" PARENT_SCOPE) +set(CPACK_WINE_32_LIBRARY_DIRS "${WINE_32_LIBRARY_DIRS}" PARENT_SCOPE) +set(CPACK_WINE_64_LIBRARY_DIRS "${WINE_64_LIBRARY_DIRS}" PARENT_SCOPE) +set(CPACK_PROJECT_NAME "${PROJECT_NAME}" PARENT_SCOPE) +set(CPACK_PROJECT_NAME_UCASE "${PROJECT_NAME_UCASE}" PARENT_SCOPE) +set(CPACK_PROJECT_VERSION "${VERSION}" PARENT_SCOPE) +set(CPACK_CMAKE_COMMAND "${CMAKE_COMMAND}" PARENT_SCOPE) +set(CPACK_SUIL_MODULES "${Suil_MODULES}" PARENT_SCOPE) +set(CPACK_SUIL_MODULES_PREFIX "${Suil_MODULES_PREFIX}" PARENT_SCOPE) +set(CPACK_STK_RAWWAVE_ROOT "${STK_RAWWAVE_ROOT}" PARENT_SCOPE) + +# TODO: Canidate for DetectMachine.cmake +if(IS_X86_64) + set(CPACK_TARGET_ARCH x86_64 PARENT_SCOPE) +elseif(IS_X86) + set(CPACK_TARGET_ARCH i386 PARENT_SCOPE) +elseif(IS_ARM64) + set(CPACK_TARGET_ARCH aarch64 PARENT_SCOPE) +elseif(IS_ARM32) + set(CPACK_TARGET_ARCH armhf PARENT_SCOPE) +else() + set(CPACK_TARGET_ARCH unknown PARENT_SCOPE) +endif() + +if(CMAKE_VERSION VERSION_LESS "3.19") + message(WARNING "AppImage creation requires at least CMake 3.19") +endif() \ No newline at end of file diff --git a/cmake/linux/LinuxDeploy.cmake b/cmake/linux/LinuxDeploy.cmake new file mode 100644 index 00000000000..032c5267505 --- /dev/null +++ b/cmake/linux/LinuxDeploy.cmake @@ -0,0 +1,295 @@ +# Create a Linux desktop installer using linuxdeploy +# * Creates a relocatable LMMS.AppDir installation in build/_CPack_Packages using linuxdeploy +# * If CPACK_TOOL=appimagetool or is not set, bundles AppDir into redistributable ".AppImage" file +# * If CPACK_TOOL=makeself is provided, bundles into a redistributable ".run" file +# +# Copyright (c) 2025, Tres Finocchiaro, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# Variables must be prefixed with "CPACK_" to be visible here +set(lmms "${CPACK_PROJECT_NAME}") +set(LMMS "${CPACK_PROJECT_NAME_UCASE}") +set(ARCH "${CPACK_TARGET_ARCH}") +set(APP "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${LMMS}.AppDir") + +# Target AppImage file +set(APPIMAGE_FILE "${CPACK_BINARY_DIR}/${CPACK_PACKAGE_FILE_NAME}.AppImage") +set(APPIMAGE_BEFORE_RENAME "${CPACK_BINARY_DIR}/${LMMS}-${ARCH}.AppImage") + +set(DESKTOP_FILE "${APP}/usr/share/applications/${lmms}.desktop") + +# Determine which packaging tool to use +if(NOT CPACK_TOOL) + # Pick up environmental variable + if(DEFINED ENV{CPACK_TOOL}) + set(CPACK_TOOL "$ENV{CPACK_TOOL}") + else() + set(CPACK_TOOL "appimagetool") + endif() +endif() + +# Toggle command echoing & verbosity +# 0 = no output, 1 = error/warning, 2 = normal, 3 = debug +if(NOT CPACK_DEBUG) + set(VERBOSITY 1) + set(APPIMAGETOOL_VERBOSITY "") + set(COMMAND_ECHO NONE) + set(OUTPUT_QUIET OUTPUT_QUIET) +else() + set(VERBOSITY 2) + set(APPIMAGETOOL_VERBOSITY "--verbose") + set(COMMAND_ECHO STDOUT) + unset(OUTPUT_QUIET) +endif() + +include(DownloadBinary) +include(CreateSymlink) + +# Cleanup CPack "External" json, txt files, old AppImage files +file(GLOB cleanup "${CPACK_BINARY_DIR}/${lmms}-*.json" + "${CPACK_BINARY_DIR}/${lmms}-*.AppImage" + "${CPACK_BINARY_DIR}/install_manifest.txt") +list(SORT cleanup) +file(REMOVE ${cleanup}) + +# Download linuxdeploy, expose bundled appimagetool to PATH +download_binary(LINUXDEPLOY_BIN + "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${ARCH}.AppImage" + linuxdeploy-${ARCH}.AppImage + FALSE) + +# Symlink nested appimagetool +set(_APPIMAGETOOL_LINK "${CPACK_CURRENT_BINARY_DIR}/appimagetool") +if(NOT EXISTS "${_APPIMAGETOOL_LINK}") + set(_APPIMAGETOOL "${CPACK_CURRENT_BINARY_DIR}/.linuxdeploy-${ARCH}.AppImage/squashfs-root/plugins/linuxdeploy-plugin-appimage/appimagetool-prefix/AppRun") + message(STATUS "Creating a symbolic link ${_APPIMAGETOOL_LINK} which points to ${_APPIMAGETOOL}") + create_symlink("${_APPIMAGETOOL}" "${_APPIMAGETOOL_LINK}") +endif() + +# Download linuxdeploy-plugin-qt +download_binary(LINUXDEPLOY_PLUGIN_BIN + "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-${ARCH}.AppImage" + linuxdeploy-plugin-qt-${ARCH}.AppImage + FALSE) + +message(STATUS "Creating AppDir ${APP}...") + +file(REMOVE_RECURSE "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/include") +file(MAKE_DIRECTORY "${APP}/usr") + +# Setup AppDir structure (/usr/bin, /usr/lib, /usr/share... etc) +file(GLOB files "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/*") +list(SORT files) +foreach(_file ${files}) + get_filename_component(_filename "${_file}" NAME) + if(NOT _filename MATCHES ".AppDir") + file(RENAME "${_file}" "${APP}/usr/${_filename}") + endif() +endforeach() + +# Copy Suil modules +if(CPACK_SUIL_MODULES) + set(SUIL_MODULES_TARGET "${APP}/usr/lib/${CPACK_SUIL_MODULES_PREFIX}") + file(MAKE_DIRECTORY "${SUIL_MODULES_TARGET}") + file(COPY ${CPACK_SUIL_MODULES} DESTINATION "${SUIL_MODULES_TARGET}") +endif() + +# Copy stk/rawwaves +if(CPACK_STK_RAWWAVE_ROOT) + set(STK_RAWWAVE_TARGET "${APP}/usr/share/stk/rawwaves/") + file(MAKE_DIRECTORY "${STK_RAWWAVE_TARGET}") + file(GLOB RAWWAVES "${CPACK_STK_RAWWAVE_ROOT}/*.raw") + file(COPY ${RAWWAVES} DESTINATION "${STK_RAWWAVE_TARGET}") +endif() + +# Ensure project's "qmake" executable is first on the PATH +get_filename_component(QTBIN "${CPACK_QMAKE_EXECUTABLE}" DIRECTORY) +set(ENV{PATH} "${QTBIN}:$ENV{PATH}") + +# Ensure "linuxdeploy-.AppImage" and "appimagetool" binaries are first on the PATH +set(ENV{PATH} "${CPACK_CURRENT_BINARY_DIR}:$ENV{PATH}") + +# Promote finding our own libraries first +set(ENV{LD_LIBRARY_PATH} "${APP}/usr/lib/${lmms}/:${APP}/usr/lib/${lmms}/optional:$ENV{LD_LIBRARY_PATH}") + +# Skip slow searching of copyright files https://github.com/linuxdeploy/linuxdeploy/issues/278 +set(ENV{DISABLE_COPYRIGHT_FILES_DEPLOYMENT} 1) + +# Patch desktop file +file(APPEND "${DESKTOP_FILE}" "X-AppImage-Version=${CPACK_PROJECT_VERSION}\n") + +# Prefer a hard-copy of .DirIcon over appimagetool's symlinking +# 256x256 default for Cinnamon Desktop https://forums.linuxmint.com/viewtopic.php?p=2585952 +file(COPY "${APP}/usr/share/icons/hicolor/256x256/apps/${lmms}.png" DESTINATION "${APP}") +file(RENAME "${APP}/${lmms}.png" "${APP}/.DirIcon") +file(COPY "${APP}/usr/share/icons/hicolor/256x256/apps/${lmms}.png" DESTINATION "${APP}") + +# Build list of libraries to inform linuxdeploy about +# e.g. --library=foo.so --library=bar.so +file(GLOB LIBS "${APP}/usr/lib/${lmms}/*.so") + +# Inform linuxdeploy about LADSPA plugins; may depend on bundled fftw3f, etc. +file(GLOB LADSPA "${APP}/usr/lib/${lmms}/ladspa/*.so") + +# Inform linuxdeploy about remote plugins +file(GLOB REMOTE_PLUGINS "${APP}/usr/lib/${lmms}/*Remote*") + +# Collect, sort and dedupe all libraries +list(APPEND LIBS ${LADSPA}) +list(APPEND LIBS ${REMOTE_PLUGINS}) +list(APPEND LIBS ${CPACK_SUIL_MODULES}) +list(REMOVE_DUPLICATES LIBS) +list(SORT LIBS) + +# Handle non-relinkable files (e.g. RemoveVstPlugin[32|64], but not NativeLinuxRemoteVstPlugin) +list(FILTER LIBS EXCLUDE REGEX "\\/RemoteVst") + +# Construct linuxdeploy parameters +foreach(_lib IN LISTS LIBS) + if(EXISTS "${_lib}") + list(APPEND LIBRARIES "--library=${_lib}") + endif() +endforeach() + +# Call linuxdeploy +message(STATUS "Calling ${LINUXDEPLOY_BIN} --appdir \"${APP}\" ... [... libraries].") +execute_process(COMMAND "${LINUXDEPLOY_BIN}" + --appdir "${APP}" + --desktop-file "${DESKTOP_FILE}" + --custom-apprun "${CPACK_SOURCE_DIR}/cmake/linux/launch_lmms.sh" + --plugin qt + ${LIBRARIES} + --verbosity ${VERBOSITY} + ${OUTPUT_QUIET} + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) + +# Remove svg ambitiously placed by linuxdeploy +file(REMOVE "${APP}/${lmms}.svg") + +# Remove libraries that are normally sytem-provided +file(GLOB EXCLUDE_LIBS + "${APP}/usr/lib/libwine*" + "${APP}/usr/lib/libcarla_native*" + "${APP}/usr/lib/${lmms}/optional/libcarla*" + "${APP}/usr/lib/libjack*") + +list(SORT EXCLUDE_LIBS) +foreach(_lib IN LISTS EXCLUDE_LIBS) + if(EXISTS "${_lib}") + file(REMOVE "${_lib}") + endif() +endforeach() + +# FIXME: Remove when linuxdeploy supports subfolders https://github.com/linuxdeploy/linuxdeploy/issues/305 +foreach(_lib IN LISTS LIBS) + if(EXISTS "${_lib}") + file(REMOVE "${_lib}") + endif() +endforeach() +# Move RemotePlugins into to LMMS_PLUGIN_DIR +file(GLOB WINE_VST_LIBS + "${APP}/usr/lib/${lmms}/RemoteVstPlugin*" + "${APP}/usr/lib/${lmms}/32") +foreach(_file IN LISTS WINE_VST_LIBS) + if(EXISTS "${_file}") + get_filename_component(_name "${_file}" NAME) + file(RENAME "${_file}" "${APP}/usr/lib/${_name}") + endif() +endforeach() +file(GLOB WINE_32_LIBS + "${APP}/usr/lib/${lmms}/RemoteVstPlugin*") +foreach(_lib IN LISTS WINE_64_LIBS) + if(EXISTS "${_lib}") + get_filename_component(_file "${_lib}" NAME) + file(RENAME "${_lib}" "${APP}/usr/lib/${_file}") + endif() +endforeach() + +file(REMOVE_RECURSE "${SUIL_MODULES_TARGET}" "${APP}/usr/lib/${lmms}/ladspa/") + +# Bundle jack to avoid crash for systems without it +# See https://github.com/LMMS/lmms/pull/4186 +execute_process(COMMAND ldd "${APP}/usr/bin/${lmms}" + OUTPUT_VARIABLE LDD_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) +string(REPLACE "\n" ";" LDD_LIST "${LDD_OUTPUT}") +foreach(line ${LDD_LIST}) + if(line MATCHES "libjack\\.so") + # Assume format "libjack.so.0 => /lib/x86_64-linux-gnu/libjack.so.0 (0x00007f48d0b0e000)" + string(REPLACE " " ";" parts "${line}") + list(LENGTH parts len) + math(EXPR index "${len}-2") + list(GET parts ${index} lib) + # Get symlink target + file(REAL_PATH "${lib}" libreal) + get_filename_component(symname "${lib}" NAME) + get_filename_component(realname "${libreal}" NAME) + file(MAKE_DIRECTORY "${APP}/usr/lib/${lmms}/optional/") + # Copy, but with original symlink name + file(COPY "${libreal}" DESTINATION "${APP}/usr/lib/${lmms}/optional/") + file(RENAME "${APP}/usr/lib/${lmms}/optional/${realname}" "${APP}/usr/lib/${lmms}/optional/${symname}") + continue() + endif() +endforeach() + +if(CPACK_TOOL STREQUAL "appimagetool") + # Create ".AppImage" file using appimagetool (default) + + # appimage plugin needs ARCH set when running in extracted form from squashfs-root / CI + set(ENV{ARCH} "${ARCH}") + message(STATUS "Finishing the AppImage...") + execute_process(COMMAND ${CPACK_TOOL} "${APP}" "${APPIMAGE_FILE}" + ${APPIMAGETOOL_VERBOSITY} + ${OUTPUT_QUIET} + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) + + message(STATUS "AppImage created: ${APPIMAGE_FILE}") +elseif(CPACK_TOOL STREQUAL "makeself") + # Create self-extracting ".run" script using makeself + find_program(MAKESELF_BIN makeself REQUIRED) + + message(STATUS "Finishing the .run file using ${MAKESELF_BIN}...") + string(REPLACE ".AppImage" ".run" RUN_FILE "${APPIMAGE_FILE}") + configure_file( + "${CPACK_SOURCE_DIR}/cmake/linux/makeself_setup.sh.in" "${APP}/setup.sh" @ONLY + FILE_PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_WRITE GROUP_READ + WORLD_READ) + + if(OUTPUT_QUIET) + set(MAKESELF_QUIET "--quiet") + set(ERROR_QUIET ERROR_QUIET) + endif() + + # makeself.sh [args] archive_dir file_name label startup_script [script_args] + file(REMOVE "${RUN_FILE}") + execute_process(COMMAND "${MAKESELF_BIN}" + --keep-umask + --nox11 + ${MAKESELF_QUIET} + "${APP}" + "${RUN_FILE}" + "${LMMS} Installer" + "./setup.sh" + ${OUTPUT_QUIET} + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) + + # ensure the installer can be executed as a script file + execute_process(COMMAND "${RUN_FILE}" --help + ${OUTPUT_QUIET} + ${ERROR_QUIET} + COMMAND_ECHO ${COMMAND_ECHO} + COMMAND_ERROR_IS_FATAL ANY) + + message(STATUS "Installer created: ${RUN_FILE}") +else() + message(FATAL_ERROR "Packaging tool CPACK_TOOL=\"${CPACK_TOOL}\" is not yet supported") +endif() diff --git a/cmake/linux/icons/256x256/apps/lmms.png b/cmake/linux/icons/256x256/apps/lmms.png new file mode 100644 index 00000000000..c069239dd05 Binary files /dev/null and b/cmake/linux/icons/256x256/apps/lmms.png differ diff --git a/cmake/linux/icons/256x256/mimetypes/application-x-lmms-project.png b/cmake/linux/icons/256x256/mimetypes/application-x-lmms-project.png new file mode 100644 index 00000000000..0e03e701255 Binary files /dev/null and b/cmake/linux/icons/256x256/mimetypes/application-x-lmms-project.png differ diff --git a/cmake/linux/launch_lmms.sh b/cmake/linux/launch_lmms.sh index ba26fb9c742..131d2b92de3 100644 --- a/cmake/linux/launch_lmms.sh +++ b/cmake/linux/launch_lmms.sh @@ -21,4 +21,10 @@ else echo "Jack does not appear to be installed. That's OK, we'll use a dummy version instead." >&2 export LD_LIBRARY_PATH=$DIR/usr/lib/lmms/optional:$LD_LIBRARY_PATH fi -QT_X11_NO_NATIVE_MENUBAR=1 "$DIR"/usr/bin/lmms.real "$@" + +# FIXME: Remove when linuxdeploy supports subfolders https://github.com/linuxdeploy/linuxdeploy/issues/305 +export LMMS_PLUGIN_DIR="$DIR/usr/lib/" +export LADSPA_PATH="$DIR/usr/lib/" +export SUIL_MODULE_DIR="$DIR/usr/lib/" + +QT_X11_NO_NATIVE_MENUBAR=1 "$DIR"/usr/bin/lmms "$@" diff --git a/cmake/linux/makeself_setup.sh.in b/cmake/linux/makeself_setup.sh.in new file mode 100644 index 00000000000..22aa5c47462 --- /dev/null +++ b/cmake/linux/makeself_setup.sh.in @@ -0,0 +1,35 @@ +#!/bin/bash + +# Halt on first error +set -e + +DESTDIR="/opt/@CPACK_PROJECT_NAME@" +BASHCOMPLETIONS="/usr/share/bash-completion/completions" +if [ "$(id -u)" != "0" ]; then + # Prepend "$HOME" so we can install to a writable location + DESTDIR="${HOME}${DESTDIR}" + BASHCOMPLETIONS="${HOME}/.local/share/bash-completion/completions" + echo "Installing as a regular user to ${DESTDIR}/..." +else + echo "Installing as elevated user to ${DESTDIR}/..." +fi + +# Deploy @CPACK_PROJECT_NAME_UCASE@ +mkdir -p "${DESTDIR}" +unalias cp &> /dev/null || true +cp -rf ./* "${DESTDIR}" +rm -f "${DESTDIR}/setup.sh" +mv "${DESTDIR}/AppRun" "${DESTDIR}/@CPACK_PROJECT_NAME@" + +# Install bash completions +mkdir -p "${BASHCOMPLETIONS}" +ln -sf "${DESTDIR}/usr/share/@CPACK_PROJECT_NAME@/bash-completion/completions/@CPACK_PROJECT_NAME@" "${BASHCOMPLETIONS}/@CPACK_PROJECT_NAME@" + +# Test @CPACK_PROJECT_NAME_UCASE@ +echo "Installation complete... Testing \"@CPACK_PROJECT_NAME@\"..." +"${DESTDIR}/@CPACK_PROJECT_NAME@" --allowroot --version &> /dev/null + +# TODO: Register file associations, desktop icon, etc + +echo "@CPACK_PROJECT_NAME_UCASE@ was installed successfully to ${DESTDIR}. To run:" +echo " ${DESTDIR}/@CPACK_PROJECT_NAME@" \ No newline at end of file diff --git a/cmake/linux/package_linux.sh.in b/cmake/linux/package_linux.sh.in deleted file mode 100644 index 16cd5719bb8..00000000000 --- a/cmake/linux/package_linux.sh.in +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env bash -# Creates Linux ".AppImage" for @PROJECT_NAME_UCASE@ -# -# Depends: linuxdeployqt -# -# Notes: Will attempt to fetch linuxdeployqt automatically (x86_64 only) -# See Also: https://github.com/probonopd/linuxdeployqt/blob/master/BUILDING.md - -VERBOSITY=2 # 3=debug -LOGFILE="@CMAKE_BINARY_DIR@/appimage.log" -APPDIR="@CMAKE_BINARY_DIR@/@PROJECT_NAME_UCASE@.AppDir/" -DESKTOPFILE="${APPDIR}usr/share/applications/lmms.desktop" -STRIP="" - -# Don't strip for Debug|RelWithDebInfo builds -# shellcheck disable=SC2193 -if [[ "@CMAKE_BUILD_TYPE@" == *"Deb"* ]]; then - STRIP="-no-strip" -fi - -# Console colors -RED="\\x1B[1;31m" -GREEN="\\x1B[1;32m" -YELLOW="\\x1B[1;33m" -PLAIN="\\x1B[0m" - -function error { - echo -e " ${PLAIN}[${RED}error${PLAIN}] ${1}" - return 1 -} - -function success { - echo -e " ${PLAIN}[${GREEN}success${PLAIN}] ${1}" -} - -function skipped { - echo -e " ${PLAIN}[${YELLOW}skipped${PLAIN}] ${1}" -} - -# Exit with error message if any command fails -trap "error 'Failed to generate AppImage'; exit 1" ERR - -# Run a command silently, but print output if it fails -function run_and_log { - echo -e "\n\n>>>>> $1" >> "$LOGFILE" - output="$("$@" 2>&1)" - status=$? - echo "$output" >> "$LOGFILE" - [[ $status != 0 ]] && echo "$output" - return $status -} - -# Blindly assume system arch is appimage arch -ARCH=$(uname -m) -export ARCH - -# Check for problematic install locations -INSTALL=$(echo "@CMAKE_INSTALL_PREFIX@" | sed 's/\/*$//g') -if [ "$INSTALL" == "/usr/local" ] || [ "$INSTALL" == "/usr" ] ; then - error "Incompatible CMAKE_INSTALL_PREFIX for creating AppImage: @CMAKE_INSTALL_PREFIX@" -fi - -# Ensure linuxdeployqt uses the same qmake version as cmake -PATH="$(dirname "@QT_QMAKE_EXECUTABLE@"):$PATH" -export PATH - -# Use linuxdeployqt from env or in PATH -[[ $LINUXDEPLOYQT ]] || LINUXDEPLOYQT="$(which linuxdeployqt 2>/dev/null)" || true -[[ $APPIMAGETOOL ]] || APPIMAGETOOL="$(which appimagetool 2>/dev/null)" || true - -# Fetch portable linuxdeployqt if not in PATH -if [[ -z $LINUXDEPLOYQT || -z $APPIMAGETOOL ]]; then - filename="linuxdeployqt-continuous-$ARCH.AppImage" - url="https://github.com/probonopd/linuxdeployqt/releases/download/continuous/$filename" - echo " [.......] Downloading: ${url}" - wget -N -q "$url" && err=0 || err=$? - case "$err" in - 0) success "Downloaded $PWD/$filename" ;; - # 8 == server issued 4xx error - 8) error "Download failed (perhaps no package available for $ARCH)" ;; - *) error "Download failed" ;; - esac - - # Extract AppImage and replace LINUXDEPLOYQT variable with extracted binary - # to support systems without fuse - # Also, we need to set LD_LIBRARY_PATH, but linuxdepoyqt's AppRun unsets it - # See https://github.com/probonopd/linuxdeployqt/pull/370/ - chmod +x "$filename" - ./"$filename" --appimage-extract >/dev/null - success "Extracted $filename" - - # Use the extracted linuxdeployqt and appimagetool - PATH="$(pwd -P)/squashfs-root/usr/bin:$PATH" - [[ $LINUXDEPLOYQT ]] || LINUXDEPLOYQT="$(which linuxdeployqt)" - [[ $APPIMAGETOOL ]] || APPIMAGETOOL="$(which appimagetool)" -fi - -# Make skeleton AppDir -echo -e "\nCreating ${APPDIR}..." -rm -rf "${APPDIR}" -mkdir -p "${APPDIR}usr" -success "Created ${APPDIR}" - -# Clone install to AppDir -echo -e "\nCopying @CMAKE_INSTALL_PREFIX@ to ${APPDIR}..." -cp -R "@CMAKE_INSTALL_PREFIX@/." "${APPDIR}usr" -rm -rf "${APPDIR}usr/include" -success "${APPDIR}" - -# Copy rawwaves directory for stk/mallets -mkdir -p "${APPDIR}usr/share/stk/" -cp -R /usr/share/stk/rawwaves/ "${APPDIR}usr/share/stk/" - -# Create a wrapper script which calls the lmms executable -mv "${APPDIR}usr/bin/lmms" "${APPDIR}usr/bin/lmms.real" - -cp "@CMAKE_CURRENT_SOURCE_DIR@/launch_lmms.sh" "${APPDIR}usr/bin/lmms" - -chmod +x "${APPDIR}usr/bin/lmms" - -# Per https://github.com/probonopd/linuxdeployqt/issues/129 -unset LD_LIBRARY_PATH - -# Ensure linuxdeployqt can find shared objects -export LD_LIBRARY_PATH="${APPDIR}"usr/lib/lmms/:"${APPDIR}"usr/lib/lmms/optional:"$LD_LIBRARY_PATH" - -# Move executables so linuxdeployqt can find them -ZYNLIB="${APPDIR}usr/lib/lmms/RemoteZynAddSubFx" -VSTLIB32="${APPDIR}usr/lib/lmms/32/RemoteVstPlugin32.exe.so" -VSTLIB64="${APPDIR}usr/lib/lmms/RemoteVstPlugin64.exe.so" - -ZYNBIN="${APPDIR}usr/bin/RemoteZynAddSubFx" -VSTBIN32="${APPDIR}usr/bin/RemoteVstPlugin32.exe.so" -VSTBIN64="${APPDIR}usr/bin/RemoteVstPlugin64.exe.so" - -mv "$ZYNLIB" "$ZYNBIN" -mv "$VSTLIB32" "$VSTBIN32" || true -mv "$VSTLIB64" "$VSTBIN64" || true - -# Handle wine linking -if [ -d "@WINE_32_LIBRARY_DIR@" ] && \ - ldd "$VSTBIN32" | grep "libwine\.so" | grep "not found"; then - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"@WINE_32_LIBRARY_DIRS@" -fi -if [ -d "@WINE_64_LIBRARY_DIR@" ] && \ - ldd "$VSTBIN64" | grep "libwine\.so" | grep "not found"; then - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"@WINE_64_LIBRARY_DIRS@" -fi - -# Patch the desktop file -sed -i 's/.*Exec=.*/Exec=lmms.real/' "$DESKTOPFILE" -echo "X-AppImage-Version=@VERSION@" >> "$DESKTOPFILE" - -# Fix linking for soft-linked plugins -for file in "${APPDIR}usr/lib/lmms/"*.so; do - thisfile="${APPDIR}usr/lib/lmms/${file##*/}" - executables="${executables} -executable=$thisfile" -done -executables="${executables} -executable=${ZYNBIN}" -executables="${executables} -executable=${VSTBIN32}" -executables="${executables} -executable=${VSTBIN64}" -executables="${executables} -executable=${APPDIR}usr/lib/lmms/ladspa/imp_1199.so" -executables="${executables} -executable=${APPDIR}usr/lib/lmms/ladspa/imbeq_1197.so" -executables="${executables} -executable=${APPDIR}usr/lib/lmms/ladspa/pitch_scale_1193.so" -executables="${executables} -executable=${APPDIR}usr/lib/lmms/ladspa/pitch_scale_1194.so" - -echo -e "\nWriting verbose output to \"${LOGFILE}\"" -echo -n > "$LOGFILE" - -# Bundle both qt and non-qt dependencies into appimage format -echo -e "\nBundling and relinking system dependencies..." - -# shellcheck disable=SC2086 -run_and_log "$LINUXDEPLOYQT" "$DESKTOPFILE" $executables -bundle-non-qt-libs -verbose=$VERBOSITY $STRIP -success "Bundled and relinked dependencies" - -# Link to original location so lmms can find them -ln -sr "$ZYNBIN" "$ZYNLIB" -ln -sr "$VSTBIN32" "$VSTLIB32" || true -ln -sr "$VSTBIN64" "$VSTLIB64" || true - -# Remove wine library conflict -rm -f "${APPDIR}/usr/lib/libwine.so.1" - -# Use system-provided carla -rm -f "${APPDIR}usr/lib/"libcarla*.so -rm -f "${APPDIR}usr/lib/lmms/optional/"libcarla*.so - -# Remove bundled jack in LD_LIBRARY_PATH if exists -if [ -e "${APPDIR}/usr/lib/libjack.so.0" ]; then - rm "${APPDIR}/usr/lib/libjack.so.0" -fi - -# Bundle jack out of LD_LIBRARY_PATH -JACK_LIB=$(ldd "${APPDIR}/usr/bin/lmms.real" | sed -n 's/\tlibjack\.so\.0 => \(.\+\) (0x[0-9a-f]\+)/\1/p') -if [ -e "$JACK_LIB" ]; then - mkdir -p "${APPDIR}usr/lib/lmms/optional/" - cp "$JACK_LIB" "${APPDIR}usr/lib/lmms/optional/" -fi - -# Point the AppRun to the shim launcher -rm -f "${APPDIR}/AppRun" -ln -sr "${APPDIR}/usr/bin/lmms" "${APPDIR}/AppRun" - -# Add icon -ln -srf "${APPDIR}/lmms.png" "${APPDIR}/.DirIcon" - -# Create AppImage -echo -e "\nFinishing the AppImage..." -run_and_log "$APPIMAGETOOL" "${APPDIR}" "@APPIMAGE_FILE@" -success "Created @APPIMAGE_FILE@" - -echo -e "\nFinished" diff --git a/cmake/modules/BashCompletion.cmake b/cmake/modules/BashCompletion.cmake index 7301e82aaf6..7ce5a488661 100644 --- a/cmake/modules/BashCompletion.cmake +++ b/cmake/modules/BashCompletion.cmake @@ -1,93 +1,78 @@ -# A wrapper around pkg-config-provided and cmake-provided bash completion that -# will have dynamic behavior at INSTALL() time to allow both root-level -# INSTALL() as well as user-level INSTALL(). -# -# See also https://github.com/scop/bash-completion -# -# Copyright (c) 2018, Tres Finocchiaro, +# Copyright (c) 2024, Tres Finocchiaro, # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # +# Description: +# Fail-safe bash-completion installation support +# - Installs to ${CMAKE_INSTALL_PREFIX}/share/bash-completion/completions +# - Attempts to calculate and install to system-wide location +# - See also https://github.com/scop/bash-completion +# # Usage: # INCLUDE(BashCompletion) # BASHCOMP_INSTALL(foo) # ... where "foo" is a shell script adjacent to the CMakeLists.txt -# -# How it determines BASHCOMP_PKG_PATH, in order: -# 1. Uses BASHCOMP_PKG_PATH if already set (e.g. -DBASHCOMP_PKG_PATH=...) -# a. If not, uses pkg-config's PKG_CHECK_MODULES to determine path -# b. Fallback to cmake's FIND_PACKAGE(bash-completion) path -# c. Fallback to hard-coded /usr/share/bash-completion/completions -# 2. Final fallback to ${CMAKE_INSTALL_PREFIX}/share/bash-completion/completions if -# detected path is unwritable. -# - Windows does not support bash completion -# - macOS support should eventually be added for Homebrew (TODO) -IF(WIN32) - MESSAGE(STATUS "Bash completion is not supported on this platform.") -ELSEIF(APPLE) - MESSAGE(STATUS "Bash completion is not yet implemented for this platform.") -ELSE() - INCLUDE(FindUnixCommands) - # Honor manual override if provided - IF(NOT BASHCOMP_PKG_PATH) - # First, use pkg-config, which is the most reliable - FIND_PACKAGE(PkgConfig QUIET) - IF(PKGCONFIG_FOUND) - PKG_CHECK_MODULES(BASH_COMPLETION bash-completion) - PKG_GET_VARIABLE(BASHCOMP_PKG_PATH bash-completion completionsdir) - ELSE() - # Second, use cmake (preferred but less common) - FIND_PACKAGE(bash-completion QUIET) - IF(BASH_COMPLETION_FOUND) - SET(BASHCOMP_PKG_PATH "${BASH_COMPLETION_COMPLETIONSDIR}") - ENDIF() - ENDIF() +# Honor manual override if provided +if(NOT BASHCOMP_PKG_PATH) + # First, use pkg-config, which is the most reliable + find_package(PkgConfig QUIET) + if(PKGCONFIG_FOUND) + PKG_CHECK_MODULES(BASH_COMPLETION bash-completion) + PKG_GET_VARIABLE(BASHCOMP_PKG_PATH bash-completion completionsdir) + else() + # Second, use cmake (preferred but less common) + find_package(bash-completion QUIET) + if(BASH_COMPLETION_FOUND) + set(BASHCOMP_PKG_PATH "${BASH_COMPLETION_COMPLETIONSDIR}") + endif() + endif() + + # Third, use a hard-coded fallback value + if("${BASHCOMP_PKG_PATH}" STREQUAL "") + set(BASHCOMP_PKG_PATH "/usr/share/bash-completion/completions") + endif() +endif() + +# Always provide a fallback for non-root INSTALL() +# * "lmms" subfolder ensures we don't pollute /usr/local/share/ on default "make install" +set(BASHCOMP_USER_PATH "share/${PROJECT_NAME}/bash-completion/completions") - # Third, use a hard-coded fallback value - IF("${BASHCOMP_PKG_PATH}" STREQUAL "") - SET(BASHCOMP_PKG_PATH "/usr/share/bash-completion/completions") - ENDIF() - ENDIF() +macro(BASHCOMP_INSTALL SCRIPT_NAME) + # Note: When running from CPack, message(...) will be supressed unless WARNING + if(WIN32) + message(STATUS "Bash completion is not supported on this platform.") + else() + # Install a copy of bash completion to the default install prefix + # See also: https://github.com/LMMS/lmms/pull/7252/files#r1815749125 + install(FILES "${SCRIPT_NAME}" DESTINATION "${BASHCOMP_USER_PATH}") - # Always provide a fallback for non-root INSTALL() - SET(BASHCOMP_USER_PATH "${CMAKE_INSTALL_PREFIX}/share/bash-completion/completions") + # Next, blindly attempt a system-wide install, ignoring failure + # See also: https://stackoverflow.com/q/58448332 + # * CPack doesn't use CMAKE_INSTALL_PREFIX, so the original will be missing when packaging + # and this step will be skipped + # * For non-root installs (e.g. ../target), this will silently fail + set(BASHCOMP_ORIG "${CMAKE_INSTALL_PREFIX}/${BASHCOMP_USER_PATH}/${CMAKE_PROJECT_NAME}") + set(BASHCOMP_LINK "${BASHCOMP_PKG_PATH}/${CMAKE_PROJECT_NAME}") - # Cmake doesn't allow easy use of conditional logic at INSTALL() time - # this is a problem because ${BASHCOMP_PKG_PATH} may not be writable and we - # need sane fallback behavior for bundled INSTALL() (e.g. .AppImage, etc). - # - # The reason this can't be detected by cmake is that it's fairly common to - # run "cmake" as a one user (i.e. non-root) and "make install" as another user - # (i.e. root). - # - # - Creates a script called "install_${SCRIPT_NAME}_completion.sh" into the - # working binary directory and invokes this script at install. - # - Script handles INSTALL()-time conditional logic for sane ballback behavior - # when ${BASHCOMP_PKG_PATH} is unwritable (i.e. non-root); Something cmake - # can't handle on its own at INSTALL() time) - MACRO(BASHCOMP_INSTALL SCRIPT_NAME) - # A shell script for wrapping conditionl logic - SET(BASHCOMP_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_${SCRIPT_NAME}_completion.sh") + if(BASHCOMP_PKG_PATH) + # TODO: CMake 3.21 Use "file(COPY_FILE ...)" + install(CODE " + if(EXISTS \"${BASHCOMP_ORIG}\") + file(REMOVE \"${BASHCOMP_LINK}\") + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + \"${BASHCOMP_ORIG}\" + \"${BASHCOMP_LINK}\" + ERROR_QUIET + RESULT_VARIABLE result) + if(result EQUAL 0) + message(STATUS \"Bash completion-support has been installed to ${BASHCOMP_LINK}\") + endif() + endif() + ") + endif() + endif() +endmacro() - FILE(WRITE ${BASHCOMP_SCRIPT} "\ -#!${BASH}\n\ -set -e\n\ -if [ -w \"${BASHCOMP_PKG_PATH}\" ]; then\n\ - BASHCOMP_PKG_PATH=\"${BASHCOMP_PKG_PATH}\"\n\ -else \n\ - BASHCOMP_PKG_PATH=\"\$DESTDIR${BASHCOMP_USER_PATH}\"\n\ -fi\n\ -echo -e \"\\nInstalling bash completion...\\n\"\n\ -mkdir -p \"\$BASHCOMP_PKG_PATH\"\n\ -cp \"${CMAKE_CURRENT_SOURCE_DIR}/${SCRIPT_NAME}\" \"\$BASHCOMP_PKG_PATH\"\n\ -chmod a+r \"\$BASHCOMP_PKG_PATH/${SCRIPT_NAME}\"\n\ -echo -e \"Bash completion for ${SCRIPT_NAME} has been installed to \$BASHCOMP_PKG_PATH/${SCRIPT_NAME}\"\n\ -") - INSTALL(CODE "EXECUTE_PROCESS(COMMAND chmod u+x \"install_${SCRIPT_NAME}_completion.sh\" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} )") - INSTALL(CODE "EXECUTE_PROCESS(COMMAND \"./install_${SCRIPT_NAME}_completion.sh\" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} )") - MESSAGE(STATUS "Bash completion script for ${SCRIPT_NAME} will be installed to ${BASHCOMP_PKG_PATH} or fallback to ${BASHCOMP_USER_PATH} if unwritable.") - ENDMACRO() -ENDIF() diff --git a/cmake/modules/CreateSymlink.cmake b/cmake/modules/CreateSymlink.cmake new file mode 100644 index 00000000000..41af2eb6ff5 --- /dev/null +++ b/cmake/modules/CreateSymlink.cmake @@ -0,0 +1,34 @@ +# Offer relative symlink support via "cmake -E create_symlink" +# For verbose, set COMMAND_ECHO to STDOUT in calling script +macro(create_symlink filepath sympath) + if(CMAKE_COMMAND) + set(_cmake_command "${CMAKE_COMMAND}") + elseif(CPACK_CMAKE_COMMAND) + set(_cmake_command "${CPACK_CMAKE_COMMAND}") + else() + message(FATAL_ERROR "Sorry, can't resolve variable CMAKE_COMMAND") + endif() + + if(NOT IS_ABSOLUTE "${sympath}") + message(FATAL_ERROR "Sorry, this command only works with absolute paths") + endif() + + if(NOT DEFINED COMMAND_ECHO) + set(_command_echo NONE) + else() + set(_command_echo "${COMMAND_ECHO}") + endif() + + # Calculate the relative path + file(RELATIVE_PATH reldir "${sympath}/../" "${filepath}") + get_filename_component(symname "${sympath}" NAME) + + # Calculate the working directory + get_filename_component(sympath_parent "${sympath}" DIRECTORY) + + # Create the symbolic link + execute_process(COMMAND "${_cmake_command}" -E create_symlink "${reldir}" "${symname}" + WORKING_DIRECTORY "${sympath_parent}" + COMMAND_ECHO ${_command_echo} + COMMAND_ERROR_IS_FATAL ANY) +endmacro() \ No newline at end of file diff --git a/cmake/modules/DownloadBinary.cmake b/cmake/modules/DownloadBinary.cmake new file mode 100644 index 00000000000..a6b92dc441c --- /dev/null +++ b/cmake/modules/DownloadBinary.cmake @@ -0,0 +1,143 @@ +# Downloads an executable from the provided URL for use in a build system +# and optionally prepends it to the PATH +# +# Assumes: +# - CMAKE_CURRENT_BINARY_DIR/[${_name}] +# - CPACK_CURRENT_BINARY_DIR/[${_name}] +# - Fallback to $ENV{TMPDIR}/[RANDOM]/[${_name}] +# - For verbose, set COMMAND_ECHO to STDOUT in calling script +# +macro(download_binary RESULT_VARIABLE _url _name _prepend_to_path) + if(NOT COMMAND_ECHO OR "${COMMAND_ECHO}" STREQUAL "NONE") + set(_command_echo NONE) + set(_output_quiet OUTPUT_QUIET) + set(_error_quiet ERROR_QUIET) + else() + set(_command_echo "${COMMAND_ECHO}") + set(_output_quiet "") + set(_error_quiet "") + endif() + + # Check if fuse is needed + if("${RESULT_VARIABLE}" MATCHES "\\.AppImage$" OR "${_name}" MATCHES "\\.AppImage$") + message(STATUS "AppImage detected, we'll extract the AppImage before using") + set(_${RESULT_VARIABLE}_IS_APPIMAGE TRUE) + endif() + + # Determine a suitable working directory + if(CMAKE_CURRENT_BINARY_DIR) + # Assume we're called from configure step + set(_working_dir "${CMAKE_CURRENT_BINARY_DIR}") + elseif(CPACK_CURRENT_BINARY_DIR) + # Assume cpack (non-standard variable name, but used throughout) + set(_working_dir "${CPACK_CURRENT_BINARY_DIR}") + else() + # Fallback to somewhere temporary, writable + if($ENV{_tmpdir}) + # POSIX + set(_tmpdir "$ENV{_tmpdir}") + elseif($ENV{TEMP}) + # Windows + set(_tmpdir "$ENV{TEMP}") + else() + # Linux, shame on you! + find_program(MKTEMP mktemp) + if(MKTEMP) + execute_process(COMMAND mktemp + OUTPUT_VARIABLE _working_dir + OUTPUT_STRIP_TRAILING_WHITESPACE + ${_output_quiet} + COMMAND_ECHO ${_command_echo}) + # mktemp formats it how we want it + else() + # Ummm... Linux you can do better! + set(_tmpdir "/tmp") + endif() + endif() + if(NOT DEFINED _working_dir) + string(RANDOM subdir) + set(_working_dir "${_tmpdir}/tmp.${subdir}") + endif() + if(NOT EXISTS "${_working_dir}") + file(MAKE_DIRECTORY "${_working_dir}") + endif() + endif() + + if(_prepend_to_path) + # Ensure the PATH is configured + string(FIND "$ENV{PATH}" "${_working_dir}" _pathloc) + if(NOT $_pathloc EQUAL 0) + set(ENV{PATH} "${_working_dir}:$ENV{PATH}") + endif() + endif() + + # First ensure the binary doesn't already exist + find_program(_${RESULT_VARIABLE} "${_name}" HINTS "${_working_dir}") + + set(_binary_path "${_working_dir}/${_name}") + if(NOT _${RESULT_VARIABLE}) + message(STATUS "Downloading ${_name} from ${_url}...") + file(DOWNLOAD + "${_url}" + "${_binary_path}" + STATUS DOWNLOAD_STATUS) + # Check if download was successful. + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) + if(NOT ${STATUS_CODE} EQUAL 0) + file(REMOVE "${_binary_path}") + message(FATAL_ERROR "Error downloading ${_url} ${ERROR_MESSAGE}") + endif() + + # Ensure the file is executable + file(CHMOD "${_binary_path}" PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_WRITE GROUP_READ) + + # Ensure it's found + find_program(_${RESULT_VARIABLE} "${_name}" HINTS "${_working_dir}" REQUIRED) + endif() + + # We need to create a subdirectory for this binary and symlink it's AppRun to where it's expected + if(_${RESULT_VARIABLE}_IS_APPIMAGE AND NOT IS_SYMLINK "${_${RESULT_VARIABLE}}") + if(NOT COMMAND create_symlink) + include(CreateSymlink) + endif() + + message(STATUS "Extracting ${_${RESULT_VARIABLE}} to ${_working_dir}/.${_name}/") + + # extract appimage + execute_process(COMMAND "${_${RESULT_VARIABLE}}" --appimage-extract + WORKING_DIRECTORY "${_working_dir}" + COMMAND_ECHO ${_command_echo} + ${_output_quiet} + ${_error_quiet} + COMMAND_ERROR_IS_FATAL ANY) + + # move extracted files to dedicated location (e.g. ".linuxdeploy-x86_64.AppImage/squashfs-root/") + file(MAKE_DIRECTORY "${_working_dir}/.${_name}/") + file(RENAME "${_working_dir}/squashfs-root/" "${_working_dir}/.${_name}/squashfs-root/") + + # remove the unusable binary + file(REMOVE "${_${RESULT_VARIABLE}}") + + # symlink the expected binary name to the AppRun file + message(STATUS "Creating a symbolic link ${_${RESULT_VARIABLE}} which points to ${_working_dir}/.${_name}/squashfs-root/AppRun") + create_symlink("${_working_dir}/.${_name}/squashfs-root/AppRun" "${_${RESULT_VARIABLE}}") + endif() + + # Test the binary + # - TODO: Add support for bad binaries that set "$?" to an error code for no good reason + # - TODO: Add support for Windows binaries expecting "/?" instead of "--help" + message(STATUS "Testing that ${_name} works on this system...") + set(_test_param "--help") + + execute_process(COMMAND "${_${RESULT_VARIABLE}}" ${_test_param} + COMMAND_ECHO ${_command_echo} + ${_output_quiet} + ${_error_quiet} + COMMAND_ERROR_IS_FATAL ANY) + + message(STATUS "The binary \"${_${RESULT_VARIABLE}}\" is now available") + set(${RESULT_VARIABLE} "${_${RESULT_VARIABLE}}") +endmacro() \ No newline at end of file diff --git a/cmake/modules/FindSuilModules.cmake b/cmake/modules/FindSuilModules.cmake new file mode 100644 index 00000000000..998521c7a04 --- /dev/null +++ b/cmake/modules/FindSuilModules.cmake @@ -0,0 +1,40 @@ +# Copyright (c) 2024 Tres Finocchiaro +# +# Redistribution and use is allowed according to the terms of the New BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# This module defines +# Suil_MODULES: List of full paths to Suil modules (e.g. "/usr/lib/suil-0/libsuil_x11.so;...") +# Suil_MODULES_PREFIX: Only the directory name of the Suil_MODULES path (e.g. "suil-0") + +pkg_check_modules(Suil QUIET suil-0) + +if(Suil_FOUND) + if(APPLE) + set(_lib_ext "dylib") + elseif(WIN32) + set(_lib_ext "dll") + else() + set(_lib_ext "so") + endif() + + # Isolate -- if needed -- the first suil library path (e.g. "/usr/lib/libsuil-0.so") + list(GET Suil_LINK_LIBRARIES 0 _lib) + if(EXISTS "${_lib}") + # Isolate -- if needed -- the first suil library name (e.g. "suil-0") + list(GET Suil_LIBRARIES 0 _modules_prefix) + get_filename_component(_lib_dir "${_lib}" DIRECTORY) + # Construct modules path (e.g. "/usr/lib/suil-0") + set(_modules_dir "${_lib_dir}/${_modules_prefix}") + if(IS_DIRECTORY "${_modules_dir}") + set(Suil_MODULES_PREFIX "${_modules_prefix}") + file(GLOB Suil_MODULES "${_modules_dir}/*.${_lib_ext}") + list(SORT Suil_MODULES) + endif() + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SuilModules + REQUIRED_VARS Suil_MODULES Suil_MODULES_PREFIX +) \ No newline at end of file diff --git a/cmake/nsis/CMakeLists.txt b/cmake/nsis/CMakeLists.txt index 8363cacf709..a978b4ee4ce 100644 --- a/cmake/nsis/CMakeLists.txt +++ b/cmake/nsis/CMakeLists.txt @@ -53,6 +53,15 @@ SET(CPACK_NSIS_DEFINES "${CPACK_NSIS_DEFINES}" PARENT_SCOPE) SET(CPACK_PACKAGE_ICON "${CPACK_PACKAGE_ICON}" PARENT_SCOPE) SET(CPACK_NSIS_MUI_ICON "${CPACK_NSIS_MUI_ICON}" PARENT_SCOPE) +# Disable cpack's strip for historic reasons +set(CPACK_STRIP_FILES_ORIG "${CPACK_STRIP_FILES}" PARENT_SCOPE) +set(CPACK_STRIP_FILES FALSE PARENT_SCOPE) + +if(CPACK_DEBUG) + # CMake 3.19+ + set(CPACK_NSIS_EXECUTABLE_PRE_ARGUMENTS "-V4") +endif() + # Windows resource compilers CONFIGURE_FILE("lmms.rc.in" "${CMAKE_BINARY_DIR}/lmms.rc") CONFIGURE_FILE("zynaddsubfx.rc.in" "${CMAKE_BINARY_DIR}/plugins/ZynAddSubFx/zynaddsubfx.rc") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c458a5cd227..55f416fae5a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -181,11 +181,11 @@ set_target_properties(lmms PROPERTIES set_target_properties(lmmsobjs PROPERTIES AUTOUIC_SEARCH_PATHS "gui/modals") -IF(NOT WIN32) +IF(NOT WIN32 AND NOT LMMS_BUILD_APPLE) if(CMAKE_INSTALL_MANDIR) SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR}) ELSE(CMAKE_INSTALL_MANDIR) - SET(INSTALL_MANDIR ${CMAKE_INSTALL_PREFIX}/share/man) + SET(INSTALL_MANDIR share/man) ENDIF(CMAKE_INSTALL_MANDIR) INSTALL(FILES "${CMAKE_BINARY_DIR}/lmms.1.gz" DESTINATION "${INSTALL_MANDIR}/man1/" diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index dc26bf2b554..28f5f915a3f 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -235,6 +235,11 @@ bool RemotePlugin::init(const QString &pluginExecutable, m_failed = false; } QString exec = QFileInfo(QDir("plugins:"), pluginExecutable).absoluteFilePath(); + + // We may have received a directory via a environment variable + if (const char* env_path = std::getenv("LMMS_PLUGIN_DIR")) + exec = QFileInfo(QDir(env_path), pluginExecutable).absoluteFilePath(); + #ifdef LMMS_BUILD_APPLE // search current directory first QString curDir = QCoreApplication::applicationDirPath() + "/" + pluginExecutable; @@ -252,7 +257,7 @@ bool RemotePlugin::init(const QString &pluginExecutable, if( ! QFile( exec ).exists() ) { - qWarning( "Remote plugin '%s' not found.", + qWarning( "Remote plugin '%s' not found", exec.toUtf8().constData() ); m_failed = true; invalidate(); diff --git a/src/core/audio/AudioSoundIo.cpp b/src/core/audio/AudioSoundIo.cpp index c7fa380e402..1d63ada3ab1 100644 --- a/src/core/audio/AudioSoundIo.cpp +++ b/src/core/audio/AudioSoundIo.cpp @@ -467,7 +467,8 @@ AudioSoundIo::setupWidget::setupWidget( QWidget * _parent ) : reconnectSoundIo(); - bool ok = connect( &m_backendModel, SIGNAL(dataChanged()), &m_setupUtil, SLOT(reconnectSoundIo())); + [[maybe_unused]] bool ok = connect(&m_backendModel, &ComboBoxModel::dataChanged, + &m_setupUtil, &AudioSoundIoSetupUtil::reconnectSoundIo); assert(ok); m_backend->setModel( &m_backendModel ); @@ -476,7 +477,8 @@ AudioSoundIo::setupWidget::setupWidget( QWidget * _parent ) : AudioSoundIo::setupWidget::~setupWidget() { - bool ok = disconnect( &m_backendModel, SIGNAL(dataChanged()), &m_setupUtil, SLOT(reconnectSoundIo())); + [[maybe_unused]] bool ok = disconnect(&m_backendModel, &ComboBoxModel::dataChanged, + &m_setupUtil, &AudioSoundIoSetupUtil::reconnectSoundIo); assert(ok); if (m_soundio) { diff --git a/src/gui/SampleLoader.cpp b/src/gui/SampleLoader.cpp index f2340852d77..d72b0ba5cc5 100644 --- a/src/gui/SampleLoader.cpp +++ b/src/gui/SampleLoader.cpp @@ -39,7 +39,7 @@ namespace lmms::gui { QString SampleLoader::openAudioFile(const QString& previousFile) { auto openFileDialog = FileDialog(nullptr, QObject::tr("Open audio file")); - auto dir = !previousFile.isEmpty() ? PathUtil::toAbsolute(previousFile) : ConfigManager::inst()->userSamplesDir(); + auto dir = !previousFile.isEmpty() ? QFileInfo(PathUtil::toAbsolute(previousFile)).absolutePath() : ConfigManager::inst()->userSamplesDir(); // change dir to position of previously opened file openFileDialog.setDirectory(dir);