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