blob: 7f5bbf042766081fd8b92424e31de69639e6b1be [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 Lesinskia40e9722015-11-24 19:11:46 -080038#include "flatten/Archive.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070039#include "flatten/XmlFlattener.h"
Adam Lesinski06460ef2017-03-14 18:52:13 -070040#include "io/BigBufferOutputStream.h"
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070041#include "io/FileInputStream.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070042#include "io/Util.h"
Adam Lesinski59e04c62016-02-04 15:59:23 -080043#include "proto/ProtoSerialize.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 Lesinskice5e56e2016-10-21 17:56:45 -0700251 std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table);
252 if (!pb_table->SerializeToZeroCopyStream(&copying_adaptor)) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700253 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700254 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700255 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700256 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800257
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700258 if (!writer->FinishEntry()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700259 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700260 return false;
261 }
262 return true;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800263}
264
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700265static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file,
266 const BigBuffer& buffer, IArchiveWriter* writer,
Adam Lesinski59e04c62016-02-04 15:59:23 -0800267 IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700268 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700269 if (!writer->StartEntry(output_path, 0)) {
270 diag->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700271 return false;
272 }
273
274 // Make sure CopyingOutputStreamAdaptor is deleted before we call
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700275 // writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700276 {
277 // Wrap our IArchiveWriter with an adaptor that implements the
Adam Lesinski06460ef2017-03-14 18:52:13 -0700278 // ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700279 CopyingOutputStreamAdaptor copying_adaptor(writer);
280 CompiledFileOutputStream output_stream(&copying_adaptor);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700281
282 // Number of CompiledFiles.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700283 output_stream.WriteLittleEndian32(1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700284
Adam Lesinski4ffea042017-08-10 15:37:28 -0700285 std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700286 output_stream.WriteCompiledFile(compiled_file.get());
287 output_stream.WriteData(&buffer);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700288
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700289 if (output_stream.HadError()) {
290 diag->Error(DiagMessage(output_path) << "failed to write data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700291 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800292 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700293 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800294
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700295 if (!writer->FinishEntry()) {
296 diag->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700297 return false;
298 }
299 return true;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800300}
301
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700302static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file,
303 const android::FileMap& map, IArchiveWriter* writer,
Adam Lesinski59e04c62016-02-04 15:59:23 -0800304 IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700305 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700306 if (!writer->StartEntry(output_path, 0)) {
307 diag->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700308 return false;
309 }
310
311 // Make sure CopyingOutputStreamAdaptor is deleted before we call
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700312 // writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700313 {
314 // Wrap our IArchiveWriter with an adaptor that implements the
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700315 // ZeroCopyOutputStream interface.
316 CopyingOutputStreamAdaptor copying_adaptor(writer);
317 CompiledFileOutputStream output_stream(&copying_adaptor);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700318
319 // Number of CompiledFiles.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700320 output_stream.WriteLittleEndian32(1);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700321
Adam Lesinski4ffea042017-08-10 15:37:28 -0700322 std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700323 output_stream.WriteCompiledFile(compiled_file.get());
324 output_stream.WriteData(map.getDataPtr(), map.getDataLength());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700325
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700326 if (output_stream.HadError()) {
327 diag->Error(DiagMessage(output_path) << "failed to write data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700328 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800329 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700330 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800331
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700332 if (!writer->FinishEntry()) {
333 diag->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700334 return false;
335 }
336 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700337}
338
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700339static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path,
340 xml::XmlResource* xmlres, CompiledFileOutputStream* out) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700341 BigBuffer buffer(1024);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700342 XmlFlattenerOptions xml_flattener_options;
343 xml_flattener_options.keep_raw_values = true;
344 XmlFlattener flattener(&buffer, xml_flattener_options);
345 if (!flattener.Consume(context, xmlres)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700346 return false;
347 }
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700348
Adam Lesinski4ffea042017-08-10 15:37:28 -0700349 std::unique_ptr<pb::internal::CompiledFile> pb_compiled_file =
350 SerializeCompiledFileToPb(xmlres->file);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700351 out->WriteCompiledFile(pb_compiled_file.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700352 out->WriteData(&buffer);
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700353
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700354 if (out->HadError()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700355 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700356 return false;
357 }
358 return true;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700359}
360
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700361static bool IsValidFile(IAaptContext* context, const std::string& input_path) {
Adam Lesinski776aa952017-04-24 15:09:32 -0700362 const file::FileType file_type = file::GetFileType(input_path);
363 if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) {
364 if (file_type == file::FileType::kDirectory) {
365 context->GetDiagnostics()->Error(DiagMessage(input_path)
366 << "resource file cannot be a directory");
Adam Lesinskicc73e992017-05-12 18:16:44 -0700367 } else if (file_type == file::FileType::kNonexistant) {
368 context->GetDiagnostics()->Error(DiagMessage(input_path) << "file not found");
Adam Lesinski776aa952017-04-24 15:09:32 -0700369 } else {
370 context->GetDiagnostics()->Error(DiagMessage(input_path)
371 << "not a valid resource file");
372 }
373 return false;
374 }
375 return true;
376}
377
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700378static bool CompileXml(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700379 const ResourcePathData& path_data, IArchiveWriter* writer,
380 const std::string& output_path) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700381 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700382 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700383 }
384
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700385 std::unique_ptr<xml::XmlResource> xmlres;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700386 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700387 FileInputStream fin(path_data.source.path);
388 if (fin.HadError()) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700389 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700390 << "failed to open file: " << fin.GetError());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700391 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700392 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700393
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700394 xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700395 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700396
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700397 if (!xmlres) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700398 return false;
399 }
400
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700401 xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700402 xmlres->file.config = path_data.config;
403 xmlres->file.source = path_data.source;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700404
405 // Collect IDs that are defined here.
406 XmlIdCollector collector;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700407 if (!collector.Consume(context, xmlres.get())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700408 return false;
409 }
410
411 // Look for and process any <aapt:attr> tags and create sub-documents.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700412 InlineXmlFormatParser inline_xml_format_parser;
413 if (!inline_xml_format_parser.Consume(context, xmlres.get())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700414 return false;
415 }
416
417 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700418 if (!writer->StartEntry(output_path, 0)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700419 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700420 return false;
421 }
422
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700423 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700424 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700425 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700426 CopyingOutputStreamAdaptor copying_adaptor(writer);
427 CompiledFileOutputStream output_stream(&copying_adaptor);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700428
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700429 std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
430 inline_xml_format_parser.GetExtractedInlineXmlDocuments();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700431
432 // Number of CompiledFiles.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700433 output_stream.WriteLittleEndian32(1 + inline_documents.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700434
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700435 if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700436 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700437 }
438
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700439 for (auto& inline_xml_doc : inline_documents) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700440 if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700441 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700442 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700443 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700444 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700445
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700446 if (!writer->FinishEntry()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700447 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700448 return false;
449 }
450 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700451}
452
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700453static bool CompilePng(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700454 const ResourcePathData& path_data, IArchiveWriter* writer,
455 const std::string& output_path) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700456 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700457 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700458 }
459
460 BigBuffer buffer(4096);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700461 ResourceFile res_file;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700462 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700463 res_file.config = path_data.config;
464 res_file.source = path_data.source;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700465
466 {
467 std::string content;
Adam Lesinski2354b562017-05-26 16:31:38 -0700468 if (!android::base::ReadFileToString(path_data.source.path, &content,
469 true /*follow_symlinks*/)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700470 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700471 << "failed to open file: "
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700472 << android::base::SystemErrorCodeToString(errno));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700473 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700474 }
475
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700476 BigBuffer crunched_png_buffer(4096);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700477 io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700478
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700479 // Ensure that we only keep the chunks we care about if we end up
480 // using the original PNG instead of the crunched one.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700481 PngChunkFilter png_chunk_filter(content);
Adam Lesinskicc73e992017-05-12 18:16:44 -0700482 std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700483 if (!image) {
484 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700485 }
486
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700487 std::unique_ptr<NinePatch> nine_patch;
488 if (path_data.extension == "9.png") {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700489 std::string err;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700490 nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700491 if (!nine_patch) {
492 context->GetDiagnostics()->Error(DiagMessage() << err);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700493 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700494 }
495
496 // Remove the 1px border around the NinePatch.
497 // Basically the row array is shifted up by 1, and the length is treated
498 // as height - 2.
499 // For each row, shift the array to the left by 1, and treat the length as
500 // width - 2.
501 image->width -= 2;
502 image->height -= 2;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700503 memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700504 for (int32_t h = 0; h < image->height; h++) {
505 memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
506 }
507
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700508 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700509 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "9-patch: "
510 << *nine_patch);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700511 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700512 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700513
514 // Write the crunched PNG.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700515 if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700516 return false;
517 }
518
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700519 if (nine_patch != nullptr ||
520 crunched_png_buffer_out.ByteCount() <= png_chunk_filter.ByteCount()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700521 // No matter what, we must use the re-encoded PNG, even if it is larger.
522 // 9-patch images must be re-encoded since their borders are stripped.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700523 buffer.AppendBuffer(std::move(crunched_png_buffer));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700524 } else {
525 // The re-encoded PNG is larger than the original, and there is
526 // no mandatory transformation. Use the original.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700527 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700528 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
529 << "original PNG is smaller than crunched PNG"
530 << ", using original");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700531 }
532
Adam Lesinski06460ef2017-03-14 18:52:13 -0700533 png_chunk_filter.Rewind();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700534 BigBuffer filtered_png_buffer(4096);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700535 io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
536 io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700537 buffer.AppendBuffer(std::move(filtered_png_buffer));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700538 }
539
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700540 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700541 // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
542 // This will help catch exotic cases where the new code may generate larger PNGs.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700543 std::stringstream legacy_stream(content);
544 BigBuffer legacy_buffer(4096);
545 Png png(context->GetDiagnostics());
546 if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700547 return false;
548 }
549
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700550 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
551 << "legacy=" << legacy_buffer.size()
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700552 << " new=" << buffer.size());
553 }
554 }
555
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700556 if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer,
557 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700558 return false;
559 }
560 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700561}
562
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700563static bool CompileFile(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700564 const ResourcePathData& path_data, IArchiveWriter* writer,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700565 const std::string& output_path) {
566 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700567 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700568 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700569
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700570 BigBuffer buffer(256);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700571 ResourceFile res_file;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700572 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700573 res_file.config = path_data.config;
574 res_file.source = path_data.source;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700575
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700576 std::string error_str;
577 Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700578 if (!f) {
Adam Lesinski776aa952017-04-24 15:09:32 -0700579 context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to mmap file: "
580 << error_str);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700581 return false;
582 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700583
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700584 if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer,
585 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700586 return false;
587 }
588 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700589}
590
591class CompileContext : public IAaptContext {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700592 public:
Chris Warrington820d72a2017-04-27 15:27:01 +0100593 CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
594 }
595
Adam Lesinskib522f042017-04-21 16:57:59 -0700596 PackageType GetPackageType() override {
597 // Every compilation unit starts as an app and then gets linked as potentially something else.
598 return PackageType::kApp;
599 }
600
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700601 void SetVerbose(bool val) {
602 verbose_ = val;
603 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800604
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700605 bool IsVerbose() override {
606 return verbose_;
607 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800608
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700609 IDiagnostics* GetDiagnostics() override {
Chris Warrington820d72a2017-04-27 15:27:01 +0100610 return diagnostics_;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700611 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700612
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700613 NameMangler* GetNameMangler() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700614 abort();
615 return nullptr;
616 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700617
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700618 const std::string& GetCompilationPackage() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700619 static std::string empty;
620 return empty;
621 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700622
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700623 uint8_t GetPackageId() override {
624 return 0x0;
625 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700626
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700627 SymbolTable* GetExternalSymbols() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700628 abort();
629 return nullptr;
630 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800631
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700632 int GetMinSdkVersion() override {
633 return 0;
634 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700635
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700636 private:
Chris Warrington820d72a2017-04-27 15:27:01 +0100637 IDiagnostics* diagnostics_;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700638 bool verbose_ = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700639};
640
641/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700642 * Entry point for compilation phase. Parses arguments and dispatches to the
643 * correct steps.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700644 */
Chris Warrington820d72a2017-04-27 15:27:01 +0100645int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
646 CompileContext context(diagnostics);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700647 CompileOptions options;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700648
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700649 bool verbose = false;
650 Flags flags =
651 Flags()
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700652 .RequiredFlag("-o", "Output path", &options.output_path)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700653 .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700654 .OptionalSwitch("--pseudo-localize",
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700655 "Generate resources for pseudo-locales "
656 "(en-XA and ar-XB)",
657 &options.pseudolocalize)
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700658 .OptionalSwitch("--no-crunch", "Disables PNG processing", &options.no_png_crunch)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700659 .OptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
660 &options.legacy_mode)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700661 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
662 if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700663 return 1;
664 }
665
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700666 context.SetVerbose(verbose);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700667
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700668 std::unique_ptr<IArchiveWriter> archive_writer;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700669
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700670 std::vector<ResourcePathData> input_data;
671 if (options.res_dir) {
672 if (!flags.GetArgs().empty()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700673 // Can't have both files and a resource directory.
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700674 context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700675 flags.Usage("aapt2 compile", &std::cerr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700676 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700677 }
678
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700679 if (!LoadInputFilesFromDir(&context, options, &input_data)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700680 return 1;
681 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800682
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700683 archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options.output_path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700684
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700685 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700686 input_data.reserve(flags.GetArgs().size());
Adam Lesinskia40e9722015-11-24 19:11:46 -0800687
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700688 // Collect data from the path for each input file.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700689 for (const std::string& arg : flags.GetArgs()) {
690 std::string error_str;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700691 if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700692 input_data.push_back(std::move(path_data.value()));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700693 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700694 context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" << arg << ")");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700695 return 1;
696 }
697 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800698
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700699 archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options.output_path);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700700 }
701
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700702 if (!archive_writer) {
Adam Lesinskidfaecaf2016-10-20 17:08:51 -0700703 return 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700704 }
705
706 bool error = false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700707 for (ResourcePathData& path_data : input_data) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700708 if (options.verbose) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700709 context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700710 }
711
Adam Lesinski776aa952017-04-24 15:09:32 -0700712 if (!IsValidFile(&context, path_data.source.path)) {
713 error = true;
714 continue;
715 }
716
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700717 if (path_data.resource_dir == "values") {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700718 // Overwrite the extension.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700719 path_data.extension = "arsc";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700720
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700721 const std::string output_filename = BuildIntermediateFilename(path_data);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700722 if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700723 error = true;
724 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800725
726 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700727 const std::string output_filename = BuildIntermediateFilename(path_data);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700728 if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700729 if (*type != ResourceType::kRaw) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700730 if (path_data.extension == "xml") {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700731 if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700732 error = true;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800733 }
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700734 } else if (!options.no_png_crunch &&
735 (path_data.extension == "png" || path_data.extension == "9.png")) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700736 if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700737 error = true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700738 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700739 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700740 if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700741 error = true;
742 }
743 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700744 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700745 if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700746 error = true;
747 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700748 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700749 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700750 context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source
751 << "'");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700752 error = true;
753 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700754 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700755 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700756
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700757 if (error) {
758 return 1;
759 }
760 return 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700761}
762
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700763} // namespace aapt