blob: eabeb47fccec4ee843cf8a9be8ee40e7d0f3c169 [file] [log] [blame]
Shane Farmer74cdea32017-05-12 16:22:36 -07001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "configuration/ConfigurationParser.h"
18
19#include <algorithm>
20#include <functional>
Shane Farmer57669432017-06-19 12:52:04 -070021#include <map>
Shane Farmer74cdea32017-05-12 16:22:36 -070022#include <memory>
23#include <utility>
24
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070025#include "android-base/file.h"
26#include "android-base/logging.h"
Shane Farmer74cdea32017-05-12 16:22:36 -070027
28#include "ConfigDescription.h"
29#include "Diagnostics.h"
Shane Farmer3edd4722017-09-01 14:34:22 -070030#include "ResourceUtils.h"
Shane Farmercb6c3f92017-11-27 13:19:36 -080031#include "configuration/ConfigurationParser.internal.h"
Shane Farmerb1027272017-06-14 09:10:28 -070032#include "io/File.h"
33#include "io/FileSystem.h"
Adam Lesinski00451162017-10-03 07:44:08 -070034#include "io/StringStream.h"
Shane Farmer9ecc0752017-08-24 15:55:36 -070035#include "util/Files.h"
Shane Farmer9f0e7f12017-06-22 12:26:44 -070036#include "util/Maybe.h"
Shane Farmer74cdea32017-05-12 16:22:36 -070037#include "util/Util.h"
38#include "xml/XmlActionExecutor.h"
39#include "xml/XmlDom.h"
40#include "xml/XmlUtil.h"
41
42namespace aapt {
43
44namespace {
45
46using ::aapt::configuration::Abi;
47using ::aapt::configuration::AndroidManifest;
48using ::aapt::configuration::AndroidSdk;
Shane Farmercb6c3f92017-11-27 13:19:36 -080049using ::aapt::configuration::ConfiguredArtifact;
50using ::aapt::configuration::DeviceFeature;
51using ::aapt::configuration::Entry;
Shane Farmer78c43d72017-12-04 09:08:38 -080052using ::aapt::configuration::ExtractConfiguration;
Shane Farmer74cdea32017-05-12 16:22:36 -070053using ::aapt::configuration::GlTexture;
54using ::aapt::configuration::Group;
55using ::aapt::configuration::Locale;
Shane Farmer78c43d72017-12-04 09:08:38 -080056using ::aapt::configuration::OrderedEntry;
Shane Farmercb6c3f92017-11-27 13:19:36 -080057using ::aapt::configuration::OutputArtifact;
58using ::aapt::configuration::PostProcessingConfiguration;
59using ::aapt::configuration::handler::AbiGroupTagHandler;
Shane Farmer78c43d72017-12-04 09:08:38 -080060using ::aapt::configuration::handler::AndroidSdkTagHandler;
Shane Farmercb6c3f92017-11-27 13:19:36 -080061using ::aapt::configuration::handler::ArtifactFormatTagHandler;
62using ::aapt::configuration::handler::ArtifactTagHandler;
63using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
64using ::aapt::configuration::handler::GlTextureGroupTagHandler;
65using ::aapt::configuration::handler::LocaleGroupTagHandler;
66using ::aapt::configuration::handler::ScreenDensityGroupTagHandler;
Shane Farmerb1027272017-06-14 09:10:28 -070067using ::aapt::io::IFile;
68using ::aapt::io::RegularFile;
Adam Lesinskiefeb7af2017-08-02 14:57:43 -070069using ::aapt::io::StringInputStream;
Shane Farmer74cdea32017-05-12 16:22:36 -070070using ::aapt::util::TrimWhitespace;
71using ::aapt::xml::Element;
Shane Farmer74cdea32017-05-12 16:22:36 -070072using ::aapt::xml::NodeCast;
73using ::aapt::xml::XmlActionExecutor;
74using ::aapt::xml::XmlActionExecutorPolicy;
75using ::aapt::xml::XmlNodeAction;
Shane Farmer0a5b2012017-06-22 12:24:12 -070076using ::android::StringPiece;
Shane Farmercb6c3f92017-11-27 13:19:36 -080077using ::android::base::ReadFileToString;
Shane Farmer74cdea32017-05-12 16:22:36 -070078
Shane Farmercb6c3f92017-11-27 13:19:36 -080079const std::unordered_map<StringPiece, Abi> kStringToAbiMap = {
Shane Farmer57669432017-06-19 12:52:04 -070080 {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a},
81 {"x86", Abi::kX86}, {"x86_64", Abi::kX86_64}, {"mips", Abi::kMips},
82 {"mips64", Abi::kMips64}, {"universal", Abi::kUniversal},
83};
Shane Farmercb6c3f92017-11-27 13:19:36 -080084const std::array<StringPiece, 8> kAbiToStringMap = {
85 {"armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64", "universal"}};
Shane Farmer74cdea32017-05-12 16:22:36 -070086
87constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
88
89/** A default noop diagnostics context. */
90class NoopDiagnostics : public IDiagnostics {
91 public:
92 void Log(Level level, DiagMessageActual& actualMsg) override {}
93};
94NoopDiagnostics noop_;
95
96std::string GetLabel(const Element* element, IDiagnostics* diag) {
97 std::string label;
98 for (const auto& attr : element->attributes) {
99 if (attr.name == "label") {
100 label = attr.value;
101 break;
102 }
103 }
104
105 if (label.empty()) {
106 diag->Error(DiagMessage() << "No label found for element " << element->name);
107 }
108 return label;
109}
110
111/** XML node visitor that removes all of the namespace URIs from the node and all children. */
112class NamespaceVisitor : public xml::Visitor {
113 public:
114 void Visit(xml::Element* node) override {
115 node->namespace_uri.clear();
116 VisitChildren(node);
117 }
118};
119
Shane Farmercb6c3f92017-11-27 13:19:36 -0800120/** Copies the values referenced in a configuration group to the target list. */
121template <typename T>
122bool CopyXmlReferences(const Maybe<std::string>& name, const Group<T>& groups,
123 std::vector<T>* target) {
124 // If there was no item configured, there is nothing to do and no error.
125 if (!name) {
126 return true;
127 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700128
Shane Farmercb6c3f92017-11-27 13:19:36 -0800129 // If the group could not be found, then something is wrong.
130 auto group = groups.find(name.value());
131 if (group == groups.end()) {
132 return false;
133 }
Shane Farmerb1027272017-06-14 09:10:28 -0700134
Shane Farmer78c43d72017-12-04 09:08:38 -0800135 for (const T& item : group->second.entry) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800136 target->push_back(item);
137 }
138 return true;
Shane Farmer57669432017-06-19 12:52:04 -0700139}
140
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700141/**
142 * Attempts to replace the placeholder in the name string with the provided value. Returns true on
143 * success, or false if the either the placeholder is not found in the name, or the value is not
144 * present and the placeholder was.
145 */
Shane Farmercb6c3f92017-11-27 13:19:36 -0800146bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value,
147 std::string* name, IDiagnostics* diag) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700148 size_t offset = name->find(placeholder.data());
Shane Farmer1a21b8c2017-07-21 09:42:42 -0700149 bool found = (offset != std::string::npos);
150
151 // Make sure the placeholder was present if the desired value is present.
152 if (!found) {
153 if (value) {
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700154 diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder);
155 return false;
156 }
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700157 return true;
158 }
159
Shane Farmer1a21b8c2017-07-21 09:42:42 -0700160 DCHECK(found) << "Missing return path for placeholder not found";
161
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700162 // Make sure the placeholder was not present if the desired value was not present.
Shane Farmer1a21b8c2017-07-21 09:42:42 -0700163 if (!value) {
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700164 diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder);
Shane Farmer1a21b8c2017-07-21 09:42:42 -0700165 return false;
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700166 }
Shane Farmer1a21b8c2017-07-21 09:42:42 -0700167
Shane Farmer0a5b2012017-06-22 12:24:12 -0700168 name->replace(offset, placeholder.length(), value.value().data());
Shane Farmer1a21b8c2017-07-21 09:42:42 -0700169
170 // Make sure there was only one instance of the placeholder.
Shane Farmer0a5b2012017-06-22 12:24:12 -0700171 if (name->find(placeholder.data()) != std::string::npos) {
Shane Farmer1a21b8c2017-07-21 09:42:42 -0700172 diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder);
173 return false;
174 }
175 return true;
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700176}
177
Shane Farmer9ecc0752017-08-24 15:55:36 -0700178/**
Shane Farmercb6c3f92017-11-27 13:19:36 -0800179 * An ActionHandler for processing XML elements in the XmlActionExecutor. Returns true if the
180 * element was successfully processed, otherwise returns false.
181 */
182using ActionHandler = std::function<bool(configuration::PostProcessingConfiguration* config,
183 xml::Element* element, IDiagnostics* diag)>;
184
185/** Binds an ActionHandler to the current configuration being populated. */
186xml::XmlNodeAction::ActionFuncWithDiag Bind(configuration::PostProcessingConfiguration* config,
187 const ActionHandler& handler) {
188 return [config, handler](xml::Element* root_element, SourcePathDiagnostics* diag) {
189 return handler(config, root_element, diag);
190 };
191}
192
Shane Farmercb6c3f92017-11-27 13:19:36 -0800193/** Converts a ConfiguredArtifact into an OutputArtifact. */
194Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact,
195 const std::string& apk_name,
196 const PostProcessingConfiguration& config,
197 IDiagnostics* diag) {
198 if (!artifact.name && !config.artifact_format) {
199 diag->Error(
200 DiagMessage() << "Artifact does not have a name and no global name template defined");
201 return {};
202 }
203
204 Maybe<std::string> artifact_name =
205 (artifact.name) ? artifact.Name(apk_name, diag)
206 : artifact.ToArtifactName(config.artifact_format.value(), apk_name, diag);
207
208 if (!artifact_name) {
209 diag->Error(DiagMessage() << "Could not determine split APK artifact name");
210 return {};
211 }
212
213 OutputArtifact output_artifact;
214 output_artifact.name = artifact_name.value();
215
216 SourcePathDiagnostics src_diag{{output_artifact.name}, diag};
217 bool has_errors = false;
218
219 if (!CopyXmlReferences(artifact.abi_group, config.abi_groups, &output_artifact.abis)) {
220 src_diag.Error(DiagMessage() << "Could not lookup required ABIs: "
221 << artifact.abi_group.value());
222 has_errors = true;
223 }
224
225 if (!CopyXmlReferences(artifact.locale_group, config.locale_groups, &output_artifact.locales)) {
226 src_diag.Error(DiagMessage() << "Could not lookup required locales: "
227 << artifact.locale_group.value());
228 has_errors = true;
229 }
230
231 if (!CopyXmlReferences(artifact.screen_density_group, config.screen_density_groups,
232 &output_artifact.screen_densities)) {
233 src_diag.Error(DiagMessage() << "Could not lookup required screen densities: "
234 << artifact.screen_density_group.value());
235 has_errors = true;
236 }
237
238 if (!CopyXmlReferences(artifact.device_feature_group, config.device_feature_groups,
239 &output_artifact.features)) {
240 src_diag.Error(DiagMessage() << "Could not lookup required device features: "
241 << artifact.device_feature_group.value());
242 has_errors = true;
243 }
244
245 if (!CopyXmlReferences(artifact.gl_texture_group, config.gl_texture_groups,
246 &output_artifact.textures)) {
247 src_diag.Error(DiagMessage() << "Could not lookup required OpenGL texture formats: "
248 << artifact.gl_texture_group.value());
249 has_errors = true;
250 }
251
Shane Farmer78c43d72017-12-04 09:08:38 -0800252 if (artifact.android_sdk) {
253 auto entry = config.android_sdks.find(artifact.android_sdk.value());
254 if (entry == config.android_sdks.end()) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800255 src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: "
Shane Farmer78c43d72017-12-04 09:08:38 -0800256 << artifact.android_sdk.value());
Shane Farmercb6c3f92017-11-27 13:19:36 -0800257 has_errors = true;
258 } else {
259 output_artifact.android_sdk = {entry->second};
260 }
261 }
262
263 if (has_errors) {
264 return {};
265 }
266 return {output_artifact};
267}
268
269} // namespace
270
271namespace configuration {
272
Shane Farmer78c43d72017-12-04 09:08:38 -0800273/** Returns the binary reprasentation of the XML configuration. */
274Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
275 const std::string& config_path,
276 IDiagnostics* diag) {
277 StringInputStream in(contents);
278 std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path));
279 if (!doc) {
280 return {};
281 }
282
283 // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
284 Element* root = doc->root.get();
285 if (root == nullptr) {
286 diag->Error(DiagMessage() << "Could not find the root element in the XML document");
287 return {};
288 }
289
290 std::string& xml_ns = root->namespace_uri;
291 if (!xml_ns.empty()) {
292 if (xml_ns != kAaptXmlNs) {
293 diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
294 return {};
295 }
296
297 xml_ns.clear();
298 NamespaceVisitor visitor;
299 root->Accept(&visitor);
300 }
301
302 XmlActionExecutor executor;
303 XmlNodeAction& root_action = executor["post-process"];
304 XmlNodeAction& artifacts_action = root_action["artifacts"];
305
306 PostProcessingConfiguration config;
307
308 // Parse the artifact elements.
309 artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
310 artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
311
312 // Parse the different configuration groups.
313 root_action["abi-groups"]["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
314 root_action["screen-density-groups"]["screen-density-group"].Action(
315 Bind(&config, ScreenDensityGroupTagHandler));
316 root_action["locale-groups"]["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
317 root_action["android-sdks"]["android-sdk"].Action(Bind(&config, AndroidSdkTagHandler));
318 root_action["gl-texture-groups"]["gl-texture-group"].Action(
319 Bind(&config, GlTextureGroupTagHandler));
320 root_action["device-feature-groups"]["device-feature-group"].Action(
321 Bind(&config, DeviceFeatureGroupTagHandler));
322
323 if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
324 diag->Error(DiagMessage() << "Could not process XML document");
325 return {};
326 }
327
328 return {config};
329}
330
Shane Farmercb6c3f92017-11-27 13:19:36 -0800331const StringPiece& AbiToString(Abi abi) {
332 return kAbiToStringMap.at(static_cast<size_t>(abi));
333}
334
335/**
Shane Farmer9ecc0752017-08-24 15:55:36 -0700336 * Returns the common artifact base name from a template string.
337 */
338Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, IDiagnostics* diag) {
339 const StringPiece ext = file::GetExtension(apk_name);
340 size_t end_index = apk_name.to_string().rfind(ext.to_string());
341 const std::string base_name =
342 (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : "";
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700343
Shane Farmer9ecc0752017-08-24 15:55:36 -0700344 // Base name is optional.
345 if (result.find("${basename}") != std::string::npos) {
346 Maybe<StringPiece> maybe_base_name =
347 base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name};
348 if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) {
349 return {};
350 }
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700351 }
352
Shane Farmer0a5b2012017-06-22 12:24:12 -0700353 // Extension is optional.
354 if (result.find("${ext}") != std::string::npos) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700355 // Make sure we disregard the '.' in the extension when replacing the placeholder.
356 if (!ReplacePlaceholder("${ext}", {ext.substr(1)}, &result, diag)) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700357 return {};
358 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700359 } else {
360 // If no extension is specified, and the name template does not end in the current extension,
361 // add the existing extension.
362 if (!util::EndsWith(result, ext)) {
363 result.append(ext.to_string());
364 }
Shane Farmer0a5b2012017-06-22 12:24:12 -0700365 }
366
Shane Farmer9ecc0752017-08-24 15:55:36 -0700367 return result;
368}
369
Shane Farmercb6c3f92017-11-27 13:19:36 -0800370Maybe<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format,
371 const StringPiece& apk_name,
372 IDiagnostics* diag) const {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700373 Maybe<std::string> base = ToBaseName(format.to_string(), apk_name, diag);
374 if (!base) {
375 return {};
376 }
377 std::string result = std::move(base.value());
378
Shane Farmer0a5b2012017-06-22 12:24:12 -0700379 if (!ReplacePlaceholder("${abi}", abi_group, &result, diag)) {
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700380 return {};
381 }
382
Shane Farmer0a5b2012017-06-22 12:24:12 -0700383 if (!ReplacePlaceholder("${density}", screen_density_group, &result, diag)) {
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700384 return {};
385 }
386
Shane Farmer0a5b2012017-06-22 12:24:12 -0700387 if (!ReplacePlaceholder("${locale}", locale_group, &result, diag)) {
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700388 return {};
389 }
390
Shane Farmer78c43d72017-12-04 09:08:38 -0800391 if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) {
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700392 return {};
393 }
394
Shane Farmer0a5b2012017-06-22 12:24:12 -0700395 if (!ReplacePlaceholder("${feature}", device_feature_group, &result, diag)) {
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700396 return {};
397 }
398
Shane Farmer0a5b2012017-06-22 12:24:12 -0700399 if (!ReplacePlaceholder("${gl}", gl_texture_group, &result, diag)) {
400 return {};
401 }
402
403 return result;
404}
405
Shane Farmercb6c3f92017-11-27 13:19:36 -0800406Maybe<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, IDiagnostics* diag) const {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700407 if (!name) {
408 return {};
409 }
410
Shane Farmer9ecc0752017-08-24 15:55:36 -0700411 return ToBaseName(name.value(), apk_name, diag);
412}
Shane Farmer0a5b2012017-06-22 12:24:12 -0700413
Shane Farmer57669432017-06-19 12:52:04 -0700414} // namespace configuration
Shane Farmerb1027272017-06-14 09:10:28 -0700415
416/** Returns a ConfigurationParser for the file located at the provided path. */
417Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) {
418 std::string contents;
419 if (!ReadFileToString(path, &contents, true)) {
420 return {};
421 }
Shane Farmer78c43d72017-12-04 09:08:38 -0800422 return ConfigurationParser(contents, path);
Shane Farmerb1027272017-06-14 09:10:28 -0700423}
424
Shane Farmer78c43d72017-12-04 09:08:38 -0800425ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path)
426 : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) {
Shane Farmer74cdea32017-05-12 16:22:36 -0700427}
428
Shane Farmercb6c3f92017-11-27 13:19:36 -0800429Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse(
430 const android::StringPiece& apk_path) {
Shane Farmer78c43d72017-12-04 09:08:38 -0800431 Maybe<PostProcessingConfiguration> maybe_config =
432 ExtractConfiguration(contents_, config_path_, diag_);
Shane Farmercb6c3f92017-11-27 13:19:36 -0800433 if (!maybe_config) {
Shane Farmer74cdea32017-05-12 16:22:36 -0700434 return {};
435 }
Shane Farmercb6c3f92017-11-27 13:19:36 -0800436
437 // Convert from a parsed configuration to a list of artifacts for processing.
Shane Farmer78c43d72017-12-04 09:08:38 -0800438 const std::string& apk_name = file::GetFilename(apk_path).to_string();
Shane Farmercb6c3f92017-11-27 13:19:36 -0800439 std::vector<OutputArtifact> output_artifacts;
440 bool has_errors = false;
441
Shane Farmer78c43d72017-12-04 09:08:38 -0800442 PostProcessingConfiguration& config = maybe_config.value();
443 config.SortArtifacts();
444
445 int version = 1;
446 for (const ConfiguredArtifact& artifact : config.artifacts) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800447 Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_);
448 if (!output_artifact) {
449 // Defer return an error condition so that all errors are reported.
450 has_errors = true;
451 } else {
Shane Farmer78c43d72017-12-04 09:08:38 -0800452 output_artifact.value().version = version++;
Shane Farmercb6c3f92017-11-27 13:19:36 -0800453 output_artifacts.push_back(std::move(output_artifact.value()));
454 }
455 }
456
457 if (has_errors) {
458 return {};
459 }
460 return {output_artifacts};
Shane Farmer74cdea32017-05-12 16:22:36 -0700461}
462
Shane Farmercb6c3f92017-11-27 13:19:36 -0800463namespace configuration {
464namespace handler {
465
466bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element,
467 IDiagnostics* diag) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800468 ConfiguredArtifact artifact{};
Shane Farmer280be342017-06-21 15:20:15 -0700469 for (const auto& attr : root_element->attributes) {
470 if (attr.name == "name") {
471 artifact.name = attr.value;
472 } else if (attr.name == "abi-group") {
473 artifact.abi_group = {attr.value};
474 } else if (attr.name == "screen-density-group") {
475 artifact.screen_density_group = {attr.value};
476 } else if (attr.name == "locale-group") {
477 artifact.locale_group = {attr.value};
Shane Farmer78c43d72017-12-04 09:08:38 -0800478 } else if (attr.name == "android-sdk") {
479 artifact.android_sdk = {attr.value};
Shane Farmer280be342017-06-21 15:20:15 -0700480 } else if (attr.name == "gl-texture-group") {
481 artifact.gl_texture_group = {attr.value};
482 } else if (attr.name == "device-feature-group") {
483 artifact.device_feature_group = {attr.value};
484 } else {
485 diag->Note(DiagMessage() << "Unknown artifact attribute: " << attr.name << " = "
486 << attr.value);
487 }
488 }
489 config->artifacts.push_back(artifact);
490 return true;
491};
Shane Farmer74cdea32017-05-12 16:22:36 -0700492
Shane Farmercb6c3f92017-11-27 13:19:36 -0800493bool ArtifactFormatTagHandler(PostProcessingConfiguration* config, Element* root_element,
494 IDiagnostics* /* diag */) {
Shane Farmer280be342017-06-21 15:20:15 -0700495 for (auto& node : root_element->children) {
496 xml::Text* t;
497 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
498 config->artifact_format = TrimWhitespace(t->text).to_string();
499 break;
500 }
501 }
502 return true;
503};
504
Shane Farmercb6c3f92017-11-27 13:19:36 -0800505bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
506 IDiagnostics* diag) {
Shane Farmer280be342017-06-21 15:20:15 -0700507 std::string label = GetLabel(root_element, diag);
508 if (label.empty()) {
509 return false;
510 }
511
Shane Farmer78c43d72017-12-04 09:08:38 -0800512 auto& group = GetOrCreateGroup(label, &config->abi_groups);
Shane Farmer280be342017-06-21 15:20:15 -0700513 bool valid = true;
514
Shane Farmer39e474f2017-12-18 14:44:11 -0800515 // Special case for empty abi-group tag. Label will be used as the ABI.
516 if (root_element->GetChildElements().empty()) {
517 auto abi = kStringToAbiMap.find(label);
518 if (abi == kStringToAbiMap.end()) {
519 return false;
520 }
521 group.push_back(abi->second);
522 return true;
523 }
524
Shane Farmer280be342017-06-21 15:20:15 -0700525 for (auto* child : root_element->GetChildElements()) {
526 if (child->name != "abi") {
527 diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name);
528 valid = false;
529 } else {
530 for (auto& node : child->children) {
Shane Farmer74cdea32017-05-12 16:22:36 -0700531 xml::Text* t;
532 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
Shane Farmer39e474f2017-12-18 14:44:11 -0800533 auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
534 if (abi != kStringToAbiMap.end()) {
535 group.push_back(abi->second);
536 } else {
537 diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text);
538 valid = false;
539 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700540 break;
541 }
542 }
Shane Farmer280be342017-06-21 15:20:15 -0700543 }
544 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700545
Shane Farmer280be342017-06-21 15:20:15 -0700546 return valid;
547};
Shane Farmer74cdea32017-05-12 16:22:36 -0700548
Shane Farmercb6c3f92017-11-27 13:19:36 -0800549bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
550 IDiagnostics* diag) {
Shane Farmer280be342017-06-21 15:20:15 -0700551 std::string label = GetLabel(root_element, diag);
552 if (label.empty()) {
553 return false;
554 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700555
Shane Farmer78c43d72017-12-04 09:08:38 -0800556 auto& group = GetOrCreateGroup(label, &config->screen_density_groups);
Shane Farmer280be342017-06-21 15:20:15 -0700557 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700558
Shane Farmer39e474f2017-12-18 14:44:11 -0800559 // Special case for empty screen-density-group tag. Label will be used as the screen density.
560 if (root_element->GetChildElements().empty()) {
561 ConfigDescription config_descriptor;
562 bool parsed = ConfigDescription::Parse(label, &config_descriptor);
563 if (parsed &&
564 (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
565 android::ResTable_config::CONFIG_DENSITY)) {
566 // Copy the density with the minimum SDK version stripped out.
567 group.push_back(config_descriptor.CopyWithoutSdkVersion());
568 } else {
569 diag->Error(DiagMessage()
570 << "Could not parse config descriptor for empty screen-density-group: "
571 << label);
572 valid = false;
573 }
574
575 return valid;
576 }
577
Shane Farmer280be342017-06-21 15:20:15 -0700578 for (auto* child : root_element->GetChildElements()) {
579 if (child->name != "screen-density") {
580 diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
581 << child->name);
582 valid = false;
583 } else {
584 for (auto& node : child->children) {
585 xml::Text* t;
586 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
587 ConfigDescription config_descriptor;
588 const android::StringPiece& text = TrimWhitespace(t->text);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700589 bool parsed = ConfigDescription::Parse(text, &config_descriptor);
590 if (parsed &&
591 (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
592 android::ResTable_config::CONFIG_DENSITY)) {
Shane Farmer280be342017-06-21 15:20:15 -0700593 // Copy the density with the minimum SDK version stripped out.
594 group.push_back(config_descriptor.CopyWithoutSdkVersion());
595 } else {
596 diag->Error(DiagMessage()
597 << "Could not parse config descriptor for screen-density: " << text);
598 valid = false;
Shane Farmer74cdea32017-05-12 16:22:36 -0700599 }
Shane Farmer280be342017-06-21 15:20:15 -0700600 break;
Shane Farmer74cdea32017-05-12 16:22:36 -0700601 }
602 }
Shane Farmer280be342017-06-21 15:20:15 -0700603 }
604 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700605
Shane Farmer280be342017-06-21 15:20:15 -0700606 return valid;
607};
Shane Farmer74cdea32017-05-12 16:22:36 -0700608
Shane Farmercb6c3f92017-11-27 13:19:36 -0800609bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
610 IDiagnostics* diag) {
Shane Farmer280be342017-06-21 15:20:15 -0700611 std::string label = GetLabel(root_element, diag);
612 if (label.empty()) {
613 return false;
614 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700615
Shane Farmer78c43d72017-12-04 09:08:38 -0800616 auto& group = GetOrCreateGroup(label, &config->locale_groups);
Shane Farmer280be342017-06-21 15:20:15 -0700617 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700618
Shane Farmer39e474f2017-12-18 14:44:11 -0800619 // Special case to auto insert a locale for an empty group. Label will be used for locale.
620 if (root_element->GetChildElements().empty()) {
621 ConfigDescription config_descriptor;
622 bool parsed = ConfigDescription::Parse(label, &config_descriptor);
623 if (parsed &&
624 (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
625 android::ResTable_config::CONFIG_LOCALE)) {
626 // Copy the locale with the minimum SDK version stripped out.
627 group.push_back(config_descriptor.CopyWithoutSdkVersion());
628 } else {
629 diag->Error(DiagMessage()
630 << "Could not parse config descriptor for empty screen-density-group: "
631 << label);
632 valid = false;
633 }
634
635 return valid;
636 }
637
Shane Farmer280be342017-06-21 15:20:15 -0700638 for (auto* child : root_element->GetChildElements()) {
639 if (child->name != "locale") {
640 diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
641 << child->name);
642 valid = false;
643 } else {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700644 for (auto& node : child->children) {
645 xml::Text* t;
646 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
647 ConfigDescription config_descriptor;
648 const android::StringPiece& text = TrimWhitespace(t->text);
649 bool parsed = ConfigDescription::Parse(text, &config_descriptor);
650 if (parsed &&
651 (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
652 android::ResTable_config::CONFIG_LOCALE)) {
653 // Copy the locale with the minimum SDK version stripped out.
654 group.push_back(config_descriptor.CopyWithoutSdkVersion());
655 } else {
656 diag->Error(DiagMessage()
657 << "Could not parse config descriptor for screen-density: " << text);
658 valid = false;
659 }
660 break;
Shane Farmer74cdea32017-05-12 16:22:36 -0700661 }
662 }
Shane Farmer280be342017-06-21 15:20:15 -0700663 }
664 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700665
Shane Farmer280be342017-06-21 15:20:15 -0700666 return valid;
667};
Shane Farmer74cdea32017-05-12 16:22:36 -0700668
Shane Farmer78c43d72017-12-04 09:08:38 -0800669bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element,
670 IDiagnostics* diag) {
671 AndroidSdk entry = AndroidSdk::ForMinSdk(-1);
Shane Farmer280be342017-06-21 15:20:15 -0700672 bool valid = true;
Shane Farmer78c43d72017-12-04 09:08:38 -0800673 for (const auto& attr : root_element->attributes) {
674 bool valid_attr = false;
675 if (attr.name == "label") {
676 entry.label = attr.value;
677 valid_attr = true;
678 } else if (attr.name == "minSdkVersion") {
679 Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
680 if (version) {
681 valid_attr = true;
682 entry.min_sdk_version = version.value();
683 }
684 } else if (attr.name == "targetSdkVersion") {
685 Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
686 if (version) {
687 valid_attr = true;
688 entry.target_sdk_version = version;
689 }
690 } else if (attr.name == "maxSdkVersion") {
691 Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
692 if (version) {
693 valid_attr = true;
694 entry.max_sdk_version = version;
695 }
696 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700697
Shane Farmer78c43d72017-12-04 09:08:38 -0800698 if (!valid_attr) {
699 diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
Shane Farmer280be342017-06-21 15:20:15 -0700700 valid = false;
Shane Farmer280be342017-06-21 15:20:15 -0700701 }
702 }
703
Shane Farmer78c43d72017-12-04 09:08:38 -0800704 if (entry.min_sdk_version == -1) {
705 diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute");
706 valid = false;
707 }
708
709 // TODO: Fill in the manifest details when they are finalised.
710 for (auto node : root_element->GetChildElements()) {
711 if (node->name == "manifest") {
712 if (entry.manifest) {
713 diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
714 continue;
715 }
716 entry.manifest = {AndroidManifest()};
717 }
718 }
719
720 config->android_sdks[entry.label] = entry;
Shane Farmer280be342017-06-21 15:20:15 -0700721 return valid;
722};
Shane Farmer74cdea32017-05-12 16:22:36 -0700723
Shane Farmercb6c3f92017-11-27 13:19:36 -0800724bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
725 IDiagnostics* diag) {
Shane Farmer280be342017-06-21 15:20:15 -0700726 std::string label = GetLabel(root_element, diag);
727 if (label.empty()) {
728 return false;
729 }
730
Shane Farmer78c43d72017-12-04 09:08:38 -0800731 auto& group = GetOrCreateGroup(label, &config->gl_texture_groups);
Shane Farmer280be342017-06-21 15:20:15 -0700732 bool valid = true;
733
734 GlTexture result;
735 for (auto* child : root_element->GetChildElements()) {
736 if (child->name != "gl-texture") {
737 diag->Error(DiagMessage() << "Unexpected element in GL texture group: " << child->name);
738 valid = false;
739 } else {
740 for (const auto& attr : child->attributes) {
741 if (attr.name == "name") {
742 result.name = attr.value;
743 break;
744 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700745 }
746
Shane Farmer280be342017-06-21 15:20:15 -0700747 for (auto* element : child->GetChildElements()) {
748 if (element->name != "texture-path") {
749 diag->Error(DiagMessage() << "Unexpected element in gl-texture element: " << child->name);
Shane Farmer74cdea32017-05-12 16:22:36 -0700750 valid = false;
Shane Farmer280be342017-06-21 15:20:15 -0700751 continue;
752 }
753 for (auto& node : element->children) {
754 xml::Text* t;
755 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
756 result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
Shane Farmer74cdea32017-05-12 16:22:36 -0700757 }
758 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700759 }
Shane Farmer280be342017-06-21 15:20:15 -0700760 }
761 group.push_back(result);
762 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700763
Shane Farmer280be342017-06-21 15:20:15 -0700764 return valid;
765};
Shane Farmer74cdea32017-05-12 16:22:36 -0700766
Shane Farmercb6c3f92017-11-27 13:19:36 -0800767bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
768 IDiagnostics* diag) {
Shane Farmer280be342017-06-21 15:20:15 -0700769 std::string label = GetLabel(root_element, diag);
770 if (label.empty()) {
771 return false;
772 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700773
Shane Farmer78c43d72017-12-04 09:08:38 -0800774 auto& group = GetOrCreateGroup(label, &config->device_feature_groups);
Shane Farmer280be342017-06-21 15:20:15 -0700775 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700776
Shane Farmer280be342017-06-21 15:20:15 -0700777 for (auto* child : root_element->GetChildElements()) {
778 if (child->name != "supports-feature") {
779 diag->Error(DiagMessage() << "Unexpected root_element in device feature group: "
780 << child->name);
781 valid = false;
782 } else {
783 for (auto& node : child->children) {
784 xml::Text* t;
785 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
786 group.push_back(TrimWhitespace(t->text).to_string());
787 break;
Shane Farmer74cdea32017-05-12 16:22:36 -0700788 }
789 }
Shane Farmer280be342017-06-21 15:20:15 -0700790 }
791 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700792
Shane Farmer280be342017-06-21 15:20:15 -0700793 return valid;
794};
Shane Farmer74cdea32017-05-12 16:22:36 -0700795
Shane Farmercb6c3f92017-11-27 13:19:36 -0800796} // namespace handler
797} // namespace configuration
798
Shane Farmer74cdea32017-05-12 16:22:36 -0700799} // namespace aapt