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