blob: 17a658ee27cf47c151eb1c8a8f2517735f648b68 [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "ConfigDescription.h"
18#include "Diagnostics.h"
19#include "Flags.h"
20#include "ResourceParser.h"
21#include "ResourceTable.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070022#include "compile/IdAssigner.h"
23#include "compile/Png.h"
24#include "compile/XmlIdCollector.h"
25#include "flatten/FileExportWriter.h"
26#include "flatten/TableFlattener.h"
27#include "flatten/XmlFlattener.h"
28#include "util/Files.h"
29#include "util/Maybe.h"
30#include "util/Util.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080031#include "xml/XmlDom.h"
32#include "xml/XmlPullParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070033
34#include <fstream>
35#include <string>
36
37namespace aapt {
38
39struct ResourcePathData {
40 Source source;
41 std::u16string resourceDir;
42 std::u16string name;
43 std::string extension;
44
45 // Original config str. We keep this because when we parse the config, we may add on
46 // version qualifiers. We want to preserve the original input so the output is easily
47 // computed before hand.
48 std::string configStr;
49 ConfigDescription config;
50};
51
52/**
53 * Resource file paths are expected to look like:
54 * [--/res/]type[-config]/name
55 */
56static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
57 std::string* outError) {
58 std::vector<std::string> parts = util::split(path, file::sDirSep);
59 if (parts.size() < 2) {
60 if (outError) *outError = "bad resource path";
61 return {};
62 }
63
64 std::string& dir = parts[parts.size() - 2];
65 StringPiece dirStr = dir;
66
67 StringPiece configStr;
68 ConfigDescription config;
69 size_t dashPos = dir.find('-');
70 if (dashPos != std::string::npos) {
71 configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
72 if (!ConfigDescription::parse(configStr, &config)) {
73 if (outError) {
74 std::stringstream errStr;
75 errStr << "invalid configuration '" << configStr << "'";
76 *outError = errStr.str();
77 }
78 return {};
79 }
80 dirStr = dirStr.substr(0, dashPos);
81 }
82
83 std::string& filename = parts[parts.size() - 1];
84 StringPiece name = filename;
85 StringPiece extension;
86 size_t dotPos = filename.find('.');
87 if (dotPos != std::string::npos) {
88 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
89 name = name.substr(0, dotPos);
90 }
91
92 return ResourcePathData{
93 Source{ path },
94 util::utf8ToUtf16(dirStr),
95 util::utf8ToUtf16(name),
96 extension.toString(),
97 configStr.toString(),
98 config
99 };
100}
101
102struct CompileOptions {
103 std::string outputPath;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700104 Maybe<std::u16string> product;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700105 bool verbose = false;
106};
107
108static std::string buildIntermediateFilename(const std::string outDir,
109 const ResourcePathData& data) {
110 std::stringstream name;
111 name << data.resourceDir;
112 if (!data.configStr.empty()) {
113 name << "-" << data.configStr;
114 }
115 name << "_" << data.name << "." << data.extension << ".flat";
116 std::string outPath = outDir;
117 file::appendPath(&outPath, name.str());
118 return outPath;
119}
120
121static bool compileTable(IAaptContext* context, const CompileOptions& options,
122 const ResourcePathData& pathData, const std::string& outputPath) {
123 ResourceTable table;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700124 {
125 std::ifstream fin(pathData.source.path, std::ifstream::binary);
126 if (!fin) {
127 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
128 return false;
129 }
130
131
132 // Parse the values file from XML.
Adam Lesinski467f1712015-11-16 17:35:44 -0800133 xml::XmlPullParser xmlParser(fin);
Adam Lesinski9f222042015-11-04 13:51:45 -0800134
135 ResourceParserOptions parserOptions;
136 parserOptions.product = options.product;
137
138 // If the filename includes donottranslate, then the default translatable is false.
139 parserOptions.translatable = pathData.name.find(u"donottranslate") == std::string::npos;
140
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700141 ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
Adam Lesinski9f222042015-11-04 13:51:45 -0800142 pathData.config, parserOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700143 if (!resParser.parse(&xmlParser)) {
144 return false;
145 }
146
147 fin.close();
148 }
149
Adam Lesinski83f22552015-11-07 11:51:23 -0800150 // Ensure we have the compilation package at least.
151 table.createPackage(context->getCompilationPackage());
152
153 for (auto& pkg : table.packages) {
154 if (!pkg->id) {
155 // If no package ID was set while parsing (public identifiers), auto assign an ID.
156 pkg->id = context->getPackageId();
157 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700158 }
159
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700160 // Assign IDs to prepare the table for flattening.
161 IdAssigner idAssigner;
162 if (!idAssigner.consume(context, &table)) {
163 return false;
164 }
165
166 // Flatten the table.
167 BigBuffer buffer(1024);
168 TableFlattenerOptions tableFlattenerOptions;
169 tableFlattenerOptions.useExtendedChunks = true;
170 TableFlattener flattener(&buffer, tableFlattenerOptions);
171 if (!flattener.consume(context, &table)) {
172 return false;
173 }
174
175 // Build the output filename.
176 std::ofstream fout(outputPath, std::ofstream::binary);
177 if (!fout) {
178 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
179 return false;
180 }
181
182 // Write it to disk.
183 if (!util::writeAll(fout, buffer)) {
184 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
185 return false;
186 }
187 return true;
188}
189
190static bool compileXml(IAaptContext* context, const CompileOptions& options,
191 const ResourcePathData& pathData, const std::string& outputPath) {
192
Adam Lesinski467f1712015-11-16 17:35:44 -0800193 std::unique_ptr<xml::XmlResource> xmlRes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700194
195 {
196 std::ifstream fin(pathData.source.path, std::ifstream::binary);
197 if (!fin) {
198 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
199 return false;
200 }
201
202 xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
203
204 fin.close();
205 }
206
207 if (!xmlRes) {
208 return false;
209 }
210
211 // Collect IDs that are defined here.
212 XmlIdCollector collector;
213 if (!collector.consume(context, xmlRes.get())) {
214 return false;
215 }
216
217 xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
218 xmlRes->file.config = pathData.config;
219 xmlRes->file.source = pathData.source;
220
221 BigBuffer buffer(1024);
222 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
223
224 XmlFlattenerOptions xmlFlattenerOptions;
225 xmlFlattenerOptions.keepRawValues = true;
226 XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
227 if (!flattener.consume(context, xmlRes.get())) {
228 return false;
229 }
230
231 fileExportWriter.finish();
232
233 std::ofstream fout(outputPath, std::ofstream::binary);
234 if (!fout) {
235 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
236 return false;
237 }
238
239 // Write it to disk.
240 if (!util::writeAll(fout, buffer)) {
241 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
242 return false;
243 }
244 return true;
245}
246
247static bool compilePng(IAaptContext* context, const CompileOptions& options,
248 const ResourcePathData& pathData, const std::string& outputPath) {
249 BigBuffer buffer(4096);
250 ResourceFile resFile;
251 resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
252 resFile.config = pathData.config;
253 resFile.source = pathData.source;
254
255 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
256
257 {
258 std::ifstream fin(pathData.source.path, std::ifstream::binary);
259 if (!fin) {
260 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
261 return false;
262 }
263
264 Png png(context->getDiagnostics());
265 if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
266 return false;
267 }
268 }
269
270 fileExportWriter.finish();
271
272 std::ofstream fout(outputPath, std::ofstream::binary);
273 if (!fout) {
274 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
275 return false;
276 }
277
278 if (!util::writeAll(fout, buffer)) {
279 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
280 return false;
281 }
282 return true;
283}
284
285static bool compileFile(IAaptContext* context, const CompileOptions& options,
286 const ResourcePathData& pathData, const std::string& outputPath) {
287 BigBuffer buffer(256);
288 ResourceFile resFile;
289 resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
290 resFile.config = pathData.config;
291 resFile.source = pathData.source;
292
293 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
294
295 std::string errorStr;
296 Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
297 if (!f) {
298 context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
299 return false;
300 }
301
302 std::ofstream fout(outputPath, std::ofstream::binary);
303 if (!fout) {
304 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
305 return false;
306 }
307
308 // Manually set the size and don't call finish(). This is because we are not copying from
309 // the buffer the entire file.
310 fileExportWriter.getChunkHeader()->size =
311 util::hostToDevice32(buffer.size() + f.value().getDataLength());
312 if (!util::writeAll(fout, buffer)) {
313 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
314 return false;
315 }
316
317 if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
318 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
319 return false;
320 }
321 return true;
322}
323
324class CompileContext : public IAaptContext {
325private:
326 StdErrDiagnostics mDiagnostics;
327
328public:
329 IDiagnostics* getDiagnostics() override {
330 return &mDiagnostics;
331 }
332
333 NameMangler* getNameMangler() override {
334 abort();
335 return nullptr;
336 }
337
338 StringPiece16 getCompilationPackage() override {
339 return {};
340 }
341
342 uint8_t getPackageId() override {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700343 return 0x0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700344 }
345
346 ISymbolTable* getExternalSymbols() override {
347 abort();
348 return nullptr;
349 }
350};
351
352/**
353 * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
354 */
355int compile(const std::vector<StringPiece>& args) {
356 CompileOptions options;
357
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700358 Maybe<std::string> product;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700359 Flags flags = Flags()
360 .requiredFlag("-o", "Output path", &options.outputPath)
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700361 .optionalFlag("--product", "Product type to compile", &product)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700362 .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
363 if (!flags.parse("aapt2 compile", args, &std::cerr)) {
364 return 1;
365 }
366
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700367 if (product) {
368 options.product = util::utf8ToUtf16(product.value());
369 }
370
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700371 CompileContext context;
372
373 std::vector<ResourcePathData> inputData;
374 inputData.reserve(flags.getArgs().size());
375
376 // Collect data from the path for each input file.
377 for (const std::string& arg : flags.getArgs()) {
378 std::string errorStr;
379 if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
380 inputData.push_back(std::move(pathData.value()));
381 } else {
382 context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
383 return 1;
384 }
385 }
386
387 bool error = false;
388 for (ResourcePathData& pathData : inputData) {
389 if (options.verbose) {
390 context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
391 }
392
393 if (pathData.resourceDir == u"values") {
394 // Overwrite the extension.
395 pathData.extension = "arsc";
396
397 const std::string outputFilename = buildIntermediateFilename(
398 options.outputPath, pathData);
399 if (!compileTable(&context, options, pathData, outputFilename)) {
400 error = true;
401 }
402
403 } else {
404 const std::string outputFilename = buildIntermediateFilename(options.outputPath,
405 pathData);
406 if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
407 if (*type != ResourceType::kRaw) {
408 if (pathData.extension == "xml") {
409 if (!compileXml(&context, options, pathData, outputFilename)) {
410 error = true;
411 }
412 } else if (pathData.extension == "png" || pathData.extension == "9.png") {
413 if (!compilePng(&context, options, pathData, outputFilename)) {
414 error = true;
415 }
416 } else {
417 if (!compileFile(&context, options, pathData, outputFilename)) {
418 error = true;
419 }
420 }
421 } else {
422 if (!compileFile(&context, options, pathData, outputFilename)) {
423 error = true;
424 }
425 }
426 } else {
427 context.getDiagnostics()->error(
428 DiagMessage() << "invalid file path '" << pathData.source << "'");
429 error = true;
430 }
431 }
432 }
433
434 if (error) {
435 return 1;
436 }
437 return 0;
438}
439
440} // namespace aapt