aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/reference/modules/java-module.qdoc24
-rw-r--r--share/qbs/modules/java/JavaModule.qbs69
-rw-r--r--share/qbs/modules/java/utils.js32
-rw-r--r--src/lib/corelib/tools/hostosinfo.h9
-rw-r--r--tests/auto/blackbox/testdata/java/vehicles.qbs13
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp32
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()