blob: 5d90ab9f6bad42d0d4d3904a3370cba324457b04 [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 Lesinski9ba47d82015-10-13 11:37:10 -0700151 ResourceTablePackage* pkg = table.createPackage(context->getCompilationPackage());
152 if (!pkg->id) {
153 // If no package ID was set while parsing (public identifiers), auto assign an ID.
154 pkg->id = context->getPackageId();
155 }
156
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700157 // Assign IDs to prepare the table for flattening.
158 IdAssigner idAssigner;
159 if (!idAssigner.consume(context, &table)) {
160 return false;
161 }
162
163 // Flatten the table.
164 BigBuffer buffer(1024);
165 TableFlattenerOptions tableFlattenerOptions;
166 tableFlattenerOptions.useExtendedChunks = true;
167 TableFlattener flattener(&buffer, tableFlattenerOptions);
168 if (!flattener.consume(context, &table)) {
169 return false;
170 }
171
172 // Build the output filename.
173 std::ofstream fout(outputPath, std::ofstream::binary);
174 if (!fout) {
175 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
176 return false;
177 }
178
179 // Write it to disk.
180 if (!util::writeAll(fout, buffer)) {
181 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
182 return false;
183 }
184 return true;
185}
186
187static bool compileXml(IAaptContext* context, const CompileOptions& options,
188 const ResourcePathData& pathData, const std::string& outputPath) {
189
190 std::unique_ptr<XmlResource> xmlRes;
191
192 {
193 std::ifstream fin(pathData.source.path, std::ifstream::binary);
194 if (!fin) {
195 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
196 return false;
197 }
198
199 xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
200
201 fin.close();
202 }
203
204 if (!xmlRes) {
205 return false;
206 }
207
208 // Collect IDs that are defined here.
209 XmlIdCollector collector;
210 if (!collector.consume(context, xmlRes.get())) {
211 return false;
212 }
213
214 xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
215 xmlRes->file.config = pathData.config;
216 xmlRes->file.source = pathData.source;
217
218 BigBuffer buffer(1024);
219 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
220
221 XmlFlattenerOptions xmlFlattenerOptions;
222 xmlFlattenerOptions.keepRawValues = true;
223 XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
224 if (!flattener.consume(context, xmlRes.get())) {
225 return false;
226 }
227
228 fileExportWriter.finish();
229
230 std::ofstream fout(outputPath, std::ofstream::binary);
231 if (!fout) {
232 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
233 return false;
234 }
235
236 // Write it to disk.
237 if (!util::writeAll(fout, buffer)) {
238 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
239 return false;
240 }
241 return true;
242}
243
244static bool compilePng(IAaptContext* context, const CompileOptions& options,
245 const ResourcePathData& pathData, const std::string& outputPath) {
246 BigBuffer buffer(4096);
247 ResourceFile resFile;
248 resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
249 resFile.config = pathData.config;
250 resFile.source = pathData.source;
251
252 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
253
254 {
255 std::ifstream fin(pathData.source.path, std::ifstream::binary);
256 if (!fin) {
257 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
258 return false;
259 }
260
261 Png png(context->getDiagnostics());
262 if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
263 return false;
264 }
265 }
266
267 fileExportWriter.finish();
268
269 std::ofstream fout(outputPath, std::ofstream::binary);
270 if (!fout) {
271 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
272 return false;
273 }
274
275 if (!util::writeAll(fout, buffer)) {
276 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
277 return false;
278 }
279 return true;
280}
281
282static bool compileFile(IAaptContext* context, const CompileOptions& options,
283 const ResourcePathData& pathData, const std::string& outputPath) {
284 BigBuffer buffer(256);
285 ResourceFile resFile;
286 resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
287 resFile.config = pathData.config;
288 resFile.source = pathData.source;
289
290 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
291
292 std::string errorStr;
293 Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
294 if (!f) {
295 context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
296 return false;
297 }
298
299 std::ofstream fout(outputPath, std::ofstream::binary);
300 if (!fout) {
301 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
302 return false;
303 }
304
305 // Manually set the size and don't call finish(). This is because we are not copying from
306 // the buffer the entire file.
307 fileExportWriter.getChunkHeader()->size =
308 util::hostToDevice32(buffer.size() + f.value().getDataLength());
309 if (!util::writeAll(fout, buffer)) {
310 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
311 return false;
312 }
313
314 if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
315 context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
316 return false;
317 }
318 return true;
319}
320
321class CompileContext : public IAaptContext {
322private:
323 StdErrDiagnostics mDiagnostics;
324
325public:
326 IDiagnostics* getDiagnostics() override {
327 return &mDiagnostics;
328 }
329
330 NameMangler* getNameMangler() override {
331 abort();
332 return nullptr;
333 }
334
335 StringPiece16 getCompilationPackage() override {
336 return {};
337 }
338
339 uint8_t getPackageId() override {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700340 return 0x0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700341 }
342
343 ISymbolTable* getExternalSymbols() override {
344 abort();
345 return nullptr;
346 }
347};
348
349/**
350 * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
351 */
352int compile(const std::vector<StringPiece>& args) {
353 CompileOptions options;
354
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700355 Maybe<std::string> product;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700356 Flags flags = Flags()
357 .requiredFlag("-o", "Output path", &options.outputPath)
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700358 .optionalFlag("--product", "Product type to compile", &product)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700359 .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
360 if (!flags.parse("aapt2 compile", args, &std::cerr)) {
361 return 1;
362 }
363
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700364 if (product) {
365 options.product = util::utf8ToUtf16(product.value());
366 }
367
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700368 CompileContext context;
369
370 std::vector<ResourcePathData> inputData;
371 inputData.reserve(flags.getArgs().size());
372
373 // Collect data from the path for each input file.
374 for (const std::string& arg : flags.getArgs()) {
375 std::string errorStr;
376 if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
377 inputData.push_back(std::move(pathData.value()));
378 } else {
379 context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
380 return 1;
381 }
382 }
383
384 bool error = false;
385 for (ResourcePathData& pathData : inputData) {
386 if (options.verbose) {
387 context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
388 }
389
390 if (pathData.resourceDir == u"values") {
391 // Overwrite the extension.
392 pathData.extension = "arsc";
393
394 const std::string outputFilename = buildIntermediateFilename(
395 options.outputPath, pathData);
396 if (!compileTable(&context, options, pathData, outputFilename)) {
397 error = true;
398 }
399
400 } else {
401 const std::string outputFilename = buildIntermediateFilename(options.outputPath,
402 pathData);
403 if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
404 if (*type != ResourceType::kRaw) {
405 if (pathData.extension == "xml") {
406 if (!compileXml(&context, options, pathData, outputFilename)) {
407 error = true;
408 }
409 } else if (pathData.extension == "png" || pathData.extension == "9.png") {
410 if (!compilePng(&context, options, pathData, outputFilename)) {
411 error = true;
412 }
413 } else {
414 if (!compileFile(&context, options, pathData, outputFilename)) {
415 error = true;
416 }
417 }
418 } else {
419 if (!compileFile(&context, options, pathData, outputFilename)) {
420 error = true;
421 }
422 }
423 } else {
424 context.getDiagnostics()->error(
425 DiagMessage() << "invalid file path '" << pathData.source << "'");
426 error = true;
427 }
428 }
429 }
430
431 if (error) {
432 return 1;
433 }
434 return 0;
435}
436
437} // namespace aapt