aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJake Petroules <jake.petroules@petroules.com>2014-07-11 16:45:34 -0400
committerJoerg Bornemann <joerg.bornemann@digia.com>2014-07-22 14:41:07 +0200
commit496707ce5ae88e124617553e8e24d887a750777c (patch)
tree9b4c9b43d6d7e226e9aeb77e873c81edf285af41
parent8c0a975afacc10f0f2c9defffc95b8ae22e8da63 (diff)
Add support for compiling Xcode asset catalogs.
Task-number: QBS-389 Change-Id: I2d93a55228c6ada801ed889404fac753182b82f6 Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
-rw-r--r--share/qbs/imports/qbs/DarwinTools/darwin-tools.js14
-rw-r--r--share/qbs/modules/cpp/DarwinGCC.qbs20
-rw-r--r--share/qbs/modules/ib/IBModule.qbs74
-rw-r--r--share/qbs/modules/ib/ib.js29
-rw-r--r--tests/auto/blackbox/testdata/ib/assetcatalog/assetcatalogempty.qbs15
-rw-r--r--tests/auto/blackbox/testdata/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16.pngbin0 -> 3303 bytes
-rw-r--r--tests/auto/blackbox/testdata/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16@2x.pngbin0 -> 3336 bytes
-rw-r--r--tests/auto/blackbox/testdata/ib/assetcatalog/main.c4
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp37
-rw-r--r--tests/auto/blackbox/tst_blackbox.h1
10 files changed, 190 insertions, 4 deletions
diff --git a/share/qbs/imports/qbs/DarwinTools/darwin-tools.js b/share/qbs/imports/qbs/DarwinTools/darwin-tools.js
index 3c30aba50..56a727399 100644
--- a/share/qbs/imports/qbs/DarwinTools/darwin-tools.js
+++ b/share/qbs/imports/qbs/DarwinTools/darwin-tools.js
@@ -1,6 +1,20 @@
var FileInfo = loadExtension("qbs.FileInfo");
/**
+ * Returns the most appropriate Apple platform name given a targetOS list.
+ * Possible platform names include macosx, iphoneos, and iphonesimulator.
+ */
+function applePlatformName(targetOSList) {
+ if (targetOSList.contains("ios-simulator"))
+ return "iphonesimulator";
+ else if (targetOSList.contains("ios"))
+ return "iphoneos";
+ else if (targetOSList.contains("osx"))
+ return "macosx";
+ throw("No Apple platform corresponds to target OS list: " + targetOSList);
+}
+
+/**
* Replace characters unsafe for use in a domain name with a '-' character (RFC 1034).
*/
function rfc1034(inStr) {
diff --git a/share/qbs/modules/cpp/DarwinGCC.qbs b/share/qbs/modules/cpp/DarwinGCC.qbs
index 1081d3dc5..0de1041ce 100644
--- a/share/qbs/modules/cpp/DarwinGCC.qbs
+++ b/share/qbs/modules/cpp/DarwinGCC.qbs
@@ -152,7 +152,7 @@ UnixGCC {
Rule {
multiplex: true
- inputs: ["qbs"]
+ inputs: ["qbs", "partial_infoplist"]
Artifact {
filePath: product.destinationDirectory + "/" + BundleTools.infoPlistPath(product)
@@ -163,6 +163,7 @@ UnixGCC {
var cmd = new JavaScriptCommand();
cmd.description = "generating Info.plist for " + product.name;
cmd.highlight = "codegen";
+ cmd.partialInfoPlistFiles = inputs.partial_infoplist;
cmd.infoPlistFile = ModUtils.moduleProperty(product, "infoPlistFile");
cmd.infoPlist = ModUtils.moduleProperty(product, "infoPlist") || {};
cmd.processInfoPlist = ModUtils.moduleProperty(product, "processInfoPlist");
@@ -287,6 +288,15 @@ UnixGCC {
env[key] = qmakeEnv[key];
DarwinTools.expandPlistEnvironmentVariables(aggregatePlist, env, true);
+
+ // Add keys from partial Info.plists from asset catalogs, XIBs, and storyboards
+ for (i in partialInfoPlistFiles) {
+ var partialInfoPlist = BundleTools.infoPlistContents(partialInfoPlistFiles[i].filePath) || {};
+ for (key in partialInfoPlist) {
+ if (partialInfoPlist.hasOwnProperty(key))
+ aggregatePlist[key] = partialInfoPlist[key];
+ }
+ }
}
if (infoPlistFormat === "same-as-input" && infoPlistFile)
@@ -332,8 +342,9 @@ UnixGCC {
Rule {
multiplex: true
- inputs: ["application", "infoplist", "pkginfo", "icns", "application_dsym", "compiled_nib",
- "resourcerules", "ipa"]
+ inputs: ["application", "infoplist", "pkginfo", "icns", "application_dsym",
+ "compiled_nib", "compiled_assetcatalog",
+ "resourcerules", "ipa"]
Artifact {
filePath: product.destinationDirectory + "/" + BundleTools.wrapperName(product)
@@ -351,7 +362,8 @@ UnixGCC {
Rule {
multiplex: true
- inputs: ["dynamiclibrary", "infoplist", "pkginfo", "icns", "dynamiclibrary_dsym", "compiled_nib"]
+ inputs: ["dynamiclibrary", "infoplist", "pkginfo", "icns", "dynamiclibrary_dsym",
+ "compiled_nib", "compiled_assetcatalog"]
Artifact {
filePath: product.destinationDirectory + "/" + BundleTools.wrapperName(product)
diff --git a/share/qbs/modules/ib/IBModule.qbs b/share/qbs/modules/ib/IBModule.qbs
index bee1dd9e4..ebcf8fc2e 100644
--- a/share/qbs/modules/ib/IBModule.qbs
+++ b/share/qbs/modules/ib/IBModule.qbs
@@ -6,6 +6,8 @@ import qbs.ModUtils
import 'ib.js' as Ib
Module {
+ Depends { name: "cpp" } // to put toolchainInstallPath in the PATH for actool
+
condition: qbs.hostOS.contains("darwin") && qbs.targetOS.contains("darwin")
property bool warnings: true
@@ -23,9 +25,17 @@ Module {
property string ibtoolPath: ibtoolName
property bool flatten: true
+ // Asset catalog specific
+ property string actoolName: "actool"
+ property string actoolPath: actoolName
+ property string appIconName
+ property string launchImageName
+ property bool compressPngs: true
+
// private properties
property string outputFormat: "human-readable-text"
property string appleIconSuffix: ".icns"
+ property string compiledAssetCatalogSuffix: ".car"
property string compiledNibSuffix: ".nib"
property string ibtoolVersion: { return Ib.ibtoolVersion(ibtoolPath); }
@@ -57,6 +67,11 @@ Module {
fileTags: ["nib"]
}
+ FileTagger {
+ patterns: ["*.xcassets"] // bundle
+ fileTags: ["assetcatalog"]
+ }
+
Rule {
inputs: ["iconset"]
@@ -126,4 +141,63 @@ Module {
return cmd;
}
}
+
+ Rule {
+ inputs: ["assetcatalog"]
+
+ // We only return one artifact, as this is a little complicated...
+ // actool takes an output *directory*, and in this directory it will
+ // potentially output "Assets.car" and/or one or more additional files.
+ // We can discover which files were written in an easily parseable manner
+ // through use of --output-format xml1, but we have a chicken and egg problem
+ // in that we only gain that information *after* running the compilation, so
+ // if we want to know in advance which artifacts are generated we have to run
+ // the compilation twice which probably isn't worth it.
+ outputArtifacts: {
+ var outputDirectory = BundleTools.isBundleProduct(product)
+ ? BundleTools.unlocalizedResourcesFolderPath(product)
+ : product.destinationDirectory;
+ return [{
+ filePath: FileInfo.joinPaths(outputDirectory, "Assets" + ModUtils.moduleProperty(product, "compiledAssetCatalogSuffix")),
+ fileTags: ["compiled_assetcatalog"]
+ },
+ {
+ filePath: FileInfo.joinPaths(product.destinationDirectory, "assetcatalog_generated_info.plist"),
+ fileTags: ["partial_infoplist"]
+ }];
+ }
+
+ outputFileTags: ["compiled_assetcatalog", "partial_infoplist"]
+
+ // Just a note, the man page for actool is somewhat outdated (probably forgotten to be updated late in the development cycle).
+ // It mentions the file extension .assetcatalog (which isn't used by Xcode), the --write option does not exist, and the example
+ // invocation near the bottom of the man page doesn't work at all.
+ // There's also the undocumented --export-dependency-info <output.txt> which is used by Xcode and generated a \0x00\0x02-delimited
+ // file (yes, really) that contains the output file names, identical to the output of actool itself (what's the point?).
+ prepare: {
+ var args = Ib.prepareIbtoold(product, input, outputs);
+
+ var flags = ModUtils.moduleProperty(input, "flags");
+ if (flags)
+ args = args.concat(flags);
+
+ var outputPath = FileInfo.path(outputs.compiled_assetcatalog[0].filePath);
+
+ args.push("--compile");
+ args.push(outputPath);
+ args.push(input.filePath);
+
+ var cmd = new Command(ModUtils.moduleProperty(input, "actoolPath"), args);
+ cmd.description = ModUtils.moduleProperty(input, "actoolName") + ' ' + input.fileName;
+ cmd.highlight = "compiler";
+ cmd.stdoutFilterFunction = function(stdout) {
+ stdout = stdout.replace("/* com.apple.actool.compilation-results */\n", "");
+ return stdout.split("\n").filter(function(line) {
+ return line.length > 0 /*&& line.indexOf(outputPath) !== 0*/;
+ }).join("\n");
+ }
+
+ return cmd;
+ }
+ }
}
diff --git a/share/qbs/modules/ib/ib.js b/share/qbs/modules/ib/ib.js
index ac5d14462..3254cdb06 100644
--- a/share/qbs/modules/ib/ib.js
+++ b/share/qbs/modules/ib/ib.js
@@ -1,3 +1,4 @@
+var DarwinTools = loadExtension("qbs.DarwinTools");
var ModUtils = loadExtension("qbs.ModUtils");
var Process = loadExtension("qbs.Process");
var PropertyList = loadExtension("qbs.PropertyList");
@@ -22,6 +23,19 @@ function prepareIbtoold(product, input, outputs) {
args.push("--notices");
if (input.fileTags.contains("assetcatalog")) {
+ args.push("--platform", DarwinTools.applePlatformName(product.moduleProperty("qbs", "targetOS")));
+
+ var appIconName = ModUtils.moduleProperty(input, "appIcon");
+ if (appIconName)
+ args.push("--app-icon", appIconName);
+
+ var launchImageName = ModUtils.moduleProperty(input, "launchImage");
+ if (launchImageName)
+ args.push("--launch-image", launchImageName);
+
+ // Undocumented but used by Xcode (only for iOS?), probably runs pngcrush or equivalent
+ if (ModUtils.moduleProperty(input, "compressPngs"))
+ args.push("--compress-pngs");
} else {
var sysroot = product.moduleProperty("qbs", "sysroot");
if (sysroot)
@@ -43,6 +57,21 @@ function prepareIbtoold(product, input, outputs) {
}
}
+ // --target-device and -output-partial-info-plist were introduced in Xcode 6.0 for ibtool
+ if (ModUtils.moduleProperty(input, "ibtoolVersionMajor") >= 6 || input.fileTags.contains("assetcatalog")) {
+ args.push("--output-partial-info-plist", outputs.partial_infoplist[0].filePath);
+
+ if (product.moduleProperty("qbs", "targetOS").contains("osx"))
+ args.push("--target-device", "mac");
+
+ if (product.moduleProperty("qbs", "targetOS").contains("ios")) {
+ // TODO: Only output the devices specified in TARGET_DEVICE_FAMILY
+ // We can't get this info from Info.plist keys due to dependency order
+ args.push("--target-device", "iphone");
+ args.push("--target-device", "ipad");
+ }
+ }
+
return args;
}
diff --git a/tests/auto/blackbox/testdata/ib/assetcatalog/assetcatalogempty.qbs b/tests/auto/blackbox/testdata/ib/assetcatalog/assetcatalogempty.qbs
new file mode 100644
index 000000000..bdd45fede
--- /dev/null
+++ b/tests/auto/blackbox/testdata/ib/assetcatalog/assetcatalogempty.qbs
@@ -0,0 +1,15 @@
+import qbs
+
+Project {
+ property bool includeIconset
+
+ CppApplication {
+ Depends { name: "ib" }
+ files: {
+ var filez = ["main.c", "empty.xcassets"];
+ if (project.includeIconset)
+ filez.push("empty.xcassets/empty.iconset");
+ return filez;
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16.png b/tests/auto/blackbox/testdata/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16.png
new file mode 100644
index 000000000..65ff1ff57
--- /dev/null
+++ b/tests/auto/blackbox/testdata/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16.png
Binary files differ
diff --git a/tests/auto/blackbox/testdata/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16@2x.png b/tests/auto/blackbox/testdata/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16@2x.png
new file mode 100644
index 000000000..a5c69bccd
--- /dev/null
+++ b/tests/auto/blackbox/testdata/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16@2x.png
Binary files differ
diff --git a/tests/auto/blackbox/testdata/ib/assetcatalog/main.c b/tests/auto/blackbox/testdata/ib/assetcatalog/main.c
new file mode 100644
index 000000000..905869dfa
--- /dev/null
+++ b/tests/auto/blackbox/testdata/ib/assetcatalog/main.c
@@ -0,0 +1,4 @@
+int main()
+{
+ return 0;
+}
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index c791a6993..8e0edef9e 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -2295,6 +2295,43 @@ void TestBlackbox::testIconsetApp()
QVERIFY(regularFileExists(productBuildDir("iconsetapp") + "/iconsetapp.app/Contents/Resources/white.icns"));
}
+void TestBlackbox::testAssetCatalog()
+{
+ if (!HostOsInfo::isOsxHost())
+ SKIP_TEST("only applies on OS X");
+
+ QDir::setCurrent(testDataDir + QLatin1String("/ib/assetcatalog"));
+
+ QbsRunParameters params;
+ params.arguments = QStringList() << "-f" << "assetcatalogempty.qbs";
+ QCOMPARE(runQbs(params), 0);
+
+ // empty asset catalogs must still produce output
+ QVERIFY((bool)m_qbsStdout.contains("actool"));
+
+ // should not produce a CAR since minimumOsxVersion will be < 10.9
+ QVERIFY(!regularFileExists(productBuildDir("assetcatalogempty") + "/assetcatalogempty.app/Contents/Resources/Assets.car"));
+
+ rmDirR(buildDir);
+ params.arguments.append("cpp.minimumOsxVersion:10.9"); // force CAR generation
+ QCOMPARE(runQbs(params), 0);
+
+ // empty asset catalogs must still produce output
+ QVERIFY((bool)m_qbsStdout.contains("actool"));
+ QVERIFY(regularFileExists(productBuildDir("assetcatalogempty") + "/assetcatalogempty.app/Contents/Resources/Assets.car"));
+
+ // this asset catalog happens to have an embedded icon set,
+ // but this should NOT be built since it is not in the files list
+ QVERIFY(!(bool)m_qbsStdout.contains("iconutil"));
+
+ // now we'll add the iconset
+ rmDirR(buildDir);
+ params.arguments.append("project.includeIconset:true");
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY((bool)m_qbsStdout.contains("actool"));
+ QVERIFY((bool)m_qbsStdout.contains("iconutil"));
+}
+
QString TestBlackbox::uniqueProductName(const QString &productName) const
{
return productName + '.' + buildProfileName;
diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h
index 0ac522d76..320039713 100644
--- a/tests/auto/blackbox/tst_blackbox.h
+++ b/tests/auto/blackbox/tst_blackbox.h
@@ -187,6 +187,7 @@ private slots:
void testTypeScript();
void testIconset();
void testIconsetApp();
+ void testAssetCatalog();
private:
QString uniqueProductName(const QString &productName) const;