diff options
author | 2018-12-13 16:56:07 -0800 | |
---|---|---|
committer | 2018-12-19 14:07:43 -0800 | |
commit | 2c8fc8669477c8f075215e4a076a4c7c150ca862 (patch) | |
tree | f5b06cb76fc89b91fe41fad338ac7906c5f500ed | |
parent | 802c62150985110df7718e874e7b0a0f89dc4d8f (diff) |
Fix long file paths for Windows
Allows specifying which arguments into aapt2 are file paths.
If the path exceeds the maximum path length for Windows, encode the path using
the extended-length prefix.
Bug: 120853927
Test: aapt2_tests.exe
Change-Id: Ibe5c1cc79a70c33c302157f2ffd54525439a910f
-rw-r--r-- | tools/aapt2/cmd/Command.cpp | 105 | ||||
-rw-r--r-- | tools/aapt2/cmd/Command.h | 82 | ||||
-rw-r--r-- | tools/aapt2/cmd/Command_test.cpp | 97 | ||||
-rw-r--r-- | tools/aapt2/cmd/Compile.h | 13 | ||||
-rw-r--r-- | tools/aapt2/cmd/Convert.h | 2 | ||||
-rw-r--r-- | tools/aapt2/cmd/Link.h | 18 | ||||
-rw-r--r-- | tools/aapt2/cmd/Optimize.h | 7 |
7 files changed, 236 insertions, 88 deletions
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp index bdee5c9d4909..4424a357c1dd 100644 --- a/tools/aapt2/cmd/Command.cpp +++ b/tools/aapt2/cmd/Command.cpp @@ -21,80 +21,97 @@ #include <string> #include <vector> +#include "android-base/stringprintf.h" +#include "android-base/utf8.h" #include "androidfw/StringPiece.h" #include "util/Util.h" +using android::base::StringPrintf; using android::StringPiece; namespace aapt { -void Command::AddRequiredFlag(const StringPiece& name, - const StringPiece& description, std::string* value) { - auto func = [value](const StringPiece& arg) -> bool { - *value = arg.to_string(); +std::string GetSafePath(const StringPiece& arg) { +#ifdef _WIN32 + // If the path exceeds the maximum path length for Windows, encode the path using the + // extended-length prefix + std::wstring path16; + CHECK(android::base::UTF8PathToWindowsLongPath(arg.data(), &path16)) + << "Failed to convert file path to UTF-16: file path " << arg.data(); + + std::string path8; + CHECK(android::base::WideToUTF8(path16, &path8)) + << "Failed to convert file path back to UTF-8: file path " << arg.data(); + + return path8; +#else + return arg.to_string(); +#endif +} + +void Command::AddRequiredFlag(const StringPiece& name, const StringPiece& description, + std::string* value, uint32_t flags) { + auto func = [value, flags](const StringPiece& arg) -> bool { + *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string(); return true; }; - flags_.push_back(Flag{name.to_string(), description.to_string(), func, true, 1, false}); + flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); } -void Command::AddRequiredFlagList(const StringPiece& name, - const StringPiece& description, - std::vector<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->push_back(arg.to_string()); +void Command::AddRequiredFlagList(const StringPiece& name, const StringPiece& description, + std::vector<std::string>* value, uint32_t flags) { + auto func = [value, flags](const StringPiece& arg) -> bool { + value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string()); return true; }; - flags_.push_back(Flag{name.to_string(), description.to_string(), func, true, 1, false}); + flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); } -void Command::AddOptionalFlag(const StringPiece& name, - const StringPiece& description, - Maybe<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - *value = arg.to_string(); +void Command::AddOptionalFlag(const StringPiece& name, const StringPiece& description, + Maybe<std::string>* value, uint32_t flags) { + auto func = [value, flags](const StringPiece& arg) -> bool { + *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string(); return true; }; - flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false}); + flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalFlagList(const StringPiece& name, - const StringPiece& description, - std::vector<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->push_back(arg.to_string()); +void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description, + std::vector<std::string>* value, uint32_t flags) { + auto func = [value, flags](const StringPiece& arg) -> bool { + value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string()); return true; }; - flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false}); + flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalFlagList(const StringPiece& name, - const StringPiece& description, - std::unordered_set<std::string>* value) { +void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description, + std::unordered_set<std::string>* value) { auto func = [value](const StringPiece& arg) -> bool { value->insert(arg.to_string()); return true; }; - flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false}); + flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalSwitch(const StringPiece& name, - const StringPiece& description, bool* value) { +void Command::AddOptionalSwitch(const StringPiece& name, const StringPiece& description, + bool* value) { auto func = [value](const StringPiece& arg) -> bool { *value = true; return true; }; - flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 0, false}); + flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 0, func)); } void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental) { - subcommand->fullname_ = name_ + " " + subcommand->name_; + subcommand->full_subcommand_name_ = StringPrintf("%s %s", name_.data(), subcommand->name_.data()); if (experimental) { experimental_subcommands_.push_back(std::move(subcommand)); } else { @@ -102,14 +119,14 @@ void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool } } -void Command::SetDescription(const android::StringPiece& description) { +void Command::SetDescription(const StringPiece& description) { description_ = description.to_string(); } void Command::Usage(std::ostream* out) { constexpr size_t kWidth = 50; - *out << fullname_; + *out << full_subcommand_name_; if (!subcommands_.empty()) { *out << " [subcommand]"; @@ -117,7 +134,7 @@ void Command::Usage(std::ostream* out) { *out << " [options]"; for (const Flag& flag : flags_) { - if (flag.required) { + if (flag.is_required) { *out << " " << flag.name << " arg"; } } @@ -160,29 +177,31 @@ void Command::Usage(std::ostream* out) { out->flush(); } -int Command::Execute(const std::vector<android::StringPiece>& args, std::ostream* out_error) { +int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_error) { std::vector<std::string> file_args; for (size_t i = 0; i < args.size(); i++) { - StringPiece arg = args[i]; + const StringPiece& arg = args[i]; if (*(arg.data()) != '-') { // Continue parsing as the subcommand if the first argument matches one of the subcommands if (i == 0) { for (auto& subcommand : subcommands_) { - if (arg == subcommand->name_ || arg==subcommand->short_name_) { + if (arg == subcommand->name_ || (!subcommand->short_name_.empty() + && arg == subcommand->short_name_)) { return subcommand->Execute( - std::vector<android::StringPiece>(args.begin() + 1, args.end()), out_error); + std::vector<StringPiece>(args.begin() + 1, args.end()), out_error); } } for (auto& subcommand : experimental_subcommands_) { - if (arg == subcommand->name_ || arg==subcommand->short_name_) { + if (arg == subcommand->name_ || (!subcommand->short_name_.empty() + && arg == subcommand->short_name_)) { return subcommand->Execute( - std::vector<android::StringPiece>(args.begin() + 1, args.end()), out_error); + std::vector<StringPiece>(args.begin() + 1, args.end()), out_error); } } } - file_args.push_back(arg.to_string()); + file_args.push_back(GetSafePath(arg)); continue; } @@ -205,7 +224,7 @@ int Command::Execute(const std::vector<android::StringPiece>& args, std::ostream } else { flag.action({}); } - flag.parsed = true; + flag.found = true; match = true; break; } @@ -219,7 +238,7 @@ int Command::Execute(const std::vector<android::StringPiece>& args, std::ostream } for (const Flag& flag : flags_) { - if (flag.required && !flag.parsed) { + if (flag.is_required && !flag.found) { *out_error << "missing required flag " << flag.name << "\n\n"; Usage(out_error); return 1; diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h index 16949883d123..d21571d530d2 100644 --- a/tools/aapt2/cmd/Command.h +++ b/tools/aapt2/cmd/Command.h @@ -32,55 +32,83 @@ namespace aapt { class Command { public: explicit Command(const android::StringPiece& name) : name_(name.to_string()), - fullname_(name.to_string()) { } + short_name_(""), + full_subcommand_name_(name.to_string()) {} + explicit Command(const android::StringPiece& name, const android::StringPiece& short_name) - : name_(name.to_string()), short_name_(short_name.to_string()), fullname_(name.to_string()) {} + : name_(name.to_string()), short_name_(short_name.to_string()), + full_subcommand_name_(name.to_string()) {} + virtual ~Command() = default; + // Behavior flags used with the following functions that change how the command flags are parsed + // displayed. + enum : uint32_t { + // Indicates the arguments are file or folder paths. On Windows, paths that exceed the maximum + // path length will be converted to use the extended path prefix '//?/'. Without this + // conversion, files with long paths cannot be opened. + kPath = 1 << 0, + }; + void AddRequiredFlag(const android::StringPiece& name, const android::StringPiece& description, - std::string* value); - void AddRequiredFlagList(const android::StringPiece& name, const android::StringPiece& - description, std::vector<std::string>* value); + std::string* value, uint32_t flags = 0); + + void AddRequiredFlagList(const android::StringPiece& name, + const android::StringPiece& description, std::vector<std::string>* value, + uint32_t flags = 0); + void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description, - Maybe<std::string>* value); + Maybe<std::string>* value, uint32_t flags = 0); + void AddOptionalFlagList(const android::StringPiece& name, - const android::StringPiece& description, std::vector<std::string>* value); + const android::StringPiece& description, std::vector<std::string>* value, + uint32_t flags = 0); + void AddOptionalFlagList(const android::StringPiece& name, - const android::StringPiece& description, std::unordered_set<std::string>* value); + const android::StringPiece& description, + std::unordered_set<std::string>* value); + void AddOptionalSwitch(const android::StringPiece& name, const android::StringPiece& description, - bool* value); + bool* value); + void AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental = false); void SetDescription(const android::StringPiece& name); - /** Prints the help menu of the command. */ + // Prints the help menu of the command. void Usage(std::ostream* out); - /** - * Parses the command line arguments, sets the flag variable values, and runs the action of - * the command. If the arguments fail to parse to the command and its subcommands, then the action - * will not be run and the usage will be printed instead. - **/ + // Parses the command line arguments, sets the flag variable values, and runs the action of + // the command. If the arguments fail to parse to the command and its subcommands, then the action + // will not be run and the usage will be printed instead. int Execute(const std::vector<android::StringPiece>& args, std::ostream* outError); - /** The action to preform when the command is executed. */ + // The action to preform when the command is executed. virtual int Action(const std::vector<std::string>& args) = 0; private: - struct Flag { - std::string name; - std::string description; - std::function<bool(const android::StringPiece& value)> action; - bool required; - size_t num_args; + DISALLOW_COPY_AND_ASSIGN(Command); - bool parsed; + struct Flag { + explicit Flag(const android::StringPiece& name, const android::StringPiece& description, + const bool is_required, const size_t num_args, + std::function<bool(const android::StringPiece& value)>&& action) + : name(name.to_string()), description(description.to_string()), is_required(is_required), + num_args(num_args), action(std::move(action)) {} + + const std::string name; + const std::string description; + const bool is_required; + const size_t num_args; + const std::function<bool(const android::StringPiece& value)> action; + bool found = false; }; - std::string description_; - std::string name_; - std::string short_name_; - std::string fullname_; + const std::string name_; + const std::string short_name_; + std::string description_ = ""; + std::string full_subcommand_name_; + std::vector<Flag> flags_; std::vector<std::unique_ptr<Command>> subcommands_; std::vector<std::unique_ptr<Command>> experimental_subcommands_; diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp new file mode 100644 index 000000000000..65608fdf64a7 --- /dev/null +++ b/tools/aapt2/cmd/Command_test.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Command.h" + +#include "test/Test.h" + +using ::testing::Eq; + +namespace aapt { + +class TestCommand : public Command { + public: + explicit TestCommand() : Command("command") {} + int Action(const std::vector<std::string>& args) override { + args_ = args; + return 0; + } + + std::vector<std::string> args_; +}; + +#ifdef _WIN32 +TEST(CommandTest, LongFullyQualifiedPathWindows) { + TestCommand command; + std::string required_flag; + command.AddRequiredFlag("--rflag", "", &required_flag, Command::kPath); + Maybe<std::string> optional_flag; + command.AddOptionalFlag("--oflag", "", &optional_flag, Command::kPath); + std::vector<std::string> required_flag_list; + command.AddRequiredFlagList("--rlflag", "", &required_flag_list, Command::kPath); + std::vector<std::string> optional_flag_list; + command.AddOptionalFlagList("--olflag", "", &optional_flag_list, Command::kPath); + std::string non_path_flag; + command.AddRequiredFlag("--nflag", "", &non_path_flag); + + const std::string kLongPath = + "C:\\Users\\jedo\\_bazel_jedo\\vcmdctjv\\execroot\\__main__\\_tmp" + "\\6767b4778f8798efc0f784ee74fa70ee\\tests\\testApksAr8c7560a9a65" + "\\1346ee7c014a089fb55d8c46cf3d9\\project\\baseModule\\build" + "\\intermediates\\processed_res\\minified\\processMinifiedResources" + "\\1346ee7c014a089fb55d8c46cf3d9\\project\\baseModule\\build" + "\\intermediates\\processed_res\\minified\\processMinifiedResources" + "\\out\\resources-minified.ap_"; + + const std::string kExpected = + "\\\\?\\C:\\Users\\jedo\\_bazel_jedo\\vcmdctjv\\execroot\\__main__\\_tmp" + "\\6767b4778f8798efc0f784ee74fa70ee\\tests\\testApksAr8c7560a9a65" + "\\1346ee7c014a089fb55d8c46cf3d9\\project\\baseModule\\build" + "\\intermediates\\processed_res\\minified\\processMinifiedResources" + "\\1346ee7c014a089fb55d8c46cf3d9\\project\\baseModule\\build" + "\\intermediates\\processed_res\\minified\\processMinifiedResources" + "\\out\\resources-minified.ap_"; + + + ASSERT_THAT(command.Execute({"--rflag", kLongPath, + "--oflag", kLongPath, + "--rlflag", kLongPath, + "--rlflag", kLongPath, + "--olflag", kLongPath, + "--olflag", kLongPath, + "--nflag", kLongPath, + kLongPath, kLongPath}, &std::cerr), Eq(0)); + + ASSERT_THAT(required_flag, Eq(kExpected)); + ASSERT_THAT(optional_flag, Eq(kExpected)); + ASSERT_THAT(required_flag_list.size(), Eq(2)); + ASSERT_THAT(required_flag_list[0], Eq(kExpected)); + ASSERT_THAT(required_flag_list[1], Eq(kExpected)); + ASSERT_THAT(optional_flag_list.size(), Eq(2)); + ASSERT_THAT(optional_flag_list[0], Eq(kExpected)); + ASSERT_THAT(optional_flag_list[1], Eq(kExpected)); + + // File arguments should also be converted to use the long path prefix + ASSERT_THAT(command.args_.size(), Eq(2)); + ASSERT_THAT(command.args_[0], Eq(kExpected)); + ASSERT_THAT(command.args_[1], Eq(kExpected)); + + // Do not convert flags that are not marged as paths + ASSERT_THAT(non_path_flag, Eq(kLongPath)); +} +#endif + +} // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index c429d5f5d4b2..9b32cb3750a5 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -44,13 +44,13 @@ class CompileCommand : public Command { explicit CompileCommand(IDiagnostics* diagnostic) : Command("compile", "c"), diagnostic_(diagnostic) { SetDescription("Compiles resources to be linked into an apk."); - AddRequiredFlag("-o", "Output path", &options_.output_path); - AddOptionalFlag("--dir", "Directory to scan for resources", &options_.res_dir); + AddRequiredFlag("-o", "Output path", &options_.output_path, Command::kPath); + AddOptionalFlag("--dir", "Directory to scan for resources", &options_.res_dir, Command::kPath); AddOptionalFlag("--zip", "Zip file containing the res directory to scan for resources", - &options_.res_zip); + &options_.res_zip, Command::kPath); AddOptionalFlag("--output-text-symbols", "Generates a text file containing the resource symbols in the\n" - "specified file", &options_.generate_text_symbols_path); + "specified file", &options_.generate_text_symbols_path, Command::kPath); AddOptionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales " "(en-XA and ar-XB)", &options_.pseudolocalize); AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch); @@ -70,8 +70,9 @@ class CompileCommand : public Command { Maybe<std::string> visibility_; }; -int Compile(IAaptContext* context, io::IFileCollection* inputs, - IArchiveWriter* output_writer, CompileOptions& options); +int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* output_writer, + CompileOptions& options); + }// namespace aapt #endif //AAPT2_COMPILE_H diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h index 6a6719c91b58..14016b106d80 100644 --- a/tools/aapt2/cmd/Convert.h +++ b/tools/aapt2/cmd/Convert.h @@ -27,7 +27,7 @@ class ConvertCommand : public Command { public: explicit ConvertCommand() : Command("convert") { SetDescription("Converts an apk between binary and proto formats."); - AddRequiredFlag("-o", "Output path", &output_path_); + AddRequiredFlag("-o", "Output path", &output_path_, Command::kPath); AddOptionalFlag("--output-format", android::base::StringPrintf("Format of the output. " "Accepted values are '%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto, kOutputFormatBinary, kOutputFormatBinary), &output_format_); diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index 950dac204dde..f740d538b326 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -100,24 +100,26 @@ class LinkCommand : public Command { explicit LinkCommand(IDiagnostics* diag) : Command("link", "l"), diag_(diag) { SetDescription("Links resources into an apk."); - AddRequiredFlag("-o", "Output path.", &options_.output_path); + AddRequiredFlag("-o", "Output path.", &options_.output_path, Command::kPath); AddRequiredFlag("--manifest", "Path to the Android manifest to build.", - &options_.manifest_path); - AddOptionalFlagList("-I", "Adds an Android APK to link against.", &options_.include_paths); + &options_.manifest_path, Command::kPath); + AddOptionalFlagList("-I", "Adds an Android APK to link against.", &options_.include_paths, + Command::kPath); AddOptionalFlagList("-A", "An assets directory to include in the APK. These are unprocessed.", - &options_.assets_dirs); + &options_.assets_dirs, Command::kPath); AddOptionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" - "The last conflicting resource given takes precedence.", &overlay_arg_list_); + "The last conflicting resource given takes precedence.", &overlay_arg_list_, + Command::kPath); AddOptionalFlag("--package-id", "Specify the package ID to use for this app. Must be greater or equal to\n" "0x7f and can't be used with --static-lib or --shared-lib.", &package_id_); AddOptionalFlag("--java", "Directory in which to generate R.java.", - &options_.generate_java_class_path); + &options_.generate_java_class_path, Command::kPath); AddOptionalFlag("--proguard", "Output file for generated Proguard rules.", - &options_.generate_proguard_rules_path); + &options_.generate_proguard_rules_path, Command::kPath); AddOptionalFlag("--proguard-main-dex", "Output file for generated Proguard rules for the main dex.", - &options_.generate_main_dex_proguard_rules_path); + &options_.generate_main_dex_proguard_rules_path, Command::kPath); AddOptionalSwitch("--proguard-conditional-keep-rules", "Generate conditional Proguard keep rules.", &options_.generate_conditional_proguard_rules); diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index 43bc216382fa..d07305bc3e04 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -61,9 +61,10 @@ class OptimizeCommand : public Command { public: explicit OptimizeCommand() : Command("optimize") { SetDescription("Preforms resource optimizations on an apk."); - AddOptionalFlag("-o", "Path to the output APK.", &options_.output_path); - AddOptionalFlag("-d", "Path to the output directory (for splits).", &options_.output_dir); - AddOptionalFlag("-x", "Path to XML configuration file.", &config_path_); + AddOptionalFlag("-o", "Path to the output APK.", &options_.output_path, Command::kPath); + AddOptionalFlag("-d", "Path to the output directory (for splits).", &options_.output_dir, + Command::kPath); + AddOptionalFlag("-x", "Path to XML configuration file.", &config_path_, Command::kPath); AddOptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only_); AddOptionalFlag( "--target-densities", |