From e6c7051b6fd0d197188f400c1ab4898ed0e2783a Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Fri, 27 Jan 2023 09:07:24 -0800 Subject: [PATCH] Multi bundle support. --- qbs/imports/VDynamicLib.qbs | 8 ++ qbs/imports/VLib.qbs | 7 +- qbs/imports/VToolApp.qbs | 4 +- qbs/modules/buildconfig/buildconfig.qbs | 5 + qbs/modules/multibundle/multibundle.qbs | 130 ++++++++++++++++++ src/app/puzzle/puzzle.qbs | 8 +- src/app/tape/tape.qbs | 6 +- src/app/valentina/core/vapplication.cpp | 73 +++++++++- src/app/valentina/core/vapplication.h | 2 + src/app/valentina/mainwindow.cpp | 36 ++--- src/app/valentina/valentina.qbs | 2 + src/libs/qmuparser/qmuparser.qbs | 4 + .../vpropertyexplorer/vpropertyexplorer.qbs | 2 + 13 files changed, 243 insertions(+), 44 deletions(-) create mode 100644 qbs/modules/multibundle/multibundle.qbs diff --git a/qbs/imports/VDynamicLib.qbs b/qbs/imports/VDynamicLib.qbs index 9962695d3..9e8eea231 100644 --- a/qbs/imports/VDynamicLib.qbs +++ b/qbs/imports/VDynamicLib.qbs @@ -10,4 +10,12 @@ VLib { condition: i18nconfig.limitDeploymentOfQtTranslations windeployqt.languages: i18nconfig.qtTranslationLocales.join(',') } + + installDebugInformation: qbs.buildVariant !== "release" + + Properties { + condition: !qbs.targetOS.contains("macos") || (qbs.targetOS.contains("macos") && !buildconfig.enableMultiBundle) + install: true + installDir: buildconfig.installLibraryPath + } } diff --git a/qbs/imports/VLib.qbs b/qbs/imports/VLib.qbs index 9e4203e7f..390e67728 100644 --- a/qbs/imports/VLib.qbs +++ b/qbs/imports/VLib.qbs @@ -7,7 +7,7 @@ Library { type: buildconfig.staticBuild ? "staticlibrary" : "dynamiclibrary" - buildconfig.appTarget: "valentina" + buildconfig.appTarget: qbs.targetOS.contains("macos") ? "Valentina" : "valentina" bundle.isBundle: buildconfig.frameworksBuild cpp.includePaths: [".."] @@ -24,9 +24,8 @@ Library { cpp.compilerWrapper: "ccache" } - install: !buildconfig.staticBuild - installDir: buildconfig.installLibraryPath - installDebugInformation: !buildconfig.staticBuild + install: false + installDebugInformation: false Properties { condition: qbs.targetOS.contains("macos") diff --git a/qbs/imports/VToolApp.qbs b/qbs/imports/VToolApp.qbs index f4b98e771..682562300 100644 --- a/qbs/imports/VToolApp.qbs +++ b/qbs/imports/VToolApp.qbs @@ -17,6 +17,8 @@ VApp { bundle.isBundle: qbs.buildVariant === "release" bundle.identifierPrefix: 'ua.com.smart-pattern' + property bool primaryApp: false + bundle.infoPlist:({ "NSHumanReadableCopyright": buildconfig.valentina_copyright_string }) @@ -74,7 +76,7 @@ VApp { Rule { multiplex: true - condition: qbs.targetOS.contains("macos") && bundle.isBundle + condition: qbs.targetOS.contains("macos") && bundle.isBundle && (buildconfig.enableMultiBundle || primaryApp) inputs: ["qm"] outputFileTags: ["bundle.qm", "bundle.content"] outputArtifacts: { diff --git a/qbs/modules/buildconfig/buildconfig.qbs b/qbs/modules/buildconfig/buildconfig.qbs index f2cc396f0..0c3f9b14c 100644 --- a/qbs/modules/buildconfig/buildconfig.qbs +++ b/qbs/modules/buildconfig/buildconfig.qbs @@ -22,6 +22,8 @@ Module { property bool enableAppImage: false + property bool enableMultiBundle: false + property string valentina_copyright_year: { return new Date().getFullYear().toString(); } property string valentina_copyright_string: "(C) 2013-" + valentina_copyright_year + ", Valentina project" @@ -117,6 +119,9 @@ Module { if (enableAppImage && qbs.targetOS.contains("unix") && !qbs.targetOS.contains("macos")) defines.push('APPIMAGE'); + if (enableMultiBundle) + defines.push('MULTI_BUNDLE'); + return defines; } diff --git a/qbs/modules/multibundle/multibundle.qbs b/qbs/modules/multibundle/multibundle.qbs new file mode 100644 index 000000000..87b722b2d --- /dev/null +++ b/qbs/modules/multibundle/multibundle.qbs @@ -0,0 +1,130 @@ +import qbs.File + +Module { + additionalProductTypes: ["multibundle"] + + property stringList targetApps: undefined + + Rule { +// alwaysRun: true + multiplex: true + condition: product.qbs.targetOS.contains("macos") && product.buildconfig.enableMultiBundle && product.type.contains("dynamiclibrary") + inputs: product.bundle.isBundle ? ["bundle.content"] : ["dynamiclibrary"] + outputFileTags: ["multibundle"] + outputArtifacts: { + var artifactNames = []; + + const fileName = product.bundle.isBundle ? product.bundle.bundleName : inputs["dynamiclibrary"][0].fileName; + + const installRoot = product.qbs.installRoot + product.qbs.installPrefix + "/" + product.buildconfig.installAppPath; + product.multibundle.targetApps.forEach(function(targetApp) { + artifactNames.push(installRoot + "/" + targetApp + ".app/Contents/Frameworks/" + fileName); + + if (product.installDebugInformation) + artifactNames.push(installRoot + "/" + targetApp + ".app/Contents/Frameworks/" + fileName + + product.cpp.debugInfoBundleSuffix); + }); + + var artifacts = artifactNames.map(function(art){ + var a = { + filePath: art, + fileTags: ["multibundle"] + } + return a; + }); + return artifacts; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Copying dynamic library into bundles"; + cmd.highlight = "filegen"; + + const fileName = product.bundle.isBundle ? product.bundle.bundleName : inputs["dynamiclibrary"][0].fileName; + const installRoot = product.qbs.installRoot + product.qbs.installPrefix + "/" + product.buildconfig.installAppPath; + var data = []; + product.multibundle.targetApps.forEach(function(targetApp) { + data.push({ + "source" : product.buildDirectory + "/" + fileName, + "destination": installRoot + "/" + targetApp + ".app/Contents/Frameworks/" + fileName + }); + + if (product.installDebugInformation) + data.push({ + "source" : product.buildDirectory + "/" + fileName + product.cpp.debugInfoBundleSuffix, + "destination": installRoot + "/" + targetApp + ".app/Contents/Frameworks/" + fileName + + product.cpp.debugInfoBundleSuffix + }); + }); + + cmd.data = data; + + cmd.sourceCode = function() { + data.forEach(function(copyData) { + File.copy(copyData.source, copyData.destination); + }); + }; + return [cmd]; + } + } + + Rule { +// alwaysRun: true + condition: product.qbs.targetOS.contains("macos") && !product.buildconfig.enableMultiBundle && product.type.contains("application") + inputs: ["application"] + outputFileTags: ["multibundle"] + outputArtifacts: { + var artifactNames = []; + + const installRoot = product.qbs.installRoot + product.qbs.installPrefix + "/" + product.buildconfig.installAppPath; + product.multibundle.targetApps.forEach(function(targetApp) { + artifactNames.push(installRoot + "/" + targetApp + ".app/Contents/MacOS/" + input.fileName); + + if (product.installDebugInformation) + artifactNames.push(installRoot + "/" + targetApp + ".app/Contents/MacOS/" + input.fileName + + product.cpp.debugInfoBundleSuffix); + }); + + var artifacts = artifactNames.map(function(art){ + var a = { + filePath: art, + fileTags: ["multibundle"] + } + return a; + }); + return artifacts; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Copying auxiliary binary into bundle"; + cmd.highlight = "filegen"; + + const fileName = product.bundle.isBundle ? product.bundle.bundleName : inputs["dynamiclibrary"][0].fileName; + const installRoot = product.qbs.installRoot + product.qbs.installPrefix + "/" + product.buildconfig.installAppPath; + var data = []; + product.multibundle.targetApps.forEach(function(targetApp) { + data.push({ + "source" : input.filePath, + "destination": installRoot + "/" + targetApp + ".app/Contents/MacOS/" + input.fileName + }); + + if (product.installDebugInformation) + data.push({ + "source" : product.buildDirectory + "/" + input.fileName + product.cpp.debugInfoBundleSuffix, + "destination": installRoot + "/" + targetApp + ".app/Contents/MacOS/" + fileName + + product.cpp.debugInfoBundleSuffix + }); + }); + + cmd.data = data; + + cmd.sourceCode = function() { + data.forEach(function(copyData) { + console.info("Dynamic source: " + copyData.source); + console.info("Dynamic destination: " + copyData.destination); + File.copy(copyData.source, copyData.destination); + }); + }; + return [cmd]; + } + } +} diff --git a/src/app/puzzle/puzzle.qbs b/src/app/puzzle/puzzle.qbs index df5a5c471..661c1397b 100644 --- a/src/app/puzzle/puzzle.qbs +++ b/src/app/puzzle/puzzle.qbs @@ -10,10 +10,12 @@ VToolApp { Depends { name: "VFormatLib" } Depends { name: "VWidgetsLib" } Depends { name: "FervorLib" } + Depends { name: "multibundle"; } name: "Puzzle" buildconfig.appTarget: qbs.targetOS.contains("macos") ? "Puzzle" : "puzzle" targetName: buildconfig.appTarget + multibundle.targetApps: ["Valentina"] files: [ "main.cpp", @@ -176,7 +178,7 @@ VToolApp { } Group { - condition: qbs.targetOS.contains("macos") && qbs.architecture.contains("x86_64") + condition: qbs.targetOS.contains("macos") && qbs.architecture.contains("x86_64") && buildconfig.enableMultiBundle name: "pdftops MacOS" prefix: project.sourceDirectory + "/dist/macx/bin64/" files: ["pdftops"] @@ -275,7 +277,7 @@ VToolApp { Group { name: "MacOS assets" - condition: qbs.targetOS.contains("macos") + condition: qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle prefix: project.sourceDirectory + "/dist/macx/puzzle/" files: [ "Info.plist", @@ -285,7 +287,7 @@ VToolApp { Group { name: "ICNS" - condition: qbs.targetOS.contains("macos") + condition: qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle prefix: project.sourceDirectory + "/dist/macx/valentina-project.xcassets/" files: "layout.iconset" } diff --git a/src/app/tape/tape.qbs b/src/app/tape/tape.qbs index b0087e24c..2dbfa6b1a 100644 --- a/src/app/tape/tape.qbs +++ b/src/app/tape/tape.qbs @@ -12,10 +12,12 @@ VToolApp { Depends { name: "VWidgetsLib"; } Depends { name: "VToolsLib"; } Depends { name: "ebr" } + Depends { name: "multibundle"; } name: "Tape" buildconfig.appTarget: qbs.targetOS.contains("macos") ? "Tape" : "tape" targetName: buildconfig.appTarget + multibundle.targetApps: ["Valentina"] files: [ "main.cpp", @@ -220,7 +222,7 @@ VToolApp { Group { name: "MacOS assets" - condition: qbs.targetOS.contains("macos") + condition: qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle prefix: project.sourceDirectory + "/dist/macx/tape/" files: [ "Info.plist", @@ -230,7 +232,7 @@ VToolApp { Group { name: "ICNS" - condition: qbs.targetOS.contains("macos") + condition: qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle prefix: project.sourceDirectory + "/dist/macx/valentina-project.xcassets/" files: [ "i-measurements.iconset", diff --git a/src/app/valentina/core/vapplication.cpp b/src/app/valentina/core/vapplication.cpp index 09773f539..ed5f50178 100644 --- a/src/app/valentina/core/vapplication.cpp +++ b/src/app/valentina/core/vapplication.cpp @@ -78,19 +78,42 @@ namespace { auto AppFilePath(const QString &appName) -> QString { - QString appNameExe = appName; // NOLINT(performance-unnecessary-copy-initialization) #ifdef Q_OS_WIN - appNameExe += ".exe"; + const QString executableSuffix = QStringLiteral(".exe"); +#else + const QString executableSuffix; #endif - QFileInfo canonicalFile(QStringLiteral("%1/%2").arg(QCoreApplication::applicationDirPath(), appNameExe)); + + QFileInfo canonicalFile(QStringLiteral("%1/%2").arg(QCoreApplication::applicationDirPath(), + appName + executableSuffix)); if (canonicalFile.exists()) { return canonicalFile.absoluteFilePath(); } +#if defined(Q_OS_MACOS) && defined(QBS_BUILD) && defined(MULTI_BUNDLE) + QFileInfo multiBundleFile(QStringLiteral("%1/../../../%2.app/Contents/MacOS/%2") + .arg(QCoreApplication::applicationDirPath(), appName)); + if (multiBundleFile.exists()) + { + return multiBundleFile.absoluteFilePath(); + } +#endif + +#if !defined(QBS_BUILD) QFileInfo debugFile(QStringLiteral("%1/../../%2/bin/%3") - .arg(QCoreApplication::applicationDirPath(), appName, appNameExe)); - return debugFile.exists() ? debugFile.absoluteFilePath() : appNameExe; + .arg(QCoreApplication::applicationDirPath(), appName, appName + executableSuffix)); + if (debugFile.exists()) + { + return debugFile.absoluteFilePath(); + } +#endif + +#if !defined(Q_OS_MACOS) + return appName + executableSuffix; +#else + return appName + QStringLiteral(".app"); +#endif } } // namespace @@ -489,13 +512,23 @@ void VApplication::ActivateDarkMode() //--------------------------------------------------------------------------------------------------------------------- auto VApplication::TapeFilePath() -> QString { - return AppFilePath(QStringLiteral("tape")); +#ifdef Q_OS_MACOS + const QString appName = QStringLiteral("Tape"); +#else + const QString appName = QStringLiteral("tape"); +#endif + return AppFilePath(appName); } //--------------------------------------------------------------------------------------------------------------------- auto VApplication::PuzzleFilePath() -> QString { - return AppFilePath(QStringLiteral("puzzle")); +#ifdef Q_OS_MACOS + const QString appName = QStringLiteral("Puzzle"); +#else + const QString appName = QStringLiteral("puzzle"); +#endif + return AppFilePath(appName); } //--------------------------------------------------------------------------------------------------------------------- @@ -642,6 +675,32 @@ void VApplication::InitOptions() ActivateDarkMode(); } +//--------------------------------------------------------------------------------------------------------------------- +void VApplication::StartDetachedProcess(const QString &program, const QStringList &arguments) +{ +#if !defined(Q_OS_MACOS) + const QString workingDirectory = QFileInfo(program).absoluteDir().absolutePath(); + QProcess::startDetached(program, arguments, workingDirectory); +#else + if (not program.endsWith(".app")) + { + const QString workingDirectory = QFileInfo(program).absoluteDir().absolutePath(); + QProcess::startDetached(program, arguments, workingDirectory); + } + else + { + QStringList openArguments {"-n", QStringLiteral("/Applications/%1").arg(program)}; + if (not arguments.isEmpty()) + { + openArguments.append("--args"); + openArguments += arguments; + } + + QProcess::startDetached("open", openArguments); + } +#endif +} + //--------------------------------------------------------------------------------------------------------------------- auto VApplication::LabelLanguages() -> QStringList { diff --git a/src/app/valentina/core/vapplication.h b/src/app/valentina/core/vapplication.h index 9bc06abf2..5143583a5 100644 --- a/src/app/valentina/core/vapplication.h +++ b/src/app/valentina/core/vapplication.h @@ -51,6 +51,8 @@ public: void InitOptions(); + static void StartDetachedProcess(const QString &program, const QStringList &arguments); + static auto TapeFilePath() -> QString; static auto PuzzleFilePath() -> QString; diff --git a/src/app/valentina/mainwindow.cpp b/src/app/valentina/mainwindow.cpp index 3812ae894..8d6203cf5 100644 --- a/src/app/valentina/mainwindow.cpp +++ b/src/app/valentina/mainwindow.cpp @@ -2127,9 +2127,7 @@ void MainWindow::ShowMeasurements() arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); } - const QString tape = VApplication::TapeFilePath(); - const QString workingDirectory = QFileInfo(tape).absoluteDir().absolutePath(); - QProcess::startDetached(tape, arguments, workingDirectory); + VApplication::StartDetachedProcess(VApplication::TapeFilePath(), arguments); } else { @@ -3803,16 +3801,13 @@ void MainWindow::on_actionOpen_triggered() //--------------------------------------------------------------------------------------------------------------------- void MainWindow::on_actionOpenPuzzle_triggered() { - const QString puzzle = VApplication::PuzzleFilePath(); - const QString workingDirectory = QFileInfo(puzzle).absoluteDir().absolutePath(); - QStringList arguments; if (isNoScaling) { arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); } - QProcess::startDetached(puzzle, arguments, workingDirectory); + VApplication::StartDetachedProcess(VApplication::PuzzleFilePath(), arguments); } //--------------------------------------------------------------------------------------------------------------------- @@ -3863,9 +3858,7 @@ void MainWindow::on_actionCreateManualLayout_triggered() rldFile.setAutoRemove(false); - const QString puzzle = VApplication::PuzzleFilePath(); - const QString workingDirectory = QFileInfo(puzzle).absoluteDir().absolutePath(); - QProcess::startDetached(puzzle, arguments, workingDirectory); + VApplication::StartDetachedProcess(VApplication::PuzzleFilePath(), arguments); } else { @@ -3950,9 +3943,7 @@ void MainWindow::on_actionUpdateManualLayout_triggered() rldFile.setAutoRemove(false); - const QString puzzle = VApplication::PuzzleFilePath(); - const QString workingDirectory = QFileInfo(puzzle).absoluteDir().absolutePath(); - QProcess::startDetached(puzzle, arguments, workingDirectory); + VApplication::StartDetachedProcess(VApplication::PuzzleFilePath(), arguments); } else { @@ -4742,16 +4733,13 @@ void MainWindow::ActionShowMainPath_triggered(bool checked) //--------------------------------------------------------------------------------------------------------------------- void MainWindow::ActionOpenTape_triggered() { - const QString tape = VApplication::TapeFilePath(); - const QString workingDirectory = QFileInfo(tape).absoluteDir().absolutePath(); - QStringList arguments; if (isNoScaling) { arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); } - QProcess::startDetached(tape, arguments, workingDirectory); + VApplication::StartDetachedProcess(VApplication::TapeFilePath(), arguments); } //--------------------------------------------------------------------------------------------------------------------- @@ -5833,16 +5821,13 @@ auto MainWindow::LoadPattern(QString fileName, const QString& customMeasureFile) if (m.Type() == MeasurementsType::Multisize || m.Type() == MeasurementsType::Individual) { - const QString tape = VApplication::TapeFilePath(); - const QString workingDirectory = QFileInfo(tape).absoluteDir().absolutePath(); - - QStringList arguments = QStringList() << fileName; + QStringList arguments {fileName}; if (isNoScaling) { arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); } - QProcess::startDetached(tape, arguments, workingDirectory); + VApplication::StartDetachedProcess(VApplication::TapeFilePath(), arguments); QCoreApplication::exit(V_EX_OK); return false; // stop continue processing } @@ -5864,16 +5849,13 @@ auto MainWindow::LoadPattern(QString fileName, const QString& customMeasureFile) { // Here comes undocumented Valentina's feature. // Because app bundle in Mac OS X doesn't allow setup assosiation for Puzzle we must do this through Valentina - const QString puzzle = VApplication::PuzzleFilePath(); - const QString workingDirectory = QFileInfo(puzzle).absoluteDir().absolutePath(); - - QStringList arguments = QStringList() << fileName; + QStringList arguments {fileName}; if (isNoScaling) { arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); } - QProcess::startDetached(puzzle, arguments, workingDirectory); + VApplication::StartDetachedProcess(VApplication::PuzzleFilePath(), arguments); QCoreApplication::exit(V_EX_OK); return false; // stop continue processing } diff --git a/src/app/valentina/valentina.qbs b/src/app/valentina/valentina.qbs index 627a7c3af..949ea417e 100644 --- a/src/app/valentina/valentina.qbs +++ b/src/app/valentina/valentina.qbs @@ -13,6 +13,8 @@ VToolApp { Depends { name: "VFormatLib"; } Depends { name: "VMiscLib"; } + primaryApp: true + Depends { name: "Qt.winextras" condition: qbs.targetOS.contains("windows") diff --git a/src/libs/qmuparser/qmuparser.qbs b/src/libs/qmuparser/qmuparser.qbs index ee22fb7f9..18ae99468 100644 --- a/src/libs/qmuparser/qmuparser.qbs +++ b/src/libs/qmuparser/qmuparser.qbs @@ -1,4 +1,6 @@ VDynamicLib { + Depends { name: "multibundle"; } + name: "QMUParserLib" version: "2.7.0" files: [ @@ -49,4 +51,6 @@ VDynamicLib { qbs.install: true qbs.installDir: buildconfig.installAppPath } + + multibundle.targetApps: ["Valentina", "Tape", "Puzzle"] } diff --git a/src/libs/vpropertyexplorer/vpropertyexplorer.qbs b/src/libs/vpropertyexplorer/vpropertyexplorer.qbs index b1f2202a7..da96a56b5 100644 --- a/src/libs/vpropertyexplorer/vpropertyexplorer.qbs +++ b/src/libs/vpropertyexplorer/vpropertyexplorer.qbs @@ -1,6 +1,7 @@ VDynamicLib { Depends { name: "Qt"; submodules: ["gui", "widgets"] } Depends { name: "VMiscLib" } + Depends { name: "multibundle"; } name: "VPropertyExplorerLib" version: "1.0.0" @@ -92,4 +93,5 @@ VDynamicLib { } cpp.defines: "VPROPERTYEXPLORER_LIBRARY" + multibundle.targetApps: ["Valentina", "Tape", "Puzzle"] }