diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index db506b62c..a30bc913b 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -1055,8 +1055,7 @@ jobs: - name: Link Sparkle shell: bash run: | - sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework /Library/Frameworks/Sparkle.framework - sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework.dSYM /Library/Frameworks/Sparkle.framework.dSYM + sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1) /usr/local/opt/sparkle - name: Create Build Environment shell: bash @@ -1082,9 +1081,6 @@ jobs: working-directory: build shell: bash run: make install - - name: Hack to make macdeployqt find plugins - shell: bash - run: sudo ln -s /usr/local/Cellar/qt/$(ls /usr/local/Cellar/qt/ | tail -n1)/share/qt/plugins /usr/local/plugins - name: Create DMG working-directory: build shell: bash @@ -1131,8 +1127,7 @@ jobs: - name: Link Sparkle shell: bash run: | - sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework /Library/Frameworks/Sparkle.framework - sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework.dSYM /Library/Frameworks/Sparkle.framework.dSYM + sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1) /usr/local/opt/sparkle - name: Create Build Environment shell: bash @@ -1158,9 +1153,6 @@ jobs: working-directory: build shell: bash run: make install - - name: Hack to make macdeployqt find plugins - shell: bash - run: sudo ln -s /usr/local/Cellar/qt/$(ls /usr/local/Cellar/qt/ | tail -n1)/share/qt/plugins /usr/local/plugins - name: Create DMG working-directory: build shell: bash diff --git a/.travis.yml b/.travis.yml index aad8aaf20..729abc7bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,7 @@ before_install: - brew install libcdio libmtp - brew install create-dmg - brew install --cask sparkle - - sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework /Library/Frameworks/Sparkle.framework - - sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework.dSYM /Library/Frameworks/Sparkle.framework.dSYM + - sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1) /usr/local/opt/sparkle - export Qt6_DIR=/usr/local/opt/qt6/lib/cmake - export Qt6LinguistTools_DIR=/usr/local/opt/qt6/lib/cmake/Qt6LinguistTools - ls /usr/local/lib/gstreamer-1.0 @@ -31,7 +30,6 @@ before_script: script: - make -j8 - make install - - sudo ln -s /usr/local/Cellar/qt/$(ls /usr/local/Cellar/qt/ | tail -n1)/share/qt/plugins /usr/local/plugins - make dmg2 after_success: - ls -lh strawberry*.dmg diff --git a/3rdparty/macdeployqt/main.cpp b/3rdparty/macdeployqt/main.cpp index b7fd985a1..01fa07588 100644 --- a/3rdparty/macdeployqt/main.cpp +++ b/3rdparty/macdeployqt/main.cpp @@ -278,6 +278,10 @@ int main(int argc, char **argv) if (runStripEnabled) stripAppBinary(appBundlePath); + if (!FinalCheck(appBundlePath)) { + return 1; + } + if (runCodesign) codesign(codesignIdentiy, appBundlePath); @@ -288,4 +292,3 @@ int main(int argc, char **argv) return 0; } - diff --git a/3rdparty/macdeployqt/shared.cpp b/3rdparty/macdeployqt/shared.cpp index 8d50ee75b..69ff9cb9f 100644 --- a/3rdparty/macdeployqt/shared.cpp +++ b/3rdparty/macdeployqt/shared.cpp @@ -856,6 +856,11 @@ void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) { changeInstallName(canonicalInstallName, deployedInstallName, binary); } + // Homebrew workaround, resolve symlink /usr/local/opt/library to /usr/local/Cellar/library + if (framework.installName.startsWith("/usr/local/opt/") && framework.installName.count('/') >= 5) { + canonicalInstallName = QFileInfo(framework.installName.section('/', 0, 4)).canonicalFilePath() + "/" + framework.installName.section('/', 5); + changeInstallName(canonicalInstallName, deployedInstallName, binary); + } } } @@ -1679,3 +1684,110 @@ void fixupFramework(const QString &frameworkName) changeIdentification("@rpath/" + frameworkBinary, frameworkBinary); addRPath("@loader_path/../../Contents/Frameworks/", frameworkBinary); } + +bool FinalCheck(const QString &appBundlePath) { + + bool success = true; + + QDirIterator iter(appBundlePath, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories); + while (iter.hasNext()) { + iter.next(); + QString filepath = iter.fileInfo().filePath(); + + if (filepath.endsWith(".plist") || + filepath.endsWith(".icns") || + filepath.endsWith(".prl") || + filepath.endsWith(".conf") || + filepath.endsWith(".h") || + filepath.endsWith(".nib") || + filepath.endsWith(".strings") || + filepath.endsWith(".css") || + filepath.endsWith("CodeResources") || + filepath.endsWith("PkgInfo") || + filepath.endsWith(".modulemap")) { + continue; + } + + //qDebug() << "Final check on" << filepath; + + QProcess otool; + otool.start("otool", QStringList() << "-L" << filepath); + otool.waitForFinished(); + if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) { + LogError() << otool.readAllStandardError(); + success = false; + continue; + } + QString output = otool.readAllStandardOutput(); + QStringList output_lines = output.split("\n", Qt::SkipEmptyParts); + if (output_lines.size() < 2) { + LogError() << "Could not parse otool output:" << output; + success = false; + continue; + } + QString first_line = output_lines.first(); + if (first_line.endsWith(':')) first_line.chop(1); + if (first_line == filepath) { + output_lines.removeFirst(); + } + else { + LogError() << "First line" << first_line << "does not match" << filepath; + success = false; + } + static const QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$")); + for (const QString &output_line : output_lines) { + + //qDebug() << "Final check on" << filepath << output_line; + + const auto match = regexp.match(output_line); + if (match.hasMatch()) { + QString library = match.captured(1); + if (QFileInfo(library).fileName() == QFileInfo(filepath).fileName()) { // It's this. + continue; + } + else if (library.startsWith("@executable_path")) { + QString real_path = library; + real_path = real_path.replace("@executable_path", appBundlePath + "/Contents/MacOS"); + if (!QFile(real_path).exists()) { + LogError() << real_path << "does not exist for" << filepath; + success = false; + } + } + else if (library.startsWith("@rpath")) { + QString real_path = library; + real_path = real_path.replace("@rpath", appBundlePath + "/Contents/Frameworks"); + if (!QFile(real_path).exists() && !real_path.endsWith("QtSvg")) { // FIXME: Ignore broken svg image plugin. + LogError() << real_path << "does not exist for" << filepath; + success = false; + } + } + else if (library.startsWith("@loader_path")) { + QString loader_path = QFileInfo(filepath).path(); + QString real_path = library; + real_path = real_path.replace("@loader_path", loader_path); + if (!QFile(real_path).exists()) { + LogError() << real_path << "does not exist for" << filepath; + success = false; + } + } + else if (library.startsWith("/System/Library/") || library.startsWith("/usr/lib/")) { // System library + continue; + } + else if (library.endsWith("libgcc_s.1.dylib")) { // fftw points to it for some reason. + continue; + } + else { + LogError() << "File" << filepath << "points to" << library; + success = false; + } + } + else { + LogError() << "Could not parse otool output line:" << output_line; + success = false; + } + } + } + + return success; + +} diff --git a/3rdparty/macdeployqt/shared.h b/3rdparty/macdeployqt/shared.h index 15ff08430..c6b8c692d 100644 --- a/3rdparty/macdeployqt/shared.h +++ b/3rdparty/macdeployqt/shared.h @@ -136,6 +136,6 @@ QSet<QString> codesignBundle(const QString &identity, void codesign(const QString &identity, const QString &appBundlePath); void createDiskImage(const QString &appBundlePath, const QString &filesystemType); void fixupFramework(const QString &appBundlePath); - +bool FinalCheck(const QString &appBundlePath); #endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 725f6a398..996401333 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -251,7 +251,7 @@ set(SINGLEAPPLICATION_LIBRARIES singleapplication) set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication) if(APPLE) - find_library(SPARKLE Sparkle) + find_library(SPARKLE Sparkle PATHS "/usr/local/opt/sparkle") add_subdirectory(3rdparty/macdeployqt) add_subdirectory(3rdparty/SPMediaKeyTap) set(SPMEDIAKEYTAP_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SPMediaKeyTap) diff --git a/cmake/Dmg.cmake b/cmake/Dmg.cmake index 2cc75c777..a030c51e7 100644 --- a/cmake/Dmg.cmake +++ b/cmake/Dmg.cmake @@ -21,12 +21,16 @@ endif() if(MACDEPLOYQT_EXECUTABLE AND CREATEDMG_EXECUTABLE AND MACOS_VERSION_PACKAGE) add_custom_target(dmg - COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader -verbose=3 + COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/ + COMMAND cp -r /usr/local/opt/sparkle/Sparkle.framework ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/ + COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader COMMAND ${CREATEDMG_EXECUTABLE} --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${MACOS_VERSION_PACKAGE}-${CMAKE_HOST_SYSTEM_PROCESSOR}.dmg strawberry.app WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) add_custom_target(dmg2 - COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader -verbose=3 + COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/ + COMMAND cp -r /usr/local/opt/sparkle/Sparkle.framework ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/ + COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader COMMAND ${CREATEDMG_EXECUTABLE} --skip-jenkins --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${MACOS_VERSION_PACKAGE}-${CMAKE_HOST_SYSTEM_PROCESSOR}.dmg strawberry.app WORKING_DIRECTORY ${CMAKE_BINARY_DIR} )