blob: a17a0d3e72998d52a6844b5ca33d67cfd38a98b0 [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"
Izabela Orlowska10560192018-04-13 11:56:35 +010033#include "cmd/Util.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070034#include "compile/IdAssigner.h"
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070035#include "compile/InlineXmlFormatParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070036#include "compile/Png.h"
Adam Lesinski393b5f02015-12-17 13:03:11 -080037#include "compile/PseudolocaleGenerator.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070038#include "compile/XmlIdCollector.h"
Adam Lesinski46708052017-09-29 14:49:15 -070039#include "format/Archive.h"
Adam Lesinski00451162017-10-03 07:44:08 -070040#include "format/Container.h"
Adam Lesinski46708052017-09-29 14:49:15 -070041#include "format/proto/ProtoSerialize.h"
Adam Lesinski00451162017-10-03 07:44:08 -070042#include "io/BigBufferStream.h"
43#include "io/FileStream.h"
44#include "io/StringStream.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070045#include "io/Util.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070046#include "util/Files.h"
47#include "util/Maybe.h"
48#include "util/Util.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080049#include "xml/XmlDom.h"
50#include "xml/XmlPullParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070051
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070052using ::aapt::io::FileInputStream;
Izabela Orlowskac81d9f32017-12-05 12:07:28 +000053using ::aapt::text::Printer;
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070054using ::android::StringPiece;
Adam Lesinski00451162017-10-03 07:44:08 -070055using ::android::base::SystemErrorCodeToString;
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070056using ::google::protobuf::io::CopyingOutputStreamAdaptor;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070057
Adam Lesinski1ab598f2015-08-14 14:26:04 -070058namespace aapt {
59
60struct ResourcePathData {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070061 Source source;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070062 std::string resource_dir;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070063 std::string name;
64 std::string extension;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070065
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070066 // Original config str. We keep this because when we parse the config, we may add on
67 // version qualifiers. We want to preserve the original input so the output is easily
Adam Lesinskicacb28f2016-10-19 12:18:14 -070068 // computed before hand.
Adam Lesinskice5e56e2016-10-21 17:56:45 -070069 std::string config_str;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070070 ConfigDescription config;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070071};
72
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070073// Resource file paths are expected to look like: [--/res/]type[-config]/name
Adam Lesinskice5e56e2016-10-21 17:56:45 -070074static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
75 std::string* out_error) {
76 std::vector<std::string> parts = util::Split(path, file::sDirSep);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070077 if (parts.size() < 2) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070078 if (out_error) *out_error = "bad resource path";
Adam Lesinskicacb28f2016-10-19 12:18:14 -070079 return {};
80 }
81
82 std::string& dir = parts[parts.size() - 2];
Adam Lesinskice5e56e2016-10-21 17:56:45 -070083 StringPiece dir_str = dir;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070084
Adam Lesinskice5e56e2016-10-21 17:56:45 -070085 StringPiece config_str;
Adam Lesinskicacb28f2016-10-19 12:18:14 -070086 ConfigDescription config;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070087 size_t dash_pos = dir.find('-');
88 if (dash_pos != std::string::npos) {
89 config_str = dir_str.substr(dash_pos + 1, dir.size() - (dash_pos + 1));
90 if (!ConfigDescription::Parse(config_str, &config)) {
91 if (out_error) {
92 std::stringstream err_str;
93 err_str << "invalid configuration '" << config_str << "'";
94 *out_error = err_str.str();
Adam Lesinskicacb28f2016-10-19 12:18:14 -070095 }
96 return {};
Adam Lesinski1ab598f2015-08-14 14:26:04 -070097 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070098 dir_str = dir_str.substr(0, dash_pos);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070099 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700100
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700101 std::string& filename = parts[parts.size() - 1];
102 StringPiece name = filename;
103 StringPiece extension;
yd6b83292018-04-11 09:54:56 -0700104
105 const std::string kNinePng = ".9.png";
106 if (filename.size() > kNinePng.size()
107 && std::equal(kNinePng.rbegin(), kNinePng.rend(), filename.rbegin())) {
108 // Split on .9.png if this extension is present at the end of the file path
109 name = name.substr(0, filename.size() - kNinePng.size());
110 extension = "9.png";
111 } else {
112 // Split on the last period occurrence
113 size_t dot_pos = filename.rfind('.');
114 if (dot_pos != std::string::npos) {
115 extension = name.substr(dot_pos + 1, filename.size() - (dot_pos + 1));
116 name = name.substr(0, dot_pos);
117 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700118 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700119
Adam Lesinskid5083f62017-01-16 15:07:21 -0800120 return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(),
121 extension.to_string(), config_str.to_string(), config};
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700122}
123
124struct CompileOptions {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700125 std::string output_path;
126 Maybe<std::string> res_dir;
Izabela Orlowskac81d9f32017-12-05 12:07:28 +0000127 Maybe<std::string> generate_text_symbols_path;
Izabela Orlowskac7ac3a12018-03-27 14:46:52 +0100128 Maybe<Visibility::Level> visibility;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700129 bool pseudolocalize = false;
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700130 bool no_png_crunch = false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700131 bool legacy_mode = false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700132 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700133};
134
Adam Lesinski00451162017-10-03 07:44:08 -0700135static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700136 std::stringstream name;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700137 name << data.resource_dir;
138 if (!data.config_str.empty()) {
139 name << "-" << data.config_str;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700140 }
141 name << "_" << data.name;
142 if (!data.extension.empty()) {
143 name << "." << data.extension;
144 }
145 name << ".flat";
146 return name.str();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800147}
148
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700149static bool IsHidden(const StringPiece& filename) {
150 return util::StartsWith(filename, ".");
Adam Lesinskia40e9722015-11-24 19:11:46 -0800151}
152
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700153// Walks the res directory structure, looking for resource files.
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700154static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
155 std::vector<ResourcePathData>* out_path_data) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700156 const std::string& root_dir = options.res_dir.value();
Adam Lesinski06460ef2017-03-14 18:52:13 -0700157 std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700158 if (!d) {
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700159 context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: "
Adam Lesinski00451162017-10-03 07:44:08 -0700160 << SystemErrorCodeToString(errno));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700161 return false;
162 }
163
164 while (struct dirent* entry = readdir(d.get())) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700165 if (IsHidden(entry->d_name)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700166 continue;
167 }
168
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700169 std::string prefix_path = root_dir;
170 file::AppendPath(&prefix_path, entry->d_name);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700171
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700172 if (file::GetFileType(prefix_path) != file::FileType::kDirectory) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700173 continue;
174 }
175
Adam Lesinski06460ef2017-03-14 18:52:13 -0700176 std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700177 if (!subdir) {
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700178 context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: "
Adam Lesinski00451162017-10-03 07:44:08 -0700179 << SystemErrorCodeToString(errno));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700180 return false;
181 }
182
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700183 while (struct dirent* leaf_entry = readdir(subdir.get())) {
184 if (IsHidden(leaf_entry->d_name)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700185 continue;
186 }
187
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700188 std::string full_path = prefix_path;
189 file::AppendPath(&full_path, leaf_entry->d_name);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700190
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700191 std::string err_str;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700192 Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700193 if (!path_data) {
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700194 context->GetDiagnostics()->Error(DiagMessage(full_path) << err_str);
Adam Lesinskia40e9722015-11-24 19:11:46 -0800195 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700196 }
197
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700198 out_path_data->push_back(std::move(path_data.value()));
Adam Lesinskia40e9722015-11-24 19:11:46 -0800199 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700200 }
Adam Lesinskie6aa6d12017-12-20 14:01:14 -0800201
202 // File-system directory enumeration order is platform-dependent. Sort the result to remove any
203 // inconsistencies between platforms.
204 std::sort(
205 out_path_data->begin(), out_path_data->end(),
206 [](const ResourcePathData& a, const ResourcePathData& b) { return a.source < b.source; });
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700207 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700208}
209
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700210static bool CompileTable(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700211 const ResourcePathData& path_data, IArchiveWriter* writer,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700212 const std::string& output_path) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700213 ResourceTable table;
214 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700215 FileInputStream fin(path_data.source.path);
216 if (fin.HadError()) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700217 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700218 << "failed to open file: " << fin.GetError());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700219 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700220 }
221
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700222 // Parse the values file from XML.
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700223 xml::XmlPullParser xml_parser(&fin);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700224
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700225 ResourceParserOptions parser_options;
226 parser_options.error_on_positional_arguments = !options.legacy_mode;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700227
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700228 // If the filename includes donottranslate, then the default translatable is false.
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700229 parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700230
Izabela Orlowskac7ac3a12018-03-27 14:46:52 +0100231 // If visibility was forced, we need to use it when creating a new resource and also error if
232 // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
233 parser_options.visibility = options.visibility;
234
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700235 ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700236 parser_options);
237 if (!res_parser.Parse(&xml_parser)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700238 return false;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800239 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700240 }
Adam Lesinski83f22552015-11-07 11:51:23 -0800241
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700242 if (options.pseudolocalize) {
243 // Generate pseudo-localized strings (en-XA and ar-XB).
244 // These are created as weak symbols, and are only generated from default
245 // configuration
246 // strings and plurals.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700247 PseudolocaleGenerator pseudolocale_generator;
248 if (!pseudolocale_generator.Consume(context, &table)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700249 return false;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700250 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700251 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700252
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700253 // Ensure we have the compilation package at least.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700254 table.CreatePackage(context->GetCompilationPackage());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700255
256 // Assign an ID to any package that has resources.
257 for (auto& pkg : table.packages) {
258 if (!pkg->id) {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700259 // If no package ID was set while parsing (public identifiers), auto assign an ID.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700260 pkg->id = context->GetPackageId();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700261 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700262 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700263
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700264 // Create the file/zip entry.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700265 if (!writer->StartEntry(output_path, 0)) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700266 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700267 return false;
268 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800269
Adam Lesinski00451162017-10-03 07:44:08 -0700270 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700271 {
Adam Lesinski00451162017-10-03 07:44:08 -0700272 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700273 CopyingOutputStreamAdaptor copying_adaptor(writer);
Adam Lesinski00451162017-10-03 07:44:08 -0700274 ContainerWriter container_writer(&copying_adaptor, 1u);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700275
Adam Lesinski8cdca1b2017-09-28 15:50:03 -0700276 pb::ResourceTable pb_table;
Ryan Mitchella15c2a82018-03-26 11:05:31 -0700277 SerializeTableToPb(table, &pb_table, context->GetDiagnostics());
Adam Lesinski00451162017-10-03 07:44:08 -0700278 if (!container_writer.AddResTableEntry(pb_table)) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700279 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700280 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700281 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700282 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800283
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700284 if (!writer->FinishEntry()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700285 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700286 return false;
287 }
Izabela Orlowskac81d9f32017-12-05 12:07:28 +0000288
289 if (options.generate_text_symbols_path) {
290 io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
291
292 if (fout_text.HadError()) {
293 context->GetDiagnostics()->Error(DiagMessage()
294 << "failed writing to'"
295 << options.generate_text_symbols_path.value()
296 << "': " << fout_text.GetError());
297 return false;
298 }
299
300 Printer r_txt_printer(&fout_text);
301 for (const auto& package : table.packages) {
302 for (const auto& type : package->types) {
303 for (const auto& entry : type->entries) {
304 // Check access modifiers.
305 switch(entry->visibility.level) {
306 case Visibility::Level::kUndefined :
307 r_txt_printer.Print("default ");
308 break;
309 case Visibility::Level::kPublic :
310 r_txt_printer.Print("public ");
311 break;
312 case Visibility::Level::kPrivate :
313 r_txt_printer.Print("private ");
314 }
315
316 if (type->type != ResourceType::kStyleable) {
317 r_txt_printer.Print("int ");
318 r_txt_printer.Print(to_string(type->type));
319 r_txt_printer.Print(" ");
320 r_txt_printer.Println(entry->name);
321 } else {
322 r_txt_printer.Print("int[] styleable ");
323 r_txt_printer.Println(entry->name);
324
325 if (!entry->values.empty()) {
326 auto styleable = static_cast<const Styleable*>(entry->values.front()->value.get());
327 for (const auto& attr : styleable->entries) {
Izabela Orlowskac7ac3a12018-03-27 14:46:52 +0100328 // The visibility of the children under the styleable does not matter as they are
329 // nested under their parent and use its visibility.
Izabela Orlowskac81d9f32017-12-05 12:07:28 +0000330 r_txt_printer.Print("default int styleable ");
331 r_txt_printer.Print(entry->name);
Izabela Orlowska10560192018-04-13 11:56:35 +0100332 // If the package name is present, also include it in the mangled name (e.g.
333 // "android")
334 if (!attr.name.value().package.empty()) {
335 r_txt_printer.Print("_");
336 r_txt_printer.Print(MakePackageSafeName(attr.name.value().package));
337 }
Izabela Orlowskac81d9f32017-12-05 12:07:28 +0000338 r_txt_printer.Print("_");
339 r_txt_printer.Println(attr.name.value().entry);
340 }
341 }
342 }
343 }
344 }
345 }
346 }
347
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700348 return true;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800349}
350
Adam Lesinski00451162017-10-03 07:44:08 -0700351static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
352 io::KnownSizeInputStream* in, IArchiveWriter* writer,
Adam Lesinski59e04c62016-02-04 15:59:23 -0800353 IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700354 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700355 if (!writer->StartEntry(output_path, 0)) {
356 diag->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700357 return false;
358 }
359
Adam Lesinski00451162017-10-03 07:44:08 -0700360 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700361 {
Adam Lesinski00451162017-10-03 07:44:08 -0700362 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700363 CopyingOutputStreamAdaptor copying_adaptor(writer);
Adam Lesinski00451162017-10-03 07:44:08 -0700364 ContainerWriter container_writer(&copying_adaptor, 1u);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700365
Adam Lesinski8cdca1b2017-09-28 15:50:03 -0700366 pb::internal::CompiledFile pb_compiled_file;
367 SerializeCompiledFileToPb(file, &pb_compiled_file);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700368
Adam Lesinski00451162017-10-03 07:44:08 -0700369 if (!container_writer.AddResFileEntry(pb_compiled_file, in)) {
370 diag->Error(DiagMessage(output_path) << "failed to write entry data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700371 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800372 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700373 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800374
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700375 if (!writer->FinishEntry()) {
376 diag->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700377 return false;
378 }
379 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700380}
381
Adam Lesinski00451162017-10-03 07:44:08 -0700382static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
383 ContainerWriter* container_writer, IDiagnostics* diag) {
Adam Lesinski8cdca1b2017-09-28 15:50:03 -0700384 pb::internal::CompiledFile pb_compiled_file;
Adam Lesinski00451162017-10-03 07:44:08 -0700385 SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700386
Adam Lesinski00451162017-10-03 07:44:08 -0700387 pb::XmlNode pb_xml_node;
388 SerializeXmlToPb(*xmlres.root, &pb_xml_node);
389
390 std::string serialized_xml = pb_xml_node.SerializeAsString();
391 io::StringInputStream serialized_in(serialized_xml);
392
393 if (!container_writer->AddResFileEntry(pb_compiled_file, &serialized_in)) {
394 diag->Error(DiagMessage(output_path) << "failed to write entry data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700395 return false;
396 }
397 return true;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700398}
399
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700400static bool IsValidFile(IAaptContext* context, const std::string& input_path) {
Adam Lesinski776aa952017-04-24 15:09:32 -0700401 const file::FileType file_type = file::GetFileType(input_path);
402 if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) {
403 if (file_type == file::FileType::kDirectory) {
404 context->GetDiagnostics()->Error(DiagMessage(input_path)
405 << "resource file cannot be a directory");
Adam Lesinskicc73e992017-05-12 18:16:44 -0700406 } else if (file_type == file::FileType::kNonexistant) {
407 context->GetDiagnostics()->Error(DiagMessage(input_path) << "file not found");
Adam Lesinski776aa952017-04-24 15:09:32 -0700408 } else {
409 context->GetDiagnostics()->Error(DiagMessage(input_path)
410 << "not a valid resource file");
411 }
412 return false;
413 }
414 return true;
415}
416
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700417static bool CompileXml(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700418 const ResourcePathData& path_data, IArchiveWriter* writer,
419 const std::string& output_path) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700420 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700421 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700422 }
423
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700424 std::unique_ptr<xml::XmlResource> xmlres;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700425 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700426 FileInputStream fin(path_data.source.path);
427 if (fin.HadError()) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700428 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700429 << "failed to open file: " << fin.GetError());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700430 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700431 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700432
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700433 xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700434 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700435
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700436 if (!xmlres) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700437 return false;
438 }
439
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700440 xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700441 xmlres->file.config = path_data.config;
442 xmlres->file.source = path_data.source;
Adam Lesinski00451162017-10-03 07:44:08 -0700443 xmlres->file.type = ResourceFile::Type::kProtoXml;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700444
445 // Collect IDs that are defined here.
446 XmlIdCollector collector;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700447 if (!collector.Consume(context, xmlres.get())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700448 return false;
449 }
450
451 // Look for and process any <aapt:attr> tags and create sub-documents.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700452 InlineXmlFormatParser inline_xml_format_parser;
453 if (!inline_xml_format_parser.Consume(context, xmlres.get())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700454 return false;
455 }
456
457 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700458 if (!writer->StartEntry(output_path, 0)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700459 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700460 return false;
461 }
462
Adam Lesinski00451162017-10-03 07:44:08 -0700463 std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
464 inline_xml_format_parser.GetExtractedInlineXmlDocuments();
465
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700466 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700467 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700468 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700469 CopyingOutputStreamAdaptor copying_adaptor(writer);
Adam Lesinski00451162017-10-03 07:44:08 -0700470 ContainerWriter container_writer(&copying_adaptor, 1u + inline_documents.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700471
Adam Lesinski00451162017-10-03 07:44:08 -0700472 if (!FlattenXmlToOutStream(output_path, *xmlres, &container_writer,
473 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700474 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700475 }
476
Adam Lesinski00451162017-10-03 07:44:08 -0700477 for (const std::unique_ptr<xml::XmlResource>& inline_xml_doc : inline_documents) {
478 if (!FlattenXmlToOutStream(output_path, *inline_xml_doc, &container_writer,
479 context->GetDiagnostics())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700480 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700481 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700482 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700483 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700484
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700485 if (!writer->FinishEntry()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700486 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700487 return false;
488 }
Izabela Orlowskac81d9f32017-12-05 12:07:28 +0000489
490 if (options.generate_text_symbols_path) {
491 io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
492
493 if (fout_text.HadError()) {
494 context->GetDiagnostics()->Error(DiagMessage()
495 << "failed writing to'"
496 << options.generate_text_symbols_path.value()
497 << "': " << fout_text.GetError());
498 return false;
499 }
500
501 Printer r_txt_printer(&fout_text);
502 for (const auto res : xmlres->file.exported_symbols) {
503 r_txt_printer.Print("default int id ");
504 r_txt_printer.Println(res.name.entry);
505 }
506
507 // And print ourselves.
508 r_txt_printer.Print("default int ");
509 r_txt_printer.Print(path_data.resource_dir);
510 r_txt_printer.Print(" ");
511 r_txt_printer.Println(path_data.name);
512 }
513
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700514 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700515}
516
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700517static bool CompilePng(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700518 const ResourcePathData& path_data, IArchiveWriter* writer,
519 const std::string& output_path) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700520 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700521 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700522 }
523
524 BigBuffer buffer(4096);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700525 ResourceFile res_file;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700526 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700527 res_file.config = path_data.config;
528 res_file.source = path_data.source;
Adam Lesinski00451162017-10-03 07:44:08 -0700529 res_file.type = ResourceFile::Type::kPng;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700530
531 {
532 std::string content;
Adam Lesinski2354b562017-05-26 16:31:38 -0700533 if (!android::base::ReadFileToString(path_data.source.path, &content,
534 true /*follow_symlinks*/)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700535 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700536 << "failed to open file: "
Adam Lesinski00451162017-10-03 07:44:08 -0700537 << SystemErrorCodeToString(errno));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700538 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700539 }
540
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700541 BigBuffer crunched_png_buffer(4096);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700542 io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700543
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700544 // Ensure that we only keep the chunks we care about if we end up
545 // using the original PNG instead of the crunched one.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700546 PngChunkFilter png_chunk_filter(content);
Adam Lesinskicc73e992017-05-12 18:16:44 -0700547 std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700548 if (!image) {
549 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700550 }
551
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700552 std::unique_ptr<NinePatch> nine_patch;
553 if (path_data.extension == "9.png") {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700554 std::string err;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700555 nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700556 if (!nine_patch) {
557 context->GetDiagnostics()->Error(DiagMessage() << err);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700558 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700559 }
560
561 // Remove the 1px border around the NinePatch.
562 // Basically the row array is shifted up by 1, and the length is treated
563 // as height - 2.
564 // For each row, shift the array to the left by 1, and treat the length as
565 // width - 2.
566 image->width -= 2;
567 image->height -= 2;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700568 memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700569 for (int32_t h = 0; h < image->height; h++) {
570 memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
571 }
572
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700573 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700574 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "9-patch: "
575 << *nine_patch);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700576 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700577 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700578
579 // Write the crunched PNG.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700580 if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700581 return false;
582 }
583
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700584 if (nine_patch != nullptr ||
585 crunched_png_buffer_out.ByteCount() <= png_chunk_filter.ByteCount()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700586 // No matter what, we must use the re-encoded PNG, even if it is larger.
587 // 9-patch images must be re-encoded since their borders are stripped.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700588 buffer.AppendBuffer(std::move(crunched_png_buffer));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700589 } else {
590 // The re-encoded PNG is larger than the original, and there is
591 // no mandatory transformation. Use the original.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700592 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700593 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
594 << "original PNG is smaller than crunched PNG"
595 << ", using original");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700596 }
597
Adam Lesinski06460ef2017-03-14 18:52:13 -0700598 png_chunk_filter.Rewind();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700599 BigBuffer filtered_png_buffer(4096);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700600 io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
601 io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700602 buffer.AppendBuffer(std::move(filtered_png_buffer));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700603 }
604
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700605 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700606 // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
607 // This will help catch exotic cases where the new code may generate larger PNGs.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700608 std::stringstream legacy_stream(content);
609 BigBuffer legacy_buffer(4096);
610 Png png(context->GetDiagnostics());
611 if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700612 return false;
613 }
614
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700615 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
616 << "legacy=" << legacy_buffer.size()
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700617 << " new=" << buffer.size());
618 }
619 }
620
Adam Lesinski00451162017-10-03 07:44:08 -0700621 io::BigBufferInputStream buffer_in(&buffer);
622 if (!WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
623 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700624 return false;
625 }
626 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700627}
628
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700629static bool CompileFile(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700630 const ResourcePathData& path_data, IArchiveWriter* writer,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700631 const std::string& output_path) {
632 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700633 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700634 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700635
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700636 BigBuffer buffer(256);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700637 ResourceFile res_file;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700638 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700639 res_file.config = path_data.config;
640 res_file.source = path_data.source;
Adam Lesinski00451162017-10-03 07:44:08 -0700641 res_file.type = ResourceFile::Type::kUnknown;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700642
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700643 std::string error_str;
644 Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700645 if (!f) {
Adam Lesinski776aa952017-04-24 15:09:32 -0700646 context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to mmap file: "
647 << error_str);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700648 return false;
649 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700650
Adam Lesinski00451162017-10-03 07:44:08 -0700651 io::MmappedData mmapped_in(std::move(f.value()));
652 if (!WriteHeaderAndDataToWriter(output_path, res_file, &mmapped_in, writer,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700653 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700654 return false;
655 }
656 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700657}
658
659class CompileContext : public IAaptContext {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700660 public:
Chris Warrington820d72a2017-04-27 15:27:01 +0100661 CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
662 }
663
Adam Lesinskib522f042017-04-21 16:57:59 -0700664 PackageType GetPackageType() override {
665 // Every compilation unit starts as an app and then gets linked as potentially something else.
666 return PackageType::kApp;
667 }
668
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700669 void SetVerbose(bool val) {
670 verbose_ = val;
671 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800672
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700673 bool IsVerbose() override {
674 return verbose_;
675 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800676
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700677 IDiagnostics* GetDiagnostics() override {
Chris Warrington820d72a2017-04-27 15:27:01 +0100678 return diagnostics_;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700679 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700680
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700681 NameMangler* GetNameMangler() override {
Adam Lesinski00451162017-10-03 07:44:08 -0700682 UNIMPLEMENTED(FATAL) << "No name mangling should be needed in compile phase";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700683 return nullptr;
684 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700685
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700686 const std::string& GetCompilationPackage() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700687 static std::string empty;
688 return empty;
689 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700690
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700691 uint8_t GetPackageId() override {
692 return 0x0;
693 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700694
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700695 SymbolTable* GetExternalSymbols() override {
Adam Lesinski00451162017-10-03 07:44:08 -0700696 UNIMPLEMENTED(FATAL) << "No symbols should be needed in compile phase";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700697 return nullptr;
698 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800699
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700700 int GetMinSdkVersion() override {
701 return 0;
702 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700703
Chris Warrington481f0272018-02-06 14:03:39 +0000704 bool IsAutoNamespace() override {
705 return false;
706 }
707
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700708 private:
Adam Lesinski00451162017-10-03 07:44:08 -0700709 DISALLOW_COPY_AND_ASSIGN(CompileContext);
710
Chris Warrington820d72a2017-04-27 15:27:01 +0100711 IDiagnostics* diagnostics_;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700712 bool verbose_ = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700713};
714
Adam Lesinski00451162017-10-03 07:44:08 -0700715// Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
Chris Warrington820d72a2017-04-27 15:27:01 +0100716int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
717 CompileContext context(diagnostics);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700718 CompileOptions options;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700719
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700720 bool verbose = false;
Izabela Orlowskac7ac3a12018-03-27 14:46:52 +0100721 Maybe<std::string> visibility;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700722 Flags flags =
723 Flags()
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700724 .RequiredFlag("-o", "Output path", &options.output_path)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700725 .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
Izabela Orlowskac81d9f32017-12-05 12:07:28 +0000726 .OptionalFlag("--output-text-symbols",
727 "Generates a text file containing the resource symbols in the\n"
728 "specified file",
729 &options.generate_text_symbols_path)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700730 .OptionalSwitch("--pseudo-localize",
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700731 "Generate resources for pseudo-locales "
732 "(en-XA and ar-XB)",
733 &options.pseudolocalize)
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700734 .OptionalSwitch("--no-crunch", "Disables PNG processing", &options.no_png_crunch)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700735 .OptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
736 &options.legacy_mode)
Izabela Orlowskac7ac3a12018-03-27 14:46:52 +0100737 .OptionalSwitch("-v", "Enables verbose logging", &verbose)
738 .OptionalFlag("--visibility",
739 "Sets the visibility of the compiled resources to the specified\n"
740 "level. Accepted levels: public, private, default",
741 &visibility);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700742 if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700743 return 1;
744 }
745
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700746 context.SetVerbose(verbose);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700747
Izabela Orlowskac7ac3a12018-03-27 14:46:52 +0100748 if (visibility) {
749 if (visibility.value() == "public") {
750 options.visibility = Visibility::Level::kPublic;
751 } else if (visibility.value() == "private") {
752 options.visibility = Visibility::Level::kPrivate;
753 } else if (visibility.value() == "default") {
754 options.visibility = Visibility::Level::kUndefined;
755 } else {
756 context.GetDiagnostics()->Error(
757 DiagMessage() << "Unrecognized visibility level passes to --visibility: '"
758 << visibility.value() << "'. Accepted levels: public, private, default");
759 return 1;
760 }
761 }
762
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700763 std::unique_ptr<IArchiveWriter> archive_writer;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700764
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700765 std::vector<ResourcePathData> input_data;
766 if (options.res_dir) {
767 if (!flags.GetArgs().empty()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700768 // Can't have both files and a resource directory.
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700769 context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700770 flags.Usage("aapt2 compile", &std::cerr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700771 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700772 }
773
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700774 if (!LoadInputFilesFromDir(&context, options, &input_data)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700775 return 1;
776 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800777
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700778 archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options.output_path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700779
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700780 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700781 input_data.reserve(flags.GetArgs().size());
Adam Lesinskia40e9722015-11-24 19:11:46 -0800782
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700783 // Collect data from the path for each input file.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700784 for (const std::string& arg : flags.GetArgs()) {
785 std::string error_str;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700786 if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700787 input_data.push_back(std::move(path_data.value()));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700788 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700789 context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" << arg << ")");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700790 return 1;
791 }
792 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800793
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700794 archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options.output_path);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700795 }
796
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700797 if (!archive_writer) {
Adam Lesinskidfaecaf2016-10-20 17:08:51 -0700798 return 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700799 }
800
801 bool error = false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700802 for (ResourcePathData& path_data : input_data) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700803 if (options.verbose) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700804 context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700805 }
806
Adam Lesinski776aa952017-04-24 15:09:32 -0700807 if (!IsValidFile(&context, path_data.source.path)) {
808 error = true;
809 continue;
810 }
811
Adam Lesinski00451162017-10-03 07:44:08 -0700812 // Determine how to compile the file based on its type.
813 auto compile_func = &CompileFile;
Adam Lesinski3a725dc2017-11-02 16:14:59 -0700814 if (path_data.resource_dir == "values" && path_data.extension == "xml") {
Adam Lesinski00451162017-10-03 07:44:08 -0700815 compile_func = &CompileTable;
816 // We use a different extension (not necessary anymore, but avoids altering the existing
817 // build system logic).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700818 path_data.extension = "arsc";
yd6b83292018-04-11 09:54:56 -0700819
Adam Lesinski00451162017-10-03 07:44:08 -0700820 } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
821 if (*type != ResourceType::kRaw) {
822 if (path_data.extension == "xml") {
823 compile_func = &CompileXml;
yd6b83292018-04-11 09:54:56 -0700824 } else if ((!options.no_png_crunch && path_data.extension == "png")
825 || path_data.extension == "9.png") {
Adam Lesinski00451162017-10-03 07:44:08 -0700826 compile_func = &CompilePng;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700827 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700828 }
Adam Lesinski00451162017-10-03 07:44:08 -0700829 } else {
830 context.GetDiagnostics()->Error(DiagMessage()
831 << "invalid file path '" << path_data.source << "'");
832 error = true;
833 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700834 }
835
yd6b83292018-04-11 09:54:56 -0700836 // Treat periods as a reserved character that should not be present in a file name
837 // Legacy support for AAPT which did not reserve periods
838 if (compile_func != &CompileFile && !options.legacy_mode
839 && std::count(path_data.name.begin(), path_data.name.end(), '.') != 0) {
840 error = true;
841 context.GetDiagnostics()->Error(DiagMessage() << "resource file '" << path_data.source.path
842 << "' name cannot contain '.' other than for"
843 << "specifying the extension");
844 continue;
845 }
846
Adam Lesinski00451162017-10-03 07:44:08 -0700847 // Compile the file.
848 const std::string out_path = BuildIntermediateContainerFilename(path_data);
849 error |= !compile_func(&context, options, path_data, archive_writer.get(), out_path);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700850 }
Adam Lesinski00451162017-10-03 07:44:08 -0700851 return error ? 1 : 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700852}
853
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700854} // namespace aapt