普通文本  |  333行  |  12.07 KB

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/gn/build_settings.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/functions.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/scope.h"
#include "tools/gn/settings.h"
#include "tools/gn/source_dir.h"
#include "tools/gn/source_file.h"
#include "tools/gn/value.h"

namespace functions {

namespace {

enum SeparatorConversion {
  SEP_NO_CHANGE,  // Don't change.
  SEP_TO_SYSTEM,  // Slashes to system ones.
  SEP_FROM_SYSTEM  // System ones to slashes.
};

// Does the specified path separator conversion in-place.
void ConvertSlashes(std::string* str, SeparatorConversion mode) {
#if defined(OS_WIN)
  switch (mode) {
    case SEP_NO_CHANGE:
      break;
    case SEP_TO_SYSTEM:
      for (size_t i = 0; i < str->size(); i++) {
        if ((*str)[i] == '/')
          (*str)[i] = '\\';
      }
      break;
    case SEP_FROM_SYSTEM:
      for (size_t i = 0; i < str->size(); i++) {
        if ((*str)[i] == '\\')
          (*str)[i] = '/';
      }
      break;
  }
#else
  DCHECK(str->find('\\') == std::string::npos)
      << "Filename contains a backslash on a non-Windows platform.";
#endif
}

bool EndsInSlash(const std::string& s) {
  return !s.empty() && (s[s.size() - 1] == '/' || s[s.size() - 1] == '\\');
}

// We want the output to match the input in terms of ending in a slash or not.
// Through all the transformations, these can get added or removed in various
// cases.
void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
  if (EndsInSlash(input)) {
    if (!EndsInSlash(*output))  // Preserve same slash type as input.
      output->push_back(input[input.size() - 1]);
  } else {
    if (EndsInSlash(*output))
      output->resize(output->size() - 1);
  }
}

// Returns true if the given value looks like a directory, otherwise we'll
// assume it's a file.
bool ValueLooksLikeDir(const std::string& value) {
  if (value.empty())
    return true;
  size_t value_size = value.size();

  // Count the number of dots at the end of the string.
  size_t num_dots = 0;
  while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
    num_dots++;

  if (num_dots == value.size())
    return true;  // String is all dots.

  if (value[value_size - num_dots - 1] == '/' ||
      value[value_size - num_dots - 1] == '\\')
    return true;  // String is a [back]slash followed by 0 or more dots.

  // Anything else.
  return false;
}

Value ConvertOnePath(const Scope* scope,
                     const FunctionCallNode* function,
                     const Value& value,
                     const SourceDir& from_dir,
                     const SourceDir& to_dir,
                     bool convert_to_system_absolute,
                     SeparatorConversion separator_conversion,
                     Err* err) {
  Value result;  // Ensure return value optimization.

  if (!value.VerifyTypeIs(Value::STRING, err))
    return result;
  const std::string& string_value = value.string_value();

  bool looks_like_dir = ValueLooksLikeDir(string_value);

  // System-absolute output special case.
  if (convert_to_system_absolute) {
    base::FilePath system_path;
    if (looks_like_dir) {
      system_path = scope->settings()->build_settings()->GetFullPath(
          from_dir.ResolveRelativeDir(string_value));
    } else {
      system_path = scope->settings()->build_settings()->GetFullPath(
          from_dir.ResolveRelativeFile(string_value));
    }
    result = Value(function, FilePathToUTF8(system_path));
    if (looks_like_dir)
      MakeSlashEndingMatchInput(string_value, &result.string_value());
    ConvertPathToSystem(&result.string_value());
    return result;
  }

  if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) {
    *err = Err(function, "System-absolute directories are not supported for "
        "the source or dest dir for rebase_path. It would be nice to add this "
        "if you're so inclined!");
    return result;
  }

  result = Value(function, Value::STRING);
  if (looks_like_dir) {
    result.string_value() = RebaseSourceAbsolutePath(
        from_dir.ResolveRelativeDir(string_value).value(),
        to_dir);
    MakeSlashEndingMatchInput(string_value, &result.string_value());
  } else {
    result.string_value() = RebaseSourceAbsolutePath(
        from_dir.ResolveRelativeFile(string_value).value(),
        to_dir);
  }

  ConvertSlashes(&result.string_value(), separator_conversion);
  return result;
}

}  // namespace

const char kRebasePath[] = "rebase_path";
const char kRebasePath_Help[] =
    "rebase_path: Rebase a file or directory to another location.\n"
    "\n"
    "  converted = rebase_path(input, current_base, new_base,\n"
    "                          [path_separators])\n"
    "\n"
    "  Takes a string argument representing a file name, or a list of such\n"
    "  strings and converts it/them to be relative to a different base\n"
    "  directory.\n"
    "\n"
    "  When invoking the compiler or scripts, GN will automatically convert\n"
    "  sources and include directories to be relative to the build directory.\n"
    "  However, if you're passing files directly in the \"args\" array or\n"
    "  doing other manual manipulations where GN doesn't know something is\n"
    "  a file name, you will need to convert paths to be relative to what\n"
    "  your tool is expecting.\n"
    "\n"
    "  The common case is to use this to convert paths relative to the\n"
    "  current directory to be relative to the build directory (which will\n"
    "  be the current directory when executing scripts).\n"
    "\n"
    "Arguments\n"
    "\n"
    "  input\n"
    "      A string or list of strings representing file or directory names\n"
    "      These can be relative paths (\"foo/bar.txt\", system absolte paths\n"
    "      (\"/foo/bar.txt\"), or source absolute paths (\"//foo/bar.txt\").\n"
    "\n"
    "  current_base\n"
    "      Directory representing the base for relative paths in the input.\n"
    "      If this is not an absolute path, it will be treated as being\n"
    "      relative to the current build file. Use \".\" to convert paths\n"
    "      from the current BUILD-file's directory.\n"
    "\n"
    "  new_base\n"
    "      The directory to convert the paths to be relative to. As with the\n"
    "      current_base, this can be a relative path, which will be treated\n"
    "      as being relative to the current BUILD-file's directory.\n"
    "\n"
    "      As a special case, if new_base is the empty string, all paths\n"
    "      will be converted to system-absolute native style paths with\n"
    "      system path separators. This is useful for invoking external\n"
    "      programs.\n"
    "\n"
    "  path_separators\n"
    "      On Windows systems, indicates whether and how path separators\n"
    "      should be converted as part of the transformation. It can be one\n"
    "      of the following strings:\n"
    "       - \"none\" Perform no changes on path separators. This is the\n"
    "         default if this argument is unspecified.\n"
    "       - \"to_system\" Convert to the system path separators\n"
    "         (backslashes on Windows).\n"
    "       - \"from_system\" Convert system path separators to forward\n"
    "         slashes.\n"
    "\n"
    "      On Posix systems there are no path separator transformations\n"
    "      applied. If the new_base is empty (specifying absolute output)\n"
    "      this parameter should not be supplied since paths will always be\n"
    "      converted,\n"
    "\n"
    "Return value\n"
    "\n"
    "  The return value will be the same type as the input value (either a\n"
    "  string or a list of strings). All relative and source-absolute file\n"
    "  names will be converted to be relative to the requested output\n"
    "  System-absolute paths will be unchanged.\n"
    "\n"
    "Example\n"
    "\n"
    "  # Convert a file in the current directory to be relative to the build\n"
    "  # directory (the current dir when executing compilers and scripts).\n"
    "  foo = rebase_path(\"myfile.txt\", \".\", root_build_dir)\n"
    "  # might produce \"../../project/myfile.txt\".\n"
    "\n"
    "  # Convert a file to be system absolute:\n"
    "  foo = rebase_path(\"myfile.txt\", \".\", \"\")\n"
    "  # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n"
    "  # \"/home/you/source/project/myfile.txt\" on Linux.\n"
    "\n"
    "  # Convert a file's path separators from forward slashes to system\n"
    "  # slashes.\n"
    "  foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n"
    "\n"
    "  # Typical usage for converting to the build directory for a script.\n"
    "  custom(\"myscript\") {\n"
    "    # Don't convert sources, GN will automatically convert these to be\n"
    "    # relative to the build directory when it contructs the command\n"
    "    # line for your script.\n"
    "    sources = [ \"foo.txt\", \"bar.txt\" ]\n"
    "\n"
    "    # Extra file args passed manually need to be explicitly converted\n"
    "    # to be relative to the build directory:\n"
    "    args = [\n"
    "      \"--data\",\n"
    "      rebase_path(\"//mything/data/input.dat\", \".\", root_build_dir),\n"
    "      \"--rel\",\n"
    "      rebase_path(\"relative_path.txt\", \".\", root_build_dir)\n"
    "    ]\n"
    "  }\n";

Value RunRebasePath(Scope* scope,
                    const FunctionCallNode* function,
                    const std::vector<Value>& args,
                    Err* err) {
  Value result;

  // Inputs.
  if (args.size() != 3 && args.size() != 4) {
    *err = Err(function->function(), "rebase_path takes 3 or 4 args.");
    return result;
  }
  const Value& inputs = args[0];

  // From path.
  if (!args[1].VerifyTypeIs(Value::STRING, err))
    return result;
  const SourceDir& current_dir = scope->GetSourceDir();
  SourceDir from_dir = current_dir.ResolveRelativeDir(args[1].string_value());

  // To path.
  if (!args[2].VerifyTypeIs(Value::STRING, err))
    return result;
  bool convert_to_system_absolute = false;
  SourceDir to_dir;
  if (args[2].string_value().empty()) {
    convert_to_system_absolute = true;
  } else {
    to_dir = current_dir.ResolveRelativeDir(args[2].string_value());
  }

  // Path conversion.
  SeparatorConversion sep_conversion = SEP_NO_CHANGE;
  if (args.size() == 4) {
    if (convert_to_system_absolute) {
      *err = Err(function, "Can't specify slash conversion.",
          "You specified absolute system path output by using an empty string "
          "for the desination directory on rebase_path(). In this case, you "
          "can't specify slash conversion.");
      return result;
    }

    if (!args[3].VerifyTypeIs(Value::STRING, err))
      return result;
    const std::string& sep_string = args[3].string_value();
    if (sep_string == "to_system") {
      sep_conversion = SEP_TO_SYSTEM;
    } else if (sep_string == "from_system") {
      sep_conversion = SEP_FROM_SYSTEM;
    } else if (sep_string != "none") {
      *err = Err(args[3], "Invalid path separator conversion mode.",
          "I was expecting \"none\",  \"to_system\", or \"from_system\" and\n"
          "you gave me \"" + args[3].string_value() + "\".");
      return result;
    }
  }

  if (inputs.type() == Value::STRING) {
    return ConvertOnePath(scope, function, inputs,
                          from_dir, to_dir, convert_to_system_absolute,
                          sep_conversion, err);

  } else if (inputs.type() == Value::LIST) {
    result = Value(function, Value::LIST);
    result.list_value().reserve(inputs.list_value().size());

    for (size_t i = 0; i < inputs.list_value().size(); i++) {
      result.list_value().push_back(
          ConvertOnePath(scope, function, inputs.list_value()[i],
                         from_dir, to_dir, convert_to_system_absolute,
                         sep_conversion, err));
      if (err->has_error()) {
        result = Value();
        return result;
      }
    }
    return result;
  }

  *err = Err(function->function(),
             "rebase_path requires a list or a string.");
  return result;
}

}  // namespace functions