aboutsummaryrefslogtreecommitdiffstats
path: root/share/qbs/modules/cpp/bundle-tools.js
blob: 2de50308d55cf18b310880029bab0ba0dd264dbd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
// NOTE: QBS and Xcode's "target" and "product" names are reversed

function isBundleProduct(product)
{
    return product.type.contains("applicationbundle")
        || product.type.contains("frameworkbundle")
        || product.type.contains("bundle")
        || product.type.contains("inapppurchase");
}

// Returns the package creator code for the given product based on its type
function packageType(product)
{
    if (product.type.contains("application") || product.type.contains("applicationbundle"))
        return 'APPL';
    else if (product.type.contains("frameworkbundle"))
        return 'FMWK';
    else if (product.type.contains("bundle"))
        return 'BNDL';

    throw ("Unsupported product type " + product.type + ". "
         + "Must be in {application, applicationbundle, frameworkbundle, bundle}.");
}

function infoPlistContents(infoPlistFilePath)
{
    if (infoPlistFilePath === undefined)
        return undefined;

    var process = new Process();
    try {
        process.start("plutil", ["-convert", "json", "-o", "-", infoPlistFilePath]);
        process.waitForFinished();
        if (process.exitCode() !== 0)
            throw("plutil: " + (process.readStdErr().trim() || process.readStdOut().trim()));

        return JSON.parse(process.readStdOut());
    } finally {
        process.close();
    }
}

function infoPlistFormat(infoPlistFilePath)
{
    if (infoPlistFilePath === undefined)
        return undefined;

    // Verify that the Info.plist format is actually valid in the first place
    var process = new Process();
    try {
        process.start("plutil", ["-lint", infoPlistFilePath]);
        process.waitForFinished();
        if (process.exitCode() !== 0)
            throw("plutil: " + (process.readStdErr().trim() || process.readStdOut().trim()));
    } finally {
        process.close();
    }

    process = new Process();
    process.start("file", ["-bI", infoPlistFilePath]);
    process.waitForFinished();
    var magic = process.readStdOut().trim();
    process.close();

    if (magic.indexOf("application/octet-stream;") === 0)
        return "binary1";
    else if (magic.indexOf("application/xml;") === 0)
        return "xml1";
    else if (magic.indexOf("text/plain;") === 0)
        return "json";

    return undefined;
}

// CONTENTS_FOLDER_PATH
// Main bundle directory
// the version parameter is only used for framework bundles
function contentsFolderPath(product, version)
{
    var path = wrapperName(product);

    if (product.type.contains("frameworkbundle"))
        path += "/Versions/" + (version || frameworkVersion(product));
    else if (!isShallowBundle(product))
        path += "/Contents";

    return path;
}

// DOCUMENTATION_FOLDER_PATH
// Directory for documentation files
// the version parameter is only used for framework bundles
function documentationFolderPath(product, localizationName, version)
{
    var path = localizedResourcesFolderPath(product, localizationName, version);
    if (!product.type.contains("inapppurchase"))
        path += "/Documentation";
    return path;
}

// EXECUTABLES_FOLDER_PATH
// Destination directory for auxiliary executables
// the version parameter is only used for framework bundles
function executablesFolderPath(product, localizationName, version)
{
    if (product.type.contains("frameworkbundle"))
        return localizedResourcesFolderPath(product, localizationName, version);
    else
        return _contentsFolderSubDirPath(product, "Executables", version);
}

// EXECUTABLE_FOLDER_PATH
// Destination directory for the primary executable
// the version parameter is only used for framework bundles
function executableFolderPath(product, version)
{
    var path = contentsFolderPath(product, version);
    if (!isShallowBundle(product)
        && !product.type.contains("frameworkbundle")
        && !product.type.contains("inapppurchase"))
        path += "/MacOS";

    return path;
}

// EXECUTABLE_PATH
// Path to the bundle's primary executable file
// the version parameter is only used for framework bundles
function executablePath(product, version)
{
    return executableFolderPath(product, version) + "/" + productName(product);
}

// FRAMEWORK_VERSION
// Major version number or letter corresponding to the bundle version
function frameworkVersion(product)
{
    if (!product.type.contains("frameworkbundle"))
        throw "Product type must be a frameworkbundle, was " + product.type;

    var n = parseInt(product.version, 10);
    return isNaN(n) ? 'A' : n;
}

// FRAMEWORKS_FOLDER_PATH
// Directory containing frameworks used by the bundle's executables
// the version parameter is only used for framework bundles
function frameworksFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "Frameworks", version);
}

// INFOPLIST_PATH
// Path to the bundle's main information property list
// the version parameter is only used for framework bundles
function infoPlistPath(product, version)
{
    var path;
    if (product.type.contains("application"))
        path = ".tmp/" + product.name;
    else if (product.type.contains("frameworkbundle"))
        path = unlocalizedResourcesFolderPath(product, version);
    else if (product.type.contains("inapppurchase"))
        path = wrapperName(product);
    else
        path = contentsFolderPath(product, version);

    return path + "/" + _infoFileNames(product)[0];
}

// INFOSTRINGS_PATH
// Path to the strings file corresponding to the bundle's main information property list
// the version parameter is only used for framework bundles
function infoStringsPath(product, localizationName, version)
{
    return localizedResourcesFolderPath(product, localizationName, version) + "/" + _infoFileNames(product)[1];
}

// LOCALIZED_RESOURCES_FOLDER_PATH
// Path to the bundle's resources directory for the given localization
// the version parameter is only used for framework bundles
function localizedResourcesFolderPath(product, localizationName, version)
{
    if (typeof localizationName !== "string")
        throw("'" + localizationName + "' is not a valid localization name");

    return unlocalizedResourcesFolderPath(product, version) + "/" + localizationName + ".lproj";
}

// PKGINFO_PATH
// Path to the bundle's PkgInfo file
function pkgInfoPath(product)
{
    var path = (product.type.contains("frameworkbundle"))
        ? wrapperName(product)
        : contentsFolderPath(product);
    return path + "/PkgInfo";
}

// PLUGINS_FOLDER_PATH
// Directory containing plugins used by the bundle's executables
// the version parameter is only used for framework bundles
function pluginsFolderPath(product, version)
{
    if (product.type.contains("frameworkbundle"))
        return unlocalizedResourcesFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "PlugIns", version);
}

// PRIVATE_HEADERS_FOLDER_PATH
// Directory containing private header files for the framework
// the version parameter is only used for framework bundles
function privateHeadersFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "PrivateHeaders", version);
}

// PRODUCT_NAME
// The name of the product (in Xcode terms) which corresponds to the target name in QBS terms
function productName(product)
{
    return product.targetName;
}

// PUBLIC_HEADERS_FOLDER_PATH
// Directory containing public header files for the framework
// the version parameter is only used for framework bundles
function publicHeadersFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "Headers", version);
}

// SCRIPTS_FOLDER_PATH
// Directory containing script files associated with the bundle
// the version parameter is only used for framework bundles
function scriptsFolderPath(product, version)
{
    return unlocalizedResourcesFolderPath(product, version) + "/Scripts";
}

// SHALLOW_BUNDLE
// Controls the presence or absence of the Contents, MacOS and Resources folders
// iOS tends to store the majority of files in its bundles in the main directory
function isShallowBundle(product)
{
    return product.moduleProperty("qbs", "targetOS").contains("ios")
        && product.type.contains("applicationbundle");
}

// SHARED_FRAMEWORKS_FOLDER_PATH
// Directory containing sub-frameworks that may be shared with other applications
// the version parameter is only used for framework bundles
function sharedFrameworksFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "SharedFrameworks", version);
}

// SHARED_SUPPORT_FOLDER_PATH
// Directory containing supporting files that may be shared with other applications
// the version parameter is only used for framework bundles
function sharedSupportFolderPath(product, version)
{
    if (product.type.contains("frameworkbundle"))
        return unlocalizedResourcesFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "SharedSupport", version);
}

// UNLOCALIZED_RESOURCES_FOLDER_PATH
// Directory containing resource files that are not specific to any given localization
function unlocalizedResourcesFolderPath(product, version)
{
    if (isShallowBundle(product))
        return contentsFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "Resources", version);
}

// VERSIONPLIST_PATH
// Directory containing the bundle's version.plist file
// the version parameter is only used for framework bundles
function versionPlistPath(product, version)
{
    var path = (product.type.contains("frameworkbundle"))
        ? unlocalizedResourcesFolderPath(product, version)
        : contentsFolderPath(product, version);
    return path + "/version.plist";
}

// WRAPPER_EXTENSION
// The file extension of the bundle directory - app, framework, bundle, etc.
function wrapperExtension(product)
{
    if (product.type.contains("applicationbundle")) {
        return "app";
    } else if (product.type.contains("frameworkbundle")) {
        return "framework";
    } else if (product.type.contains("inapppurchase")) {
        return "";
    } else if (product.type.contains("bundle")) {
        // Potentially: kext, prefPane, qlgenerator, saver, mdimporter, or a custom extension
        var bundleExtension = ModUtils.moduleProperty(product, "bundleExtension");

        // default to bundle if none was specified by the user
        return bundleExtension || "bundle";
    } else {
        throw ("Unsupported bundle product type " + product.type + ". "
             + "Must be in {applicationbundle, frameworkbundle, bundle, inapppurchase}.");
    }
}

// WRAPPER_NAME
// The name of the bundle directory - the product name plus the bundle extension
function wrapperName(product)
{
    return productName(product) + wrapperSuffix(product);
}

// WRAPPER_SUFFIX
// The suffix of the bundle directory, that is, its extension prefixed with a '.',
// or an empty string if the extension is also an empty string
function wrapperSuffix(product)
{
    var ext = wrapperExtension(product);
    return ext ? ("." + ext) : "";
}

// Private helper functions

// In-App purchase content bundles use virtually no subfolders of Contents;
// this is a convenience method to avoid repeating that logic everywhere
// the version parameter is only used for framework bundles
function _contentsFolderSubDirPath(product, subdirectoryName, version)
{
    var path = contentsFolderPath(product, version);
    if (!product.type.contains("inapppurchase"))
        path += "/" + subdirectoryName;
    return path;
}

// Returns a list containing the filename of the bundle's main information
// property list and filename of the corresponding strings file
function _infoFileNames(product)
{
    if (product.type.contains("inapppurchase"))
        return ["ContentInfo.plist", "ContentInfo.strings"];
    else
        return ["Info.plist", "InfoPlist.strings"];
}