Implement Mac App Store build support by introducing the BUILD_FOR_MAC_APP_STORE option. This change disables Sparkle and localhost OAuth redirect server for MAS builds, updates CMake configuration, and modifies build scripts accordingly. Additionally, the macOS bundle identifier is now configurable via CMake.

This commit is contained in:
2026-01-22 19:28:00 +09:00
parent 6a1d8bbc87
commit bd59c19301
11 changed files with 479 additions and 29 deletions

View File

@@ -6,6 +6,14 @@ if(APPLE)
enable_language(OBJC OBJCXX)
endif()
if(APPLE)
option(BUILD_FOR_MAC_APP_STORE "Build for Mac App Store (MAS): disables Sparkle + any localhost port-listener OAuth redirect server, and uses MAS-focused defaults." OFF)
else()
set(BUILD_FOR_MAC_APP_STORE OFF)
endif()
set(MACOS_BUNDLE_ID "com.dryark.strawberry" CACHE STRING "macOS bundle identifier (CFBundleIdentifier)")
if(POLICY CMP0054)
cmake_policy(SET CMP0054 NEW)
endif()
@@ -32,18 +40,24 @@ if(LINUX)
endif()
if(APPLE)
# Find Sparkle early so cmake/Dmg.cmake (deploy target) can bundle it into the app.
# Sparkle is optional; if not found, update functionality is disabled.
find_library(SPARKLE Sparkle
PATHS
/Library/Frameworks
/System/Library/Frameworks
/opt/homebrew/Frameworks
/opt/homebrew/opt/sparkle-framework/Frameworks
/usr/local/Frameworks
/usr/local/opt/sparkle-framework/Frameworks
PATH_SUFFIXES Frameworks
)
if(BUILD_FOR_MAC_APP_STORE)
# MAS builds: Sparkle (and QtSparkle) must be disabled.
set(ENABLE_SPARKLE OFF CACHE BOOL "Sparkle integration" FORCE)
set(ENABLE_QTSPARKLE OFF CACHE BOOL "QtSparkle integration" FORCE)
else()
# Find Sparkle early so cmake/Dmg.cmake (deploy target) can bundle it into the app.
# Sparkle is optional; if not found, update functionality is disabled.
find_library(SPARKLE Sparkle
PATHS
/Library/Frameworks
/System/Library/Frameworks
/opt/homebrew/Frameworks
/opt/homebrew/opt/sparkle-framework/Frameworks
/usr/local/Frameworks
/usr/local/opt/sparkle-framework/Frameworks
PATH_SUFFIXES Frameworks
)
endif()
include(cmake/Dmg.cmake)
endif()

View File

@@ -91,7 +91,7 @@ run_with_heartbeat() {
usage() {
cat <<'EOF'
Usage:
./build_tools/macos/build_app.sh [--debug|--release] [--deploy] [--dmg] [--clean] [--build-dir <path>]
./build_tools/macos/build_app.sh [--debug|--release] [--mas] [--deploy] [--dmg] [--clean] [--build-dir <path>]
What it does:
- Configures and builds Strawberry with CMake + Ninja
@@ -100,6 +100,7 @@ What it does:
Options:
--release Release build (default)
--debug Debug build
--mas Build for Mac App Store (BUILD_FOR_MAC_APP_STORE=ON). Disables Sparkle/QtSparkle and any localhost OAuth redirect listener.
--deploy Run: cmake --build <builddir> --target deploy
--dmg Run: cmake --build <builddir> --target dmg (implies --deploy)
--clean Delete the build dir before configuring
@@ -109,6 +110,7 @@ EOF
}
config="Release"
do_mas=0
do_deploy=0
do_dmg=0
do_clean=0
@@ -118,6 +120,7 @@ while [[ $# -gt 0 ]]; do
case "$1" in
--release) config="Release"; shift ;;
--debug) config="Debug"; shift ;;
--mas) do_mas=1; shift ;;
--deploy) do_deploy=1; shift ;;
--dmg) do_dmg=1; do_deploy=1; shift ;;
--clean) do_clean=1; shift ;;
@@ -165,6 +168,9 @@ fi
echo "==> [$(ts)] Repo: ${repo_root}"
echo "==> [$(ts)] Build dir: ${build_dir}"
echo "==> [$(ts)] Config: ${config}"
if [[ "$do_mas" -eq 1 ]]; then
echo "==> [$(ts)] MAS: enabled (BUILD_FOR_MAC_APP_STORE=ON)"
fi
if [[ "$do_clean" -eq 1 ]]; then
echo "==> [$(ts)] Cleaning build dir"
@@ -213,6 +219,11 @@ cmake_prefix_path="${qt_prefix};${brew_prefix}"
cmake_extra_args=()
# Mac App Store build mode
if [[ "$do_mas" -eq 1 ]]; then
cmake_extra_args+=("-DBUILD_FOR_MAC_APP_STORE=ON")
fi
# Optional: override Sparkle update feed / key for your own published builds.
# Example:
# export SPARKLE_FEED_URL="https://example.com/appcast.xml"

View File

@@ -0,0 +1,26 @@
Encryption Export Compliance Statement (EAR)
App Name: Strawberry
Bundle ID: @BUNDLE_ID@
Version: @VERSION@
Developer: @DEVELOPER@
Date: @DATE@
Contact: @CONTACT@
Statement
---------
This app uses encryption only for standard network communications security (for example, TLS/HTTPS connections to web services).
The app does not implement proprietary or non-standard encryption algorithms, and it does not ship its own cryptographic library in place of Apples operating system encryption.
The app uses only encryption provided by Apples operating system (e.g., Apple-provided TLS stacks accessed through system frameworks used by the app and its dependencies).
The app is not a VPN client/server, does not provide end-to-end encrypted messaging, and does not provide user-controlled key management or custom cryptographic functionality beyond standard transport security.
Accordingly, the apps use of encryption is believed to qualify as exempt under U.S. export regulations for mass-market software using standard OS-provided encryption.
Reference
---------
Apple: Complying with Encryption Export Regulations
https://developer.apple.com/documentation/security/complying-with-encryption-export-regulations

View File

@@ -0,0 +1,103 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
./build_tools/macos/export_compliance/make_pdf.sh \
--bundle-id com.dryark.strawberry \
--version 1.2.3 \
--developer "Dry Ark LLC" \
--contact "support@example.com"
Outputs (in the same folder as this script):
- EXPORT_COMPLIANCE.filled.txt
- EXPORT_COMPLIANCE.pdf
Notes:
- Uses macOS built-in /usr/bin/textutil + /usr/sbin/cupsfilter to generate the PDF.
EOF
}
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
template="${script_dir}/EXPORT_COMPLIANCE.txt"
filled="${script_dir}/EXPORT_COMPLIANCE.filled.txt"
pdf="${script_dir}/EXPORT_COMPLIANCE.pdf"
bundle_id=""
version=""
developer=""
contact=""
date_str="$(date +%Y-%m-%d)"
while [[ $# -gt 0 ]]; do
case "$1" in
--bundle-id) bundle_id="${2:-}"; shift 2 ;;
--version) version="${2:-}"; shift 2 ;;
--developer) developer="${2:-}"; shift 2 ;;
--contact) contact="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown arg: $1" >&2; usage; exit 2 ;;
esac
done
if [[ -z "$bundle_id" || -z "$version" || -z "$developer" || -z "$contact" ]]; then
echo "Error: missing required args." >&2
usage
exit 2
fi
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "Error: This script is for macOS only (uses textutil)." >&2
exit 1
fi
if [[ ! -f "$template" ]]; then
echo "Error: missing template file: $template" >&2
exit 1
fi
if [[ ! -x /usr/bin/textutil ]]; then
echo "Error: /usr/bin/textutil not found. This should exist on macOS." >&2
exit 1
fi
if [[ ! -x /usr/sbin/cupsfilter ]]; then
echo "Error: /usr/sbin/cupsfilter not found. This should exist on macOS." >&2
exit 1
fi
escape_sed_repl() {
# Escape characters that are special in sed replacement strings: \ & and delimiter |
# bash 3.2 compatible
echo "$1" | sed -e 's/[\\&|]/\\&/g'
}
bundle_id_esc="$(escape_sed_repl "$bundle_id")"
version_esc="$(escape_sed_repl "$version")"
developer_esc="$(escape_sed_repl "$developer")"
contact_esc="$(escape_sed_repl "$contact")"
date_esc="$(escape_sed_repl "$date_str")"
sed \
-e "s|@BUNDLE_ID@|${bundle_id_esc}|g" \
-e "s|@VERSION@|${version_esc}|g" \
-e "s|@DEVELOPER@|${developer_esc}|g" \
-e "s|@CONTACT@|${contact_esc}|g" \
-e "s|@DATE@|${date_esc}|g" \
"$template" > "$filled"
rm -f "$pdf" >/dev/null 2>&1 || true
# textutil does not support direct PDF output on modern macOS; convert to HTML first, then print-to-PDF via cupsfilter.
tmp_html="${script_dir}/EXPORT_COMPLIANCE.tmp.html"
rm -f "$tmp_html" >/dev/null 2>&1 || true
/usr/bin/textutil -convert html "$filled" -output "$tmp_html" >/dev/null
# Convert HTML to PDF. cupsfilter writes to stdout by default.
/usr/sbin/cupsfilter -i text/html -m application/pdf "$tmp_html" > "$pdf"
rm -f "$tmp_html" >/dev/null 2>&1 || true
echo "Wrote:"
echo " $filled"
echo " $pdf"

33
dist/CMakeLists.txt vendored
View File

@@ -10,21 +10,26 @@ if(APPLE)
set(LSMinimumSystemVersion 12.0)
endif()
# Sparkle (macOS updates)
# These values are embedded into Info.plist and control where the app checks for updates.
# Downstream builders can override on the CMake command line:
# -DSPARKLE_FEED_URL="https://example.com/appcast.xml"
# -DSPARKLE_PUBLIC_ED25519_KEY="base64=="
#
# Defaults preserve upstream behavior, but are intentionally configurable for third-party builds.
if(NOT DEFINED SPARKLE_FEED_URL)
set(SPARKLE_FEED_URL "https://www.strawberrymusicplayer.org/sparkle-macos-@ARCH@")
endif()
if(NOT DEFINED SPARKLE_PUBLIC_ED25519_KEY)
set(SPARKLE_PUBLIC_ED25519_KEY "/OydhYVfypuO2Mf7G6DUqVZWW9G19eFV74qaDCBTOUk=")
endif()
if(BUILD_FOR_MAC_APP_STORE)
# MAS builds must not embed Sparkle update configuration in Info.plist.
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.mas.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist)
else()
# Sparkle (macOS updates)
# These values are embedded into Info.plist and control where the app checks for updates.
# Downstream builders can override on the CMake command line:
# -DSPARKLE_FEED_URL="https://example.com/appcast.xml"
# -DSPARKLE_PUBLIC_ED25519_KEY="base64=="
#
# Defaults preserve upstream behavior, but are intentionally configurable for third-party builds.
if(NOT DEFINED SPARKLE_FEED_URL)
set(SPARKLE_FEED_URL "https://www.strawberrymusicplayer.org/sparkle-macos-@ARCH@")
endif()
if(NOT DEFINED SPARKLE_PUBLIC_ED25519_KEY)
set(SPARKLE_PUBLIC_ED25519_KEY "/OydhYVfypuO2Mf7G6DUqVZWW9G19eFV74qaDCBTOUk=")
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist)
endif()
endif()
if(WIN32)

253
dist/macos/Info.mas.plist.in vendored Normal file
View File

@@ -0,0 +1,253 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>strawberry</string>
<key>CFBundleGetInfoString</key>
<string>Strawberry ${STRAWBERRY_VERSION_DISPLAY}</string>
<key>CFBundleIconFile</key>
<string>strawberry.icns</string>
<key>CFBundleIdentifier</key>
<string>@MACOS_BUNDLE_ID@</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${STRAWBERRY_VERSION_DISPLAY}</string>
<key>CFBundleName</key>
<string>Strawberry</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${STRAWBERRY_VERSION_DISPLAY}</string>
<key>CFBundleVersion</key>
<string>${STRAWBERRY_VERSION_PACKAGE}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.music</string>
<key>LSMinimumSystemVersion</key>
<string>@LSMinimumSystemVersion@</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>@MACOS_BUNDLE_ID@</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tidal</string>
</array>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeOSTypes</key>
<array>
<string>****</string>
<string>fold</string>
<string>disk</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>xspf</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Generic.icns</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>application/xspf+xml</string>
</array>
<key>CFBundleTypeName</key>
<string>XSPF Playlist</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>wav</string>
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>audio/x-wav</string>
</array>
<key>CFBundleTypeName</key>
<string>WAVE Audio File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>pls</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>pls.icns</string>
<key>CFBundleTypeName</key>
<string>Shoutcast playlist</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>m3u</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>m3u.icns</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>audio/x-mpegurl</string>
</array>
<key>CFBundleTypeName</key>
<string>Playlist file</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>aac</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>mpeg4.icns</string>
<key>CFBundleTypeName</key>
<string>AAC file</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>ogg</string>
<string>ogx</string>
<string>ogm</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ogg.icns</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>audio/ogg</string>
</array>
<key>CFBundleTypeName</key>
<string>Ogg Vorbis File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>oga</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>ogg.icns</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>audio/ogg</string>
</array>
<key>CFBundleTypeName</key>
<string>Ogg Audio File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>wma</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>wma.icns</string>
<key>CFBundleTypeName</key>
<string>WIndows Media Audio</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mp3</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>mp3.icns</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>audio/mpeg</string>
</array>
<key>CFBundleTypeName</key>
<string>MPEG Audio Layer 3</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>3gp</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>generic.icns</string>
<key>CFBundleTypeName</key>
<string>3GPP File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>m4a</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>mpeg4.icns</string>
<key>CFBundleTypeName</key>
<string>MPEG-4 Audio File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mpc</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>generic.icns</string>
<key>CFBundleTypeName</key>
<string>Musepack Audio File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>flac</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>generic.icns</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>audio/flac</string>
</array>
<key>CFBundleTypeName</key>
<string>FLAC Audio File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
</dict>
</plist>

View File

@@ -13,7 +13,7 @@
<key>CFBundleIconFile</key>
<string>strawberry.icns</string>
<key>CFBundleIdentifier</key>
<string>org.strawberrymusicplayer.strawberry</string>
<string>@MACOS_BUNDLE_ID@</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
@@ -34,6 +34,8 @@
<string>public.app-category.music</string>
<key>LSMinimumSystemVersion</key>
<string>@LSMinimumSystemVersion@</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>SUFeedURL</key>
<string>@SPARKLE_FEED_URL@</string>
<key>SUPublicEDKey</key>
@@ -44,7 +46,7 @@
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>org.strawberrymusicplayer.strawberry</string>
<string>@MACOS_BUNDLE_ID@</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tidal</string>

20
dist/macos/entitlements.mas.plist vendored Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Enable the App Sandbox (required for Mac App Store). -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Strawberry is a client app that needs outbound network access (streaming/scrobbling/etc). -->
<key>com.apple.security.network.client</key>
<true/>
<!-- Allow access to user-selected music folders/files (via NSOpenPanel security-scoped bookmarks). -->
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<!-- If iPod classic / other device access is rejected, we'll adjust entitlements after App Review feedback. -->
</dict>
</plist>

View File

@@ -7,6 +7,8 @@
#cmakedefine USE_INSTALL_PREFIX
#cmakedefine BUILD_FOR_MAC_APP_STORE
#cmakedefine HAVE_BACKTRACE
#cmakedefine HAVE_ALSA
#cmakedefine HAVE_PULSE

View File

@@ -52,6 +52,12 @@ LocalRedirectServer::~LocalRedirectServer() {
bool LocalRedirectServer::Listen() {
#ifdef BUILD_FOR_MAC_APP_STORE
success_ = false;
error_ = "Local redirect server is disabled in Mac App Store builds."_L1;
return false;
#endif
if (!listen(QHostAddress::LocalHost, static_cast<quint64>(port_))) {
success_ = false;
error_ = errorString();

View File

@@ -236,6 +236,14 @@ void OAuthenticator::Authenticate() {
return;
}
// Mac App Store builds: do not start any localhost listening redirect server.
#ifdef BUILD_FOR_MAC_APP_STORE
if (use_local_redirect_server_) {
Q_EMIT AuthenticationFinished(false, tr("This authentication flow is disabled in Mac App Store builds."));
return;
}
#endif
QUrl redirect_url(redirect_url_);
if (use_local_redirect_server_) {