Home | History | Annotate | Download | only in gn
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "tools/gn/build_settings.h"
      6 #include "tools/gn/filesystem_utils.h"
      7 #include "tools/gn/functions.h"
      8 #include "tools/gn/parse_tree.h"
      9 #include "tools/gn/scope.h"
     10 #include "tools/gn/settings.h"
     11 #include "tools/gn/source_dir.h"
     12 #include "tools/gn/source_file.h"
     13 #include "tools/gn/value.h"
     14 
     15 namespace functions {
     16 
     17 namespace {
     18 
     19 // We want the output to match the input in terms of ending in a slash or not.
     20 // Through all the transformations, these can get added or removed in various
     21 // cases.
     22 void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
     23   if (EndsWithSlash(input)) {
     24     if (!EndsWithSlash(*output))  // Preserve same slash type as input.
     25       output->push_back(input[input.size() - 1]);
     26   } else {
     27     if (EndsWithSlash(*output))
     28       output->resize(output->size() - 1);
     29   }
     30 }
     31 
     32 // Returns true if the given value looks like a directory, otherwise we'll
     33 // assume it's a file.
     34 bool ValueLooksLikeDir(const std::string& value) {
     35   if (value.empty())
     36     return true;
     37   size_t value_size = value.size();
     38 
     39   // Count the number of dots at the end of the string.
     40   size_t num_dots = 0;
     41   while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
     42     num_dots++;
     43 
     44   if (num_dots == value.size())
     45     return true;  // String is all dots.
     46 
     47   if (IsSlash(value[value_size - num_dots - 1]))
     48     return true;  // String is a [back]slash followed by 0 or more dots.
     49 
     50   // Anything else.
     51   return false;
     52 }
     53 
     54 Value ConvertOnePath(const Scope* scope,
     55                      const FunctionCallNode* function,
     56                      const Value& value,
     57                      const SourceDir& from_dir,
     58                      const SourceDir& to_dir,
     59                      bool convert_to_system_absolute,
     60                      Err* err) {
     61   Value result;  // Ensure return value optimization.
     62 
     63   if (!value.VerifyTypeIs(Value::STRING, err))
     64     return result;
     65   const std::string& string_value = value.string_value();
     66 
     67   bool looks_like_dir = ValueLooksLikeDir(string_value);
     68 
     69   // System-absolute output special case.
     70   if (convert_to_system_absolute) {
     71     base::FilePath system_path;
     72     if (looks_like_dir) {
     73       system_path = scope->settings()->build_settings()->GetFullPath(
     74           from_dir.ResolveRelativeDir(string_value));
     75     } else {
     76       system_path = scope->settings()->build_settings()->GetFullPath(
     77           from_dir.ResolveRelativeFile(string_value));
     78     }
     79     result = Value(function, FilePathToUTF8(system_path));
     80     if (looks_like_dir)
     81       MakeSlashEndingMatchInput(string_value, &result.string_value());
     82     return result;
     83   }
     84 
     85   if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) {
     86     *err = Err(function, "System-absolute directories are not supported for "
     87         "the source or dest dir for rebase_path. It would be nice to add this "
     88         "if you're so inclined!");
     89     return result;
     90   }
     91 
     92   result = Value(function, Value::STRING);
     93   if (looks_like_dir) {
     94     result.string_value() = RebaseSourceAbsolutePath(
     95         from_dir.ResolveRelativeDir(string_value).value(),
     96         to_dir);
     97     MakeSlashEndingMatchInput(string_value, &result.string_value());
     98   } else {
     99     result.string_value() = RebaseSourceAbsolutePath(
    100         from_dir.ResolveRelativeFile(string_value).value(),
    101         to_dir);
    102   }
    103 
    104   return result;
    105 }
    106 
    107 }  // namespace
    108 
    109 const char kRebasePath[] = "rebase_path";
    110 const char kRebasePath_HelpShort[] =
    111     "rebase_path: Rebase a file or directory to another location.";
    112 const char kRebasePath_Help[] =
    113     "rebase_path: Rebase a file or directory to another location.\n"
    114     "\n"
    115     "  converted = rebase_path(input,\n"
    116     "                          new_base = \"\",\n"
    117     "                          current_base = \".\")\n"
    118     "\n"
    119     "  Takes a string argument representing a file name, or a list of such\n"
    120     "  strings and converts it/them to be relative to a different base\n"
    121     "  directory.\n"
    122     "\n"
    123     "  When invoking the compiler or scripts, GN will automatically convert\n"
    124     "  sources and include directories to be relative to the build directory.\n"
    125     "  However, if you're passing files directly in the \"args\" array or\n"
    126     "  doing other manual manipulations where GN doesn't know something is\n"
    127     "  a file name, you will need to convert paths to be relative to what\n"
    128     "  your tool is expecting.\n"
    129     "\n"
    130     "  The common case is to use this to convert paths relative to the\n"
    131     "  current directory to be relative to the build directory (which will\n"
    132     "  be the current directory when executing scripts).\n"
    133     "\n"
    134     "  If you want to convert a file path to be source-absolute (that is,\n"
    135     "  beginning with a double slash like \"//foo/bar\"), you should use\n"
    136     "  the get_path_info() function. This function won't work because it will\n"
    137     "  always make relative paths, and it needs to support making paths\n"
    138     "  relative to the source root, so can't also generate source-absolute\n"
    139     "  paths without more special-cases.\n"
    140     "\n"
    141     "Arguments:\n"
    142     "\n"
    143     "  input\n"
    144     "      A string or list of strings representing file or directory names\n"
    145     "      These can be relative paths (\"foo/bar.txt\"), system absolute\n"
    146     "      paths (\"/foo/bar.txt\"), or source absolute paths\n"
    147     "      (\"//foo/bar.txt\").\n"
    148     "\n"
    149     "  new_base\n"
    150     "      The directory to convert the paths to be relative to. This can be\n"
    151     "      an absolute path or a relative path (which will be treated\n"
    152     "      as being relative to the current BUILD-file's directory).\n"
    153     "\n"
    154     "      As a special case, if new_base is the empty string (the default),\n"
    155     "      all paths will be converted to system-absolute native style paths\n"
    156     "      with system path separators. This is useful for invoking external\n"
    157     "      programs.\n"
    158     "\n"
    159     "  current_base\n"
    160     "      Directory representing the base for relative paths in the input.\n"
    161     "      If this is not an absolute path, it will be treated as being\n"
    162     "      relative to the current build file. Use \".\" (the default) to\n"
    163     "      convert paths from the current BUILD-file's directory.\n"
    164     "\n"
    165     "      On Posix systems there are no path separator transformations\n"
    166     "      applied. If the new_base is empty (specifying absolute output)\n"
    167     "      this parameter should not be supplied since paths will always be\n"
    168     "      converted,\n"
    169     "\n"
    170     "Return value\n"
    171     "\n"
    172     "  The return value will be the same type as the input value (either a\n"
    173     "  string or a list of strings). All relative and source-absolute file\n"
    174     "  names will be converted to be relative to the requested output\n"
    175     "  System-absolute paths will be unchanged.\n"
    176     "\n"
    177     "Example\n"
    178     "\n"
    179     "  # Convert a file in the current directory to be relative to the build\n"
    180     "  # directory (the current dir when executing compilers and scripts).\n"
    181     "  foo = rebase_path(\"myfile.txt\", root_build_dir)\n"
    182     "  # might produce \"../../project/myfile.txt\".\n"
    183     "\n"
    184     "  # Convert a file to be system absolute:\n"
    185     "  foo = rebase_path(\"myfile.txt\")\n"
    186     "  # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n"
    187     "  # \"/home/you/source/project/myfile.txt\" on Linux.\n"
    188     "\n"
    189     "  # Convert a file's path separators from forward slashes to system\n"
    190     "  # slashes.\n"
    191     "  foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n"
    192     "\n"
    193     "  # Typical usage for converting to the build directory for a script.\n"
    194     "  action(\"myscript\") {\n"
    195     "    # Don't convert sources, GN will automatically convert these to be\n"
    196     "    # relative to the build directory when it contructs the command\n"
    197     "    # line for your script.\n"
    198     "    sources = [ \"foo.txt\", \"bar.txt\" ]\n"
    199     "\n"
    200     "    # Extra file args passed manually need to be explicitly converted\n"
    201     "    # to be relative to the build directory:\n"
    202     "    args = [\n"
    203     "      \"--data\",\n"
    204     "      rebase_path(\"//mything/data/input.dat\", root_build_dir),\n"
    205     "      \"--rel\",\n"
    206     "      rebase_path(\"relative_path.txt\", root_build_dir)\n"
    207     "    ] + sources\n"
    208     "  }\n";
    209 
    210 Value RunRebasePath(Scope* scope,
    211                     const FunctionCallNode* function,
    212                     const std::vector<Value>& args,
    213                     Err* err) {
    214   Value result;
    215 
    216   // Argument indices.
    217   static const size_t kArgIndexInputs = 0;
    218   static const size_t kArgIndexDest = 1;
    219   static const size_t kArgIndexFrom = 2;
    220 
    221   // Inputs.
    222   if (args.size() < 1 || args.size() > 3) {
    223     *err = Err(function->function(), "Wrong # of arguments for rebase_path.");
    224     return result;
    225   }
    226   const Value& inputs = args[kArgIndexInputs];
    227 
    228   // To path.
    229   bool convert_to_system_absolute = true;
    230   SourceDir to_dir;
    231   const SourceDir& current_dir = scope->GetSourceDir();
    232   if (args.size() > kArgIndexDest) {
    233     if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
    234       return result;
    235     if (!args[kArgIndexDest].string_value().empty()) {
    236       to_dir =
    237           current_dir.ResolveRelativeDir(args[kArgIndexDest].string_value());
    238       convert_to_system_absolute = false;
    239     }
    240   }
    241 
    242   // From path.
    243   SourceDir from_dir;
    244   if (args.size() > kArgIndexFrom) {
    245     if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
    246       return result;
    247     from_dir =
    248         current_dir.ResolveRelativeDir(args[kArgIndexFrom].string_value());
    249   } else {
    250     // Default to current directory if unspecified.
    251     from_dir = current_dir;
    252   }
    253 
    254   // Path conversion.
    255   if (inputs.type() == Value::STRING) {
    256     return ConvertOnePath(scope, function, inputs,
    257                           from_dir, to_dir, convert_to_system_absolute, err);
    258 
    259   } else if (inputs.type() == Value::LIST) {
    260     result = Value(function, Value::LIST);
    261     result.list_value().reserve(inputs.list_value().size());
    262 
    263     for (size_t i = 0; i < inputs.list_value().size(); i++) {
    264       result.list_value().push_back(
    265           ConvertOnePath(scope, function, inputs.list_value()[i],
    266                          from_dir, to_dir, convert_to_system_absolute, err));
    267       if (err->has_error()) {
    268         result = Value();
    269         return result;
    270       }
    271     }
    272     return result;
    273   }
    274 
    275   *err = Err(function->function(),
    276              "rebase_path requires a list or a string.");
    277   return result;
    278 }
    279 
    280 }  // namespace functions
    281