diff options
-rw-r--r-- | doc/reference/modules/java-module.qdoc | 24 | ||||
-rw-r--r-- | share/qbs/modules/java/JavaModule.qbs | 69 | ||||
-rw-r--r-- | share/qbs/modules/java/utils.js | 32 | ||||
-rw-r--r-- | src/lib/corelib/tools/hostosinfo.h | 9 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/java/vehicles.qbs | 13 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.cpp | 32 |
6 files changed, 176 insertions, 3 deletions
diff --git a/doc/reference/modules/java-module.qdoc b/doc/reference/modules/java-module.qdoc index e7d5e2cdc..dd1647b83 100644 --- a/doc/reference/modules/java-module.qdoc +++ b/doc/reference/modules/java-module.qdoc @@ -150,6 +150,30 @@ \li The version of the Java runtime to generate compatible bytecode for. If undefined, the compiler will use its default. \row + \li manifest + \li object + \li 1.4.2 + \li undefined + \li The properties to add to the manifest file when building a JAR. + The contents of this property will be aggregated with the values from \c{manifestFile}. + If \c{manifest} and \c{manifestFile} contain the same key, the former will take + precedence. If undefined, will not be taken into account. + \row + \li manifestFile + \li path + \li 1.4.2 + \li undefined + \li The manifest file to embed when building a JAR. + The contents of this file will be aggregated with the values in \c{manifest}. + If \c{manifestFile} and \c{manifest} contain the same key, the latter will take + precedence. If undefined, will not be taken into account. + \row + \li manifestClassPath + \li stringList + \li 1.4.2 + \li undefined + \li The entries to add to the manifest's Class-Path when building a JAR. + \row \li warningsAsErrors \li bool \li 1.4 diff --git a/share/qbs/modules/java/JavaModule.qbs b/share/qbs/modules/java/JavaModule.qbs index 70dd6477b..e39999c3c 100644 --- a/share/qbs/modules/java/JavaModule.qbs +++ b/share/qbs/modules/java/JavaModule.qbs @@ -33,6 +33,7 @@ import qbs.FileInfo import qbs.ModUtils import qbs.Probes import qbs.Process +import qbs.TextFile import "utils.js" as JavaUtils @@ -75,6 +76,30 @@ Module { description: "version of the Java runtime to generate compatible bytecode for" } + property var manifest: { + return { + "Manifest-Version": "1.0", + "Class-Path": manifestClassPath ? manifestClassPath.join(" ") : undefined + }; + } + + PropertyOptions { + name: "manifest" + description: "properties to add to the manifest file when building a JAR" + } + + property path manifestFile + PropertyOptions { + name: "manifestFile" + description: "manifest file to embed when building a JAR" + } + + property stringList manifestClassPath + PropertyOptions { + name: "manifestClassPath" + description: "entries to add to the manifest's Class-Path when building a JAR" + } + property bool warningsAsErrors: false property pathList jdkIncludePaths: { @@ -189,12 +214,50 @@ Module { } prepare: { - var i; + var i, key; var flags = "cf"; var args = [output.filePath]; - var manifestFile = ModUtils.moduleProperty(product, "manifest"); - if (manifestFile) { + var manifestFile = ModUtils.moduleProperty(product, "manifestFile"); + var manifest = ModUtils.moduleProperty(product, "manifest"); + var aggregateManifest = JavaUtils.manifestContents(manifestFile) || {}; + + // Add local key-value pairs (overrides equivalent keys specified in the file if + // one was given) + for (key in manifest) { + if (manifest.hasOwnProperty(key)) + aggregateManifest[key] = manifest[key]; + } + + for (key in aggregateManifest) { + if (aggregateManifest.hasOwnProperty(key) && aggregateManifest[key] === undefined) + delete aggregateManifest[key]; + } + + // Use default manifest unless we actually have properties to set + var needsManifestFile = manifestFile !== undefined || aggregateManifest !== {"Manifest-Version": "1.0"}; + + manifestFile = FileInfo.joinPaths(product.buildDirectory, "manifest.mf"); + + var mf; + try { + mf = new TextFile(manifestFile, TextFile.WriteOnly); + + // Ensure that manifest version comes first + mf.write("Manifest-Version: " + (aggregateManifest["Manifest-Version"] || "1.0") + "\n"); + delete aggregateManifest["Manifest-Version"]; + + for (key in aggregateManifest) + mf.write(key + ": " + aggregateManifest[key] + "\n"); + + mf.write("\n"); + } finally { + if (mf) { + mf.close(); + } + } + + if (needsManifestFile) { flags += "m"; args.push(manifestFile); } diff --git a/share/qbs/modules/java/utils.js b/share/qbs/modules/java/utils.js index c3ece67e2..7533e3dcc 100644 --- a/share/qbs/modules/java/utils.js +++ b/share/qbs/modules/java/utils.js @@ -237,6 +237,10 @@ function helperOverrideArgs(product, tool) { } function outputArtifacts(product, inputs) { + // Handle the case where a product depends on Java but has no Java sources + if (!inputs["java.java"] || inputs["java.java"].length === 0) + return []; + // We need to ensure that the output directory is created first, because the Java compiler // internally checks that it is present before performing any actions File.makePath(ModUtils.moduleProperty(product, "classFilesDir")); @@ -255,3 +259,31 @@ function outputArtifacts(product, inputs) { process.close(); } } + +function manifestContents(filePath) { + if (filePath === undefined) + return undefined; + + var contents, file; + try { + file = new TextFile(filePath); + contents = file.readAll(); + } finally { + if (file) { + file.close(); + } + } + + if (contents) { + var dict = {}; + var lines = contents.split(/[\r\n]/g); + for (var i in lines) { + var kv = lines[i].split(":"); + if (kv.length !== 2) + return undefined; + dict[kv[0]] = kv[1]; + } + + return dict; + } +} diff --git a/src/lib/corelib/tools/hostosinfo.h b/src/lib/corelib/tools/hostosinfo.h index 8e8d6ae1c..fc27853c4 100644 --- a/src/lib/corelib/tools/hostosinfo.h +++ b/src/lib/corelib/tools/hostosinfo.h @@ -88,6 +88,15 @@ public: return isWindowsHost() ? Qt::CaseInsensitive: Qt::CaseSensitive; } + static QString libraryPathEnvironmentVariable() + { + if (isWindowsHost()) + return QStringLiteral("PATH"); + if (isOsxHost()) + return QStringLiteral("DYLD_LIBRARY_PATH"); + return QStringLiteral("LD_LIBRARY_PATH"); + } + static QChar pathListSeparator() { return isWindowsHost() ? QLatin1Char(';') : QLatin1Char(':'); diff --git a/tests/auto/blackbox/testdata/java/vehicles.qbs b/tests/auto/blackbox/testdata/java/vehicles.qbs index 4c2e7a5ed..5ac235172 100644 --- a/tests/auto/blackbox/testdata/java/vehicles.qbs +++ b/tests/auto/blackbox/testdata/java/vehicles.qbs @@ -24,6 +24,11 @@ Project { "Car.java", "HelloWorld.java", "Jet.java", "NoPackage.java", "Ship.java", "Vehicle.java", "Vehicles.java" ] + + Export { + Depends { name: "java" } + java.manifestClassPath: [product.targetName + ".jar"] + } } JavaJarFile { @@ -34,6 +39,11 @@ Project { fileTagsFilter: ["java.jar"] qbs.install: true } + + Export { + Depends { name: "java" } + java.manifestClassPath: [product.targetName + ".jar"] + } } JavaJarFile { @@ -50,6 +60,9 @@ Project { Export { Depends { name: "cpp" } cpp.systemIncludePaths: product.cppIncludePaths + + Depends { name: "java" } + java.manifestClassPath: [product.targetName + ".jar"] } Group { diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index 9ffffff77..7bf59f487 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -1547,6 +1547,15 @@ void TestBlackbox::installTree() QVERIFY(QFile::exists(installRoot + "content/subdir2/baz.txt")); } +static QProcessEnvironment processEnvironmentWithCurrentDirectoryInLibraryPath() +{ + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(HostOsInfo::libraryPathEnvironmentVariable(), + QStringList({env.value(HostOsInfo::libraryPathEnvironmentVariable()), "."}) + .join(HostOsInfo::pathListSeparator())); + return env; +} + void TestBlackbox::java() { Settings settings((QString())); @@ -1584,6 +1593,29 @@ void TestBlackbox::java() foreach (const QString &classFile, classFiles1) { QVERIFY2(!regularFileExists(classFile), qPrintable(classFile)); } + + // This tests various things: java.manifestClassPath, JNI, etc. + QDir::setCurrent(relativeBuildDir() + "/install-root"); + QProcess process; + process.setProcessEnvironment(processEnvironmentWithCurrentDirectoryInLibraryPath()); + process.start("java", QStringList() << "-jar" << "jar_file.jar"); + QVERIFY2(process.waitForStarted(), qPrintable(process.errorString())); + QVERIFY2(process.waitForFinished(), qPrintable(process.errorString())); + QVERIFY2(process.exitCode() == 0, process.readAllStandardError().constData()); + QByteArray stdout = process.readAllStandardOutput(); + QVERIFY2(stdout.contains("Driving!"), stdout.constData()); + QVERIFY2(stdout.contains("Flying!"), stdout.constData()); + QVERIFY2(stdout.contains("Flying (this is a space ship)!"), stdout.constData()); + QVERIFY2(stdout.contains("Sailing!"), stdout.constData()); + QVERIFY2(stdout.contains("Native code performing complex internal combustion process (0x"), + stdout.constData()); + + process.start("unzip", QStringList() << "-p" << "jar_file.jar"); + QVERIFY2(process.waitForStarted(), qPrintable(process.errorString())); + QVERIFY2(process.waitForFinished(), qPrintable(process.errorString())); + stdout = process.readAllStandardOutput(); + QVERIFY2(stdout.contains("Class-Path: car_jar.jar random_stuff.jar"), stdout.constData()); + QVERIFY2(stdout.contains("Main-Class: Vehicles"), stdout.constData()); } void TestBlackbox::jsExtensionsFile() |