blob: 8536edb34b702316980c788754b16ea20a678ab2 [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 Lesinski06460ef2017-03-14 18:52:13 -0700285 std::unique_ptr<pb::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 Lesinskid0f492d2017-04-03 18:12:45 -0700322 std::unique_ptr<pb::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 Lesinski06460ef2017-03-14 18:52:13 -0700349 std::unique_ptr<pb::CompiledFile> pb_compiled_file = SerializeCompiledFileToPb(xmlres->file);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700350 out->WriteCompiledFile(pb_compiled_file.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700351 out->WriteData(&buffer);
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700352
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700353 if (out->HadError()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700354 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700355 return false;
356 }
357 return true;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700358}
359
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700360static bool IsValidFile(IAaptContext* context, const std::string& input_path) {
Adam Lesinski776aa952017-04-24 15:09:32 -0700361 const file::FileType file_type = file::GetFileType(input_path);
362 if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) {
363 if (file_type == file::FileType::kDirectory) {
364 context->GetDiagnostics()->Error(DiagMessage(input_path)
365 << "resource file cannot be a directory");
Adam Lesinskicc73e992017-05-12 18:16:44 -0700366 } else if (file_type == file::FileType::kNonexistant) {
367 context->GetDiagnostics()->Error(DiagMessage(input_path) << "file not found");
Adam Lesinski776aa952017-04-24 15:09:32 -0700368 } else {
369 context->GetDiagnostics()->Error(DiagMessage(input_path)
370 << "not a valid resource file");
371 }
372 return false;
373 }
374 return true;
375}
376
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700377static bool CompileXml(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700378 const ResourcePathData& path_data, IArchiveWriter* writer,
379 const std::string& output_path) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700380 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700381 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700382 }
383
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700384 std::unique_ptr<xml::XmlResource> xmlres;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700385 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700386 FileInputStream fin(path_data.source.path);
387 if (fin.HadError()) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700388 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700389 << "failed to open file: " << fin.GetError());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700390 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700391 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700392
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700393 xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700394 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700395
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700396 if (!xmlres) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700397 return false;
398 }
399
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700400 xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700401 xmlres->file.config = path_data.config;
402 xmlres->file.source = path_data.source;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700403
404 // Collect IDs that are defined here.
405 XmlIdCollector collector;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700406 if (!collector.Consume(context, xmlres.get())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700407 return false;
408 }
409
410 // Look for and process any <aapt:attr> tags and create sub-documents.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700411 InlineXmlFormatParser inline_xml_format_parser;
412 if (!inline_xml_format_parser.Consume(context, xmlres.get())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700413 return false;
414 }
415
416 // Start the entry so we can write the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700417 if (!writer->StartEntry(output_path, 0)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700418 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700419 return false;
420 }
421
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700422 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700423 {
Adam Lesinskiefeb7af2017-08-02 14:57:43 -0700424 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700425 CopyingOutputStreamAdaptor copying_adaptor(writer);
426 CompiledFileOutputStream output_stream(&copying_adaptor);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700427
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700428 std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
429 inline_xml_format_parser.GetExtractedInlineXmlDocuments();
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700430
431 // Number of CompiledFiles.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700432 output_stream.WriteLittleEndian32(1 + inline_documents.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700433
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700434 if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700435 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700436 }
437
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700438 for (auto& inline_xml_doc : inline_documents) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700439 if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700440 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700441 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700442 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700443 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700444
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700445 if (!writer->FinishEntry()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700446 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700447 return false;
448 }
449 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700450}
451
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700452static bool CompilePng(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700453 const ResourcePathData& path_data, IArchiveWriter* writer,
454 const std::string& output_path) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700455 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700456 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700457 }
458
459 BigBuffer buffer(4096);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700460 ResourceFile res_file;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700461 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700462 res_file.config = path_data.config;
463 res_file.source = path_data.source;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700464
465 {
466 std::string content;
Adam Lesinski2354b562017-05-26 16:31:38 -0700467 if (!android::base::ReadFileToString(path_data.source.path, &content,
468 true /*follow_symlinks*/)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700469 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700470 << "failed to open file: "
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700471 << android::base::SystemErrorCodeToString(errno));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700472 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700473 }
474
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700475 BigBuffer crunched_png_buffer(4096);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700476 io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700477
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700478 // Ensure that we only keep the chunks we care about if we end up
479 // using the original PNG instead of the crunched one.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700480 PngChunkFilter png_chunk_filter(content);
Adam Lesinskicc73e992017-05-12 18:16:44 -0700481 std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700482 if (!image) {
483 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700484 }
485
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700486 std::unique_ptr<NinePatch> nine_patch;
487 if (path_data.extension == "9.png") {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700488 std::string err;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700489 nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700490 if (!nine_patch) {
491 context->GetDiagnostics()->Error(DiagMessage() << err);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700492 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700493 }
494
495 // Remove the 1px border around the NinePatch.
496 // Basically the row array is shifted up by 1, and the length is treated
497 // as height - 2.
498 // For each row, shift the array to the left by 1, and treat the length as
499 // width - 2.
500 image->width -= 2;
501 image->height -= 2;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700502 memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700503 for (int32_t h = 0; h < image->height; h++) {
504 memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
505 }
506
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700507 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700508 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "9-patch: "
509 << *nine_patch);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700510 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700511 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700512
513 // Write the crunched PNG.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700514 if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700515 return false;
516 }
517
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700518 if (nine_patch != nullptr ||
519 crunched_png_buffer_out.ByteCount() <= png_chunk_filter.ByteCount()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700520 // No matter what, we must use the re-encoded PNG, even if it is larger.
521 // 9-patch images must be re-encoded since their borders are stripped.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700522 buffer.AppendBuffer(std::move(crunched_png_buffer));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700523 } else {
524 // The re-encoded PNG is larger than the original, and there is
525 // no mandatory transformation. Use the original.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700526 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700527 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
528 << "original PNG is smaller than crunched PNG"
529 << ", using original");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700530 }
531
Adam Lesinski06460ef2017-03-14 18:52:13 -0700532 png_chunk_filter.Rewind();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700533 BigBuffer filtered_png_buffer(4096);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700534 io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
535 io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700536 buffer.AppendBuffer(std::move(filtered_png_buffer));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700537 }
538
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700539 if (context->IsVerbose()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700540 // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
541 // This will help catch exotic cases where the new code may generate larger PNGs.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700542 std::stringstream legacy_stream(content);
543 BigBuffer legacy_buffer(4096);
544 Png png(context->GetDiagnostics());
545 if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700546 return false;
547 }
548
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700549 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
550 << "legacy=" << legacy_buffer.size()
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700551 << " new=" << buffer.size());
552 }
553 }
554
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700555 if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer,
556 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700557 return false;
558 }
559 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700560}
561
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700562static bool CompileFile(IAaptContext* context, const CompileOptions& options,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700563 const ResourcePathData& path_data, IArchiveWriter* writer,
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700564 const std::string& output_path) {
565 if (context->IsVerbose()) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700566 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700567 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700568
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700569 BigBuffer buffer(256);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700570 ResourceFile res_file;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700571 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700572 res_file.config = path_data.config;
573 res_file.source = path_data.source;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700574
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700575 std::string error_str;
576 Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700577 if (!f) {
Adam Lesinski776aa952017-04-24 15:09:32 -0700578 context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to mmap file: "
579 << error_str);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700580 return false;
581 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700582
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700583 if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer,
584 context->GetDiagnostics())) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700585 return false;
586 }
587 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700588}
589
590class CompileContext : public IAaptContext {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700591 public:
Chris Warrington820d72a2017-04-27 15:27:01 +0100592 CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
593 }
594
Adam Lesinskib522f042017-04-21 16:57:59 -0700595 PackageType GetPackageType() override {
596 // Every compilation unit starts as an app and then gets linked as potentially something else.
597 return PackageType::kApp;
598 }
599
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700600 void SetVerbose(bool val) {
601 verbose_ = val;
602 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800603
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700604 bool IsVerbose() override {
605 return verbose_;
606 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800607
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700608 IDiagnostics* GetDiagnostics() override {
Chris Warrington820d72a2017-04-27 15:27:01 +0100609 return diagnostics_;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700610 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700611
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700612 NameMangler* GetNameMangler() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700613 abort();
614 return nullptr;
615 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700616
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700617 const std::string& GetCompilationPackage() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700618 static std::string empty;
619 return empty;
620 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700621
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700622 uint8_t GetPackageId() override {
623 return 0x0;
624 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700625
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700626 SymbolTable* GetExternalSymbols() override {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700627 abort();
628 return nullptr;
629 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800630
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700631 int GetMinSdkVersion() override {
632 return 0;
633 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700634
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700635 private:
Chris Warrington820d72a2017-04-27 15:27:01 +0100636 IDiagnostics* diagnostics_;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700637 bool verbose_ = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700638};
639
640/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700641 * Entry point for compilation phase. Parses arguments and dispatches to the
642 * correct steps.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700643 */
Chris Warrington820d72a2017-04-27 15:27:01 +0100644int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
645 CompileContext context(diagnostics);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700646 CompileOptions options;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700647
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700648 bool verbose = false;
649 Flags flags =
650 Flags()
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700651 .RequiredFlag("-o", "Output path", &options.output_path)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700652 .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700653 .OptionalSwitch("--pseudo-localize",
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700654 "Generate resources for pseudo-locales "
655 "(en-XA and ar-XB)",
656 &options.pseudolocalize)
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700657 .OptionalSwitch("--no-crunch", "Disables PNG processing", &options.no_png_crunch)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700658 .OptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
659 &options.legacy_mode)
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700660 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
661 if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700662 return 1;
663 }
664
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700665 context.SetVerbose(verbose);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700666
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700667 std::unique_ptr<IArchiveWriter> archive_writer;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700668
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700669 std::vector<ResourcePathData> input_data;
670 if (options.res_dir) {
671 if (!flags.GetArgs().empty()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700672 // Can't have both files and a resource directory.
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700673 context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700674 flags.Usage("aapt2 compile", &std::cerr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700675 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700676 }
677
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700678 if (!LoadInputFilesFromDir(&context, options, &input_data)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700679 return 1;
680 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800681
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700682 archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options.output_path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700683
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700684 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700685 input_data.reserve(flags.GetArgs().size());
Adam Lesinskia40e9722015-11-24 19:11:46 -0800686
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700687 // Collect data from the path for each input file.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700688 for (const std::string& arg : flags.GetArgs()) {
689 std::string error_str;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700690 if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700691 input_data.push_back(std::move(path_data.value()));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700692 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700693 context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" << arg << ")");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700694 return 1;
695 }
696 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800697
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700698 archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options.output_path);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700699 }
700
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700701 if (!archive_writer) {
Adam Lesinskidfaecaf2016-10-20 17:08:51 -0700702 return 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700703 }
704
705 bool error = false;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700706 for (ResourcePathData& path_data : input_data) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700707 if (options.verbose) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700708 context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700709 }
710
Adam Lesinski776aa952017-04-24 15:09:32 -0700711 if (!IsValidFile(&context, path_data.source.path)) {
712 error = true;
713 continue;
714 }
715
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700716 if (path_data.resource_dir == "values") {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700717 // Overwrite the extension.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700718 path_data.extension = "arsc";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700719
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700720 const std::string output_filename = BuildIntermediateFilename(path_data);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700721 if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700722 error = true;
723 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800724
725 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700726 const std::string output_filename = BuildIntermediateFilename(path_data);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700727 if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700728 if (*type != ResourceType::kRaw) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700729 if (path_data.extension == "xml") {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700730 if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700731 error = true;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800732 }
Adam Lesinski28e6c0b2017-05-10 14:56:36 -0700733 } else if (!options.no_png_crunch &&
734 (path_data.extension == "png" || path_data.extension == "9.png")) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700735 if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700736 error = true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700737 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700738 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700739 if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700740 error = true;
741 }
742 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700743 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700744 if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700745 error = true;
746 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700747 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700748 } else {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700749 context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source
750 << "'");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700751 error = true;
752 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700753 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700754 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700755
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700756 if (error) {
757 return 1;
758 }
759 return 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700760}
761
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700762} // namespace aapt