blob: a5e6aefd1e0f625cccfabbdaeb9c5bbc110f3db7 [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
Adam Lesinskice5e56e2016-10-21 17:56:45 -070017#include <dirent.h>
18
Adam Lesinskice5e56e2016-10-21 17:56:45 -070019#include <string>
20
Adam Lesinskid5083f62017-01-16 15:07:21 -080021#include "android-base/errors.h"
22#include "android-base/file.h"
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070023#include "android-base/utf8.h"
Adam Lesinskid5083f62017-01-16 15:07:21 -080024#include "androidfw/StringPiece.h"
25#include "google/protobuf/io/coded_stream.h"
26#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
27
Adam Lesinski1ab598f2015-08-14 14:26:04 -070028#include "ConfigDescription.h"
29#include "Diagnostics.h"
30#include "Flags.h"
31#include "ResourceParser.h"
32#include "ResourceTable.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070033#include "compile/IdAssigner.h"
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070034#include "compile/InlineXmlFormatParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070035#include "compile/Png.h"
Adam Lesinski393b5f02015-12-17 13:03:11 -080036#include "compile/PseudolocaleGenerator.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070037#include "compile/XmlIdCollector.h"
Adam Lesinski46708052017-09-29 14:49:15 -070038#include "format/Archive.h"
39#include "format/binary/XmlFlattener.h"
40#include "format/proto/ProtoSerialize.h"
Adam Lesinski06460ef2017-03-14 18:52:13 -070041#include "io/BigBufferOutputStream.h"
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070042#include "io/FileInputStream.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070043#include "io/Util.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070044#include "util/Files.h"
45#include "util/Maybe.h"
46#include "util/Util.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080047#include "xml/XmlDom.h"
48#include "xml/XmlPullParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070049
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070050using ::aapt::io::FileInputStream;
51using ::android::StringPiece;
52using ::google::protobuf::io::CopyingOutputStreamAdaptor;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070053
Adam Lesinski1ab598f2015-08-14 14:26:04 -070054namespace aapt {
55
56struct ResourcePathData {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070057 Source source;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070058 std::string resource_dir;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070059 std::string name;
60 std::string extension;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070061
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070062 // Original config str. We keep this because when we parse the config, we may add on
63 // version qualifiers. We want to preserve the original input so the output is easily
Adam Lesinskicacb28f2016-10-19 12:18:14 -070064 // computed before hand.
Adam Lesinskice5e56e2016-10-21 17:56:45 -070065 std::string config_str;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070066 ConfigDescription config;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070067};
68
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070069// Resource file paths are expected to look like: [--/res/]type[-config]/name
Adam Lesinskice5e56e2016-10-21 17:56:45 -070070static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
71 std::string* out_error) {
72 std::vector<std::string> parts = util::Split(path, file::sDirSep);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070073 if (parts.size() < 2) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070074 if (out_error) *out_error = "bad resource path";
Adam Lesinskicacb28f2016-10-19 12:18:14 -070075 return {};
76 }
77
78 std::string& dir = parts[parts.size() - 2];
Adam Lesinskice5e56e2016-10-21 17:56:45 -070079 StringPiece dir_str = dir;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070080
Adam Lesinskice5e56e2016-10-21 17:56:45 -070081 StringPiece config_str;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070082 ConfigDescription config;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070083 size_t dash_pos = dir.find('-');
84 if (dash_pos != std::string::npos) {
85 config_str = dir_str.substr(dash_pos + 1, dir.size() - (dash_pos + 1));
86 if (!ConfigDescription::Parse(config_str, &config)) {
87 if (out_error) {
88 std::stringstream err_str;
89 err_str << "invalid configuration '" << config_str << "'";
90 *out_error = err_str.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -070091 }
92 return {};
Adam Lesinski1ab598f2015-08-14 14:26:04 -070093 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070094 dir_str = dir_str.substr(0, dash_pos);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070095 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070096
Adam Lesinskicacb28f2016-10-19 12:18:14 -070097 std::string& filename = parts[parts.size() - 1];
98 StringPiece name = filename;
99 StringPiece extension;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700100 size_t dot_pos = filename.find('.');
101 if (dot_pos != std::string::npos) {
102 extension = name.substr(dot_pos + 1, filename.size() - (dot_pos + 1));
103 name = name.substr(0, dot_pos);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700104 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700105
Adam Lesinskid5083f62017-01-16 15:07:21 -0800106 return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(),
107 extension.to_string(), config_str.to_string(), config};
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700108}
109
110struct CompileOptions {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700111 std::string output_path;
112 Maybe<std::string> res_dir;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700113 bool pseudolocalize = false;
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700114 bool no_png_crunch = false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700115 bool legacy_mode = false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700116 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700117};
118
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700119static std::string BuildIntermediateFilename(const ResourcePathData& data) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700120 std::stringstream name;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700121 name << data.resource_dir;
122 if (!data.config_str.empty()) {
123 name << "-" << data.config_str;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700124 }
125 name << "_" << data.name;
126 if (!data.extension.empty()) {
127 name << "." << data.extension;
128 }
129 name << ".flat";
130 return name.str();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800131}
132
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700133static bool IsHidden(const StringPiece& filename) {
134 return util::StartsWith(filename, ".");
Adam Lesinskia40e9722015-11-24 19:11:46 -0800135}
136
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700137// Walks the res directory structure, looking for resource files.
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700138static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
139 std::vector<ResourcePathData>* out_path_data) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700140 const std::string& root_dir = options.res_dir.value();
Adam Lesinski06460ef2017-03-14 18:52:13 -0700141 std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700142 if (!d) {
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700143 context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: "
Adam Lesinski06460ef2017-03-14 18:52:13 -0700144 << android::base::SystemErrorCodeToString(errno));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700145 return false;
146 }
147
148 while (struct dirent* entry = readdir(d.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700149 if (IsHidden(entry->d_name)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700150 continue;
151 }
152
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700153 std::string prefix_path = root_dir;
154 file::AppendPath(&prefix_path, entry->d_name);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700155
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700156 if (file::GetFileType(prefix_path) != file::FileType::kDirectory) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700157 continue;
158 }
159
Adam Lesinski06460ef2017-03-14 18:52:13 -0700160 std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700161 if (!subdir) {
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700162 context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: "
Adam Lesinski06460ef2017-03-14 18:52:13 -0700163 << android::base::SystemErrorCodeToString(errno));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700164 return false;
165 }
166
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700167 while (struct dirent* leaf_entry = readdir(subdir.get())) {
168 if (IsHidden(leaf_entry->d_name)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700169 continue;
170 }
171
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700172 std::string full_path = prefix_path;
173 file::AppendPath(&full_path, leaf_entry->d_name);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700174
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700175 std::string err_str;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700176 Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700177 if (!path_data) {
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700178 context->GetDiagnostics()->Error(DiagMessage(full_path) << err_str);
Adam Lesinskia40e9722015-11-24 19:11:46 -0800179 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700180 }
181
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700182 out_path_data->push_back(std::move(path_data.value()));
Adam Lesinskia40e9722015-11-24 19:11:46 -0800183 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700184 }
185 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700186}
187
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700188static bool CompileTable(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700189 const ResourcePathData& path_data, IArchiveWriter* writer,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700190 const std::string& output_path) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700191 ResourceTable table;
192 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700193 FileInputStream fin(path_data.source.path);
194 if (fin.HadError()) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700195 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700196 << "failed to open file: " << fin.GetError());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700197 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700198 }
199
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700200 // Parse the values file from XML.
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700201 xml::XmlPullParser xml_parser(&fin);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700202
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700203 ResourceParserOptions parser_options;
204 parser_options.error_on_positional_arguments = !options.legacy_mode;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700205
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700206 // If the filename includes donottranslate, then the default translatable is false.
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700207 parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700208
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700209 ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700210 parser_options);
211 if (!res_parser.Parse(&xml_parser)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700212 return false;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800213 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700214 }
Adam Lesinski83f22552015-11-07 11:51:23 -0800215
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700216 if (options.pseudolocalize) {
217 // Generate pseudo-localized strings (en-XA and ar-XB).
218 // These are created as weak symbols, and are only generated from default
219 // configuration
220 // strings and plurals.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700221 PseudolocaleGenerator pseudolocale_generator;
222 if (!pseudolocale_generator.Consume(context, &table)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700223 return false;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700224 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700225 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700226
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700227 // Ensure we have the compilation package at least.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700228 table.CreatePackage(context->GetCompilationPackage());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700229
230 // Assign an ID to any package that has resources.
231 for (auto& pkg : table.packages) {
232 if (!pkg->id) {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700233 // If no package ID was set while parsing (public identifiers), auto assign an ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700234 pkg->id = context->GetPackageId();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700235 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700236 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700237
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700238 // Create the file/zip entry.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700239 if (!writer->StartEntry(output_path, 0)) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700240 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700241 return false;
242 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800243
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700244 // Make sure CopyingOutputStreamAdaptor is deleted before we call
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700245 // writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700246 {
247 // Wrap our IArchiveWriter with an adaptor that implements the
Adam Lesinski06460ef2017-03-14 18:52:13 -0700248 // ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700249 CopyingOutputStreamAdaptor copying_adaptor(writer);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700250
Adam Lesinski8cdca1b2017-09-28 15:50:03 -0700251 pb::ResourceTable pb_table;
252 SerializeTableToPb(table, &pb_table);
253 if (!pb_table.SerializeToZeroCopyStream(&copying_adaptor)) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700254 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700255 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700256 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700257 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800258
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700259 if (!writer->FinishEntry()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700260 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700261 return false;
262 }
263 return true;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800264}
265
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700266static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file,
267 const BigBuffer& buffer, IArchiveWriter* writer,
Adam Lesinski59e04c62016-02-04 15:59:23 -0800268 IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700269 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700270 if (!writer->StartEntry(output_path, 0)) {
271 diag->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700272 return false;
273 }
274
275 // Make sure CopyingOutputStreamAdaptor is deleted before we call
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700276 // writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700277 {
278 // Wrap our IArchiveWriter with an adaptor that implements the
Adam Lesinski06460ef2017-03-14 18:52:13 -0700279 // ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700280 CopyingOutputStreamAdaptor copying_adaptor(writer);
281 CompiledFileOutputStream output_stream(&copying_adaptor);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700282
283 // Number of CompiledFiles.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700284 output_stream.WriteLittleEndian32(1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700285
Adam Lesinski8cdca1b2017-09-28 15:50:03 -0700286 pb::internal::CompiledFile pb_compiled_file;
287 SerializeCompiledFileToPb(file, &pb_compiled_file);
288 output_stream.WriteCompiledFile(pb_compiled_file);
289 output_stream.WriteData(buffer);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700290
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700291 if (output_stream.HadError()) {
292 diag->Error(DiagMessage(output_path) << "failed to write data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700293 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800294 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700295 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800296
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700297 if (!writer->FinishEntry()) {
298 diag->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700299 return false;
300 }
301 return true;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800302}
303
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700304static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file,
305 const android::FileMap& map, IArchiveWriter* writer,
Adam Lesinski59e04c62016-02-04 15:59:23 -0800306 IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700307 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700308 if (!writer->StartEntry(output_path, 0)) {
309 diag->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700310 return false;
311 }
312
313 // Make sure CopyingOutputStreamAdaptor is deleted before we call
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700314 // writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700315 {
316 // Wrap our IArchiveWriter with an adaptor that implements the
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700317 // ZeroCopyOutputStream interface.
318 CopyingOutputStreamAdaptor copying_adaptor(writer);
319 CompiledFileOutputStream output_stream(&copying_adaptor);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700320
321 // Number of CompiledFiles.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700322 output_stream.WriteLittleEndian32(1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700323
Adam Lesinski8cdca1b2017-09-28 15:50:03 -0700324 pb::internal::CompiledFile pb_compiled_file;
325 SerializeCompiledFileToPb(file, &pb_compiled_file);
326 output_stream.WriteCompiledFile(pb_compiled_file);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700327 output_stream.WriteData(map.getDataPtr(), map.getDataLength());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700328
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700329 if (output_stream.HadError()) {
330 diag->Error(DiagMessage(output_path) << "failed to write data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700331 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800332 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700333 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800334
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700335 if (!writer->FinishEntry()) {
336 diag->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700337 return false;
338 }
339 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700340}
341
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700342static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path,
343 xml::XmlResource* xmlres, CompiledFileOutputStream* out) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700344 BigBuffer buffer(1024);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700345 XmlFlattenerOptions xml_flattener_options;
346 xml_flattener_options.keep_raw_values = true;
347 XmlFlattener flattener(&buffer, xml_flattener_options);
348 if (!flattener.Consume(context, xmlres)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700349 return false;
350 }
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700351
Adam Lesinski8cdca1b2017-09-28 15:50:03 -0700352 pb::internal::CompiledFile pb_compiled_file;
353 SerializeCompiledFileToPb(xmlres->file, &pb_compiled_file);
354 out->WriteCompiledFile(pb_compiled_file);
355 out->WriteData(buffer);
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700356
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700357 if (out->HadError()) {
Adam Lesinski8cdca1b2017-09-28 15:50:03 -0700358 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write XML data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700359 return false;
360 }
361 return true;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700362}
363
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700364static bool IsValidFile(IAaptContext* context, const std::string& input_path) {
Adam Lesinski776aa952017-04-24 15:09:32 -0700365 const file::FileType file_type = file::GetFileType(input_path);
366 if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) {
367 if (file_type == file::FileType::kDirectory) {
368 context->GetDiagnostics()->Error(DiagMessage(input_path)
369 << "resource file cannot be a directory");
Adam Lesinskicc73e992017-05-12 18:16:44 -0700370 } else if (file_type == file::FileType::kNonexistant) {
371 context->GetDiagnostics()->Error(DiagMessage(input_path) << "file not found");
Adam Lesinski776aa952017-04-24 15:09:32 -0700372 } else {
373 context->GetDiagnostics()->Error(DiagMessage(input_path)
374 << "not a valid resource file");
375 }
376 return false;
377 }
378 return true;
379}
380
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700381static bool CompileXml(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700382 const ResourcePathData& path_data, IArchiveWriter* writer,
383 const std::string& output_path) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700384 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700385 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700386 }
387
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700388 std::unique_ptr<xml::XmlResource> xmlres;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700389 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700390 FileInputStream fin(path_data.source.path);
391 if (fin.HadError()) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700392 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700393 << "failed to open file: " << fin.GetError());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700394 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700395 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700396
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700397 xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700398 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700399
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700400 if (!xmlres) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700401 return false;
402 }
403
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700404 xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700405 xmlres->file.config = path_data.config;
406 xmlres->file.source = path_data.source;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700407
408 // Collect IDs that are defined here.
409 XmlIdCollector collector;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700410 if (!collector.Consume(context, xmlres.get())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700411 return false;
412 }
413
414 // Look for and process any <aapt:attr> tags and create sub-documents.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700415 InlineXmlFormatParser inline_xml_format_parser;
416 if (!inline_xml_format_parser.Consume(context, xmlres.get())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700417 return false;
418 }
419
420 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700421 if (!writer->StartEntry(output_path, 0)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700422 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700423 return false;
424 }
425
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700426 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700427 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700428 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700429 CopyingOutputStreamAdaptor copying_adaptor(writer);
430 CompiledFileOutputStream output_stream(&copying_adaptor);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700431
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700432 std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
433 inline_xml_format_parser.GetExtractedInlineXmlDocuments();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700434
435 // Number of CompiledFiles.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700436 output_stream.WriteLittleEndian32(1 + inline_documents.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700437
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700438 if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700439 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700440 }
441
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700442 for (auto& inline_xml_doc : inline_documents) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700443 if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700444 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700445 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700446 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700447 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700448
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700449 if (!writer->FinishEntry()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700450 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700451 return false;
452 }
453 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700454}
455
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700456static bool CompilePng(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700457 const ResourcePathData& path_data, IArchiveWriter* writer,
458 const std::string& output_path) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700459 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700460 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700461 }
462
463 BigBuffer buffer(4096);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700464 ResourceFile res_file;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700465 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700466 res_file.config = path_data.config;
467 res_file.source = path_data.source;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700468
469 {
470 std::string content;
Adam Lesinski2354b562017-05-26 16:31:38 -0700471 if (!android::base::ReadFileToString(path_data.source.path, &content,
472 true /*follow_symlinks*/)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700473 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700474 << "failed to open file: "
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700475 << android::base::SystemErrorCodeToString(errno));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700476 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700477 }
478
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700479 BigBuffer crunched_png_buffer(4096);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700480 io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700481
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700482 // Ensure that we only keep the chunks we care about if we end up
483 // using the original PNG instead of the crunched one.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700484 PngChunkFilter png_chunk_filter(content);
Adam Lesinskicc73e992017-05-12 18:16:44 -0700485 std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700486 if (!image) {
487 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700488 }
489
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700490 std::unique_ptr<NinePatch> nine_patch;
491 if (path_data.extension == "9.png") {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700492 std::string err;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700493 nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700494 if (!nine_patch) {
495 context->GetDiagnostics()->Error(DiagMessage() << err);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700496 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700497 }
498
499 // Remove the 1px border around the NinePatch.
500 // Basically the row array is shifted up by 1, and the length is treated
501 // as height - 2.
502 // For each row, shift the array to the left by 1, and treat the length as
503 // width - 2.
504 image->width -= 2;
505 image->height -= 2;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700506 memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700507 for (int32_t h = 0; h < image->height; h++) {
508 memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
509 }
510
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700511 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700512 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "9-patch: "
513 << *nine_patch);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700514 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700515 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700516
517 // Write the crunched PNG.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700518 if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700519 return false;
520 }
521
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700522 if (nine_patch != nullptr ||
523 crunched_png_buffer_out.ByteCount() <= png_chunk_filter.ByteCount()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700524 // No matter what, we must use the re-encoded PNG, even if it is larger.
525 // 9-patch images must be re-encoded since their borders are stripped.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700526 buffer.AppendBuffer(std::move(crunched_png_buffer));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700527 } else {
528 // The re-encoded PNG is larger than the original, and there is
529 // no mandatory transformation. Use the original.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700530 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700531 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
532 << "original PNG is smaller than crunched PNG"
533 << ", using original");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700534 }
535
Adam Lesinski06460ef2017-03-14 18:52:13 -0700536 png_chunk_filter.Rewind();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700537 BigBuffer filtered_png_buffer(4096);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700538 io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
539 io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700540 buffer.AppendBuffer(std::move(filtered_png_buffer));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700541 }
542
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700543 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700544 // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
545 // This will help catch exotic cases where the new code may generate larger PNGs.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700546 std::stringstream legacy_stream(content);
547 BigBuffer legacy_buffer(4096);
548 Png png(context->GetDiagnostics());
549 if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700550 return false;
551 }
552
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700553 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
554 << "legacy=" << legacy_buffer.size()
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700555 << " new=" << buffer.size());
556 }
557 }
558
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700559 if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer,
560 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700561 return false;
562 }
563 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700564}
565
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700566static bool CompileFile(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700567 const ResourcePathData& path_data, IArchiveWriter* writer,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700568 const std::string& output_path) {
569 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700570 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700571 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700572
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700573 BigBuffer buffer(256);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700574 ResourceFile res_file;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700575 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700576 res_file.config = path_data.config;
577 res_file.source = path_data.source;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700578
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700579 std::string error_str;
580 Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700581 if (!f) {
Adam Lesinski776aa952017-04-24 15:09:32 -0700582 context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to mmap file: "
583 << error_str);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700584 return false;
585 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700586
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700587 if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer,
588 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700589 return false;
590 }
591 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700592}
593
594class CompileContext : public IAaptContext {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700595 public:
Chris Warrington820d72a2017-04-27 15:27:01 +0100596 CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
597 }
598
Adam Lesinskib522f042017-04-21 16:57:59 -0700599 PackageType GetPackageType() override {
600 // Every compilation unit starts as an app and then gets linked as potentially something else.
601 return PackageType::kApp;
602 }
603
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700604 void SetVerbose(bool val) {
605 verbose_ = val;
606 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800607
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700608 bool IsVerbose() override {
609 return verbose_;
610 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800611
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700612 IDiagnostics* GetDiagnostics() override {
Chris Warrington820d72a2017-04-27 15:27:01 +0100613 return diagnostics_;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700614 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700615
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700616 NameMangler* GetNameMangler() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700617 abort();
618 return nullptr;
619 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700620
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700621 const std::string& GetCompilationPackage() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700622 static std::string empty;
623 return empty;
624 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700625
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700626 uint8_t GetPackageId() override {
627 return 0x0;
628 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700629
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700630 SymbolTable* GetExternalSymbols() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700631 abort();
632 return nullptr;
633 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800634
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700635 int GetMinSdkVersion() override {
636 return 0;
637 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700638
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700639 private:
Chris Warrington820d72a2017-04-27 15:27:01 +0100640 IDiagnostics* diagnostics_;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700641 bool verbose_ = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700642};
643
644/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700645 * Entry point for compilation phase. Parses arguments and dispatches to the
646 * correct steps.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700647 */
Chris Warrington820d72a2017-04-27 15:27:01 +0100648int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
649 CompileContext context(diagnostics);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700650 CompileOptions options;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700651
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700652 bool verbose = false;
653 Flags flags =
654 Flags()
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700655 .RequiredFlag("-o", "Output path", &options.output_path)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700656 .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700657 .OptionalSwitch("--pseudo-localize",
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700658 "Generate resources for pseudo-locales "
659 "(en-XA and ar-XB)",
660 &options.pseudolocalize)
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700661 .OptionalSwitch("--no-crunch", "Disables PNG processing", &options.no_png_crunch)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700662 .OptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
663 &options.legacy_mode)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700664 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
665 if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700666 return 1;
667 }
668
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700669 context.SetVerbose(verbose);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700670
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700671 std::unique_ptr<IArchiveWriter> archive_writer;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700672
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700673 std::vector<ResourcePathData> input_data;
674 if (options.res_dir) {
675 if (!flags.GetArgs().empty()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700676 // Can't have both files and a resource directory.
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700677 context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700678 flags.Usage("aapt2 compile", &std::cerr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700679 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700680 }
681
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700682 if (!LoadInputFilesFromDir(&context, options, &input_data)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700683 return 1;
684 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800685
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700686 archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options.output_path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700687
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700688 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700689 input_data.reserve(flags.GetArgs().size());
Adam Lesinskia40e9722015-11-24 19:11:46 -0800690
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700691 // Collect data from the path for each input file.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700692 for (const std::string& arg : flags.GetArgs()) {
693 std::string error_str;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700694 if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700695 input_data.push_back(std::move(path_data.value()));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700696 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700697 context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" << arg << ")");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700698 return 1;
699 }
700 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800701
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700702 archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options.output_path);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700703 }
704
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700705 if (!archive_writer) {
Adam Lesinskidfaecaf2016-10-20 17:08:51 -0700706 return 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700707 }
708
709 bool error = false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700710 for (ResourcePathData& path_data : input_data) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700711 if (options.verbose) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700712 context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700713 }
714
Adam Lesinski776aa952017-04-24 15:09:32 -0700715 if (!IsValidFile(&context, path_data.source.path)) {
716 error = true;
717 continue;
718 }
719
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700720 if (path_data.resource_dir == "values") {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700721 // Overwrite the extension.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700722 path_data.extension = "arsc";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700723
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700724 const std::string output_filename = BuildIntermediateFilename(path_data);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700725 if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700726 error = true;
727 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800728
729 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700730 const std::string output_filename = BuildIntermediateFilename(path_data);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700731 if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700732 if (*type != ResourceType::kRaw) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700733 if (path_data.extension == "xml") {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700734 if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700735 error = true;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800736 }
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700737 } else if (!options.no_png_crunch &&
738 (path_data.extension == "png" || path_data.extension == "9.png")) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700739 if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700740 error = true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700741 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700742 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700743 if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700744 error = true;
745 }
746 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700747 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700748 if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700749 error = true;
750 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700751 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700752 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700753 context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source
754 << "'");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700755 error = true;
756 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700757 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700758 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700759
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700760 if (error) {
761 return 1;
762 }
763 return 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700764}
765
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700766} // namespace aapt