Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a695f1da authored by Ryan Mitchell's avatar Ryan Mitchell Committed by Android (Google) Code Review
Browse files

Merge "Fix long file paths for Windows"

parents 05c48793 2c8fc866
Loading
Loading
Loading
Loading
+62 −43
Original line number Diff line number Diff line
@@ -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,
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;
+55 −27
Original line number Diff line number Diff line
@@ -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);

  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_;
+97 −0
Original line number Diff line number Diff line
/*
 * 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
+7 −6
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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_);
Loading