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
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index bdee5c9..4424a35 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::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 @@
*out << " [options]";
for (const Flag& flag : flags_) {
- if (flag.required) {
+ if (flag.is_required) {
*out << " " << flag.name << " arg";
}
}
@@ -160,29 +177,31 @@
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 @@
} else {
flag.action({});
}
- flag.parsed = true;
+ flag.found = true;
match = true;
break;
}
@@ -219,7 +238,7 @@
}
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 1694988..d21571d 100644
--- a/tools/aapt2/cmd/Command.h
+++ b/tools/aapt2/cmd/Command.h
@@ -32,55 +32,83 @@
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 0000000..65608fd
--- /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 c429d5f..9b32cb3 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -44,13 +44,13 @@
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 @@
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 6a6719c..14016b1 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -27,7 +27,7 @@
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 950dac20..f740d53 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -100,24 +100,26 @@
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 43bc216..d07305b 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -61,9 +61,10 @@
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",