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 enum SeparatorConversion {
     20   SEP_NO_CHANGE,  // Don't change.
     21   SEP_TO_SYSTEM,  // Slashes to system ones.
     22   SEP_FROM_SYSTEM  // System ones to slashes.
     23 };
     24 
     25 // Does the specified path separator conversion in-place.
     26 void ConvertSlashes(std::string* str, SeparatorConversion mode) {
     27 #if defined(OS_WIN)
     28   switch (mode) {
     29     case SEP_NO_CHANGE:
     30       break;
     31     case SEP_TO_SYSTEM:
     32       for (size_t i = 0; i < str->size(); i++) {
     33         if ((*str)[i] == '/')
     34           (*str)[i] = '\\';
     35       }
     36       break;
     37     case SEP_FROM_SYSTEM:
     38       for (size_t i = 0; i < str->size(); i++) {
     39         if ((*str)[i] == '\\')
     40           (*str)[i] = '/';
     41       }
     42       break;
     43   }
     44 #else
     45   DCHECK(str->find('\\') == std::string::npos)
     46       << "Filename contains a backslash on a non-Windows platform.";
     47 #endif
     48 }
     49 
     50 bool EndsInSlash(const std::string& s) {
     51   return !s.empty() && (s[s.size() - 1] == '/' || s[s.size() - 1] == '\\');
     52 }
     53 
     54 // We want the output to match the input in terms of ending in a slash or not.
     55 // Through all the transformations, these can get added or removed in various
     56 // cases.
     57 void MakeSlashEndingMatchInput(const std::string& input, std::string* output) {
     58   if (EndsInSlash(input)) {
     59     if (!EndsInSlash(*output))  // Preserve same slash type as input.
     60       output->push_back(input[input.size() - 1]);
     61   } else {
     62     if (EndsInSlash(*output))
     63       output->resize(output->size() - 1);
     64   }
     65 }
     66 
     67 // Returns true if the given value looks like a directory, otherwise we'll
     68 // assume it's a file.
     69 bool ValueLooksLikeDir(const std::string& value) {
     70   if (value.empty())
     71     return true;
     72   size_t value_size = value.size();
     73 
     74   // Count the number of dots at the end of the string.
     75   size_t num_dots = 0;
     76   while (num_dots < value_size && value[value_size - num_dots - 1] == '.')
     77     num_dots++;
     78 
     79   if (num_dots == value.size())
     80     return true;  // String is all dots.
     81 
     82   if (value[value_size - num_dots - 1] == '/' ||
     83       value[value_size - num_dots - 1] == '\\')
     84     return true;  // String is a [back]slash followed by 0 or more dots.
     85 
     86   // Anything else.
     87   return false;
     88 }
     89 
     90 Value ConvertOnePath(const Scope* scope,
     91                      const FunctionCallNode* function,
     92                      const Value& value,
     93                      const SourceDir& from_dir,
     94                      const SourceDir& to_dir,
     95                      bool convert_to_system_absolute,
     96                      SeparatorConversion separator_conversion,
     97                      Err* err) {
     98   Value result;  // Ensure return value optimization.
     99 
    100   if (!value.VerifyTypeIs(Value::STRING, err))
    101     return result;
    102   const std::string& string_value = value.string_value();
    103 
    104   bool looks_like_dir = ValueLooksLikeDir(string_value);
    105 
    106   // System-absolute output special case.
    107   if (convert_to_system_absolute) {
    108     base::FilePath system_path;
    109     if (looks_like_dir) {
    110       system_path = scope->settings()->build_settings()->GetFullPath(
    111           from_dir.ResolveRelativeDir(string_value));
    112     } else {
    113       system_path = scope->settings()->build_settings()->GetFullPath(
    114           from_dir.ResolveRelativeFile(string_value));
    115     }
    116     result = Value(function, FilePathToUTF8(system_path));
    117     if (looks_like_dir)
    118       MakeSlashEndingMatchInput(string_value, &result.string_value());
    119     ConvertPathToSystem(&result.string_value());
    120     return result;
    121   }
    122 
    123   if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) {
    124     *err = Err(function, "System-absolute directories are not supported for "
    125         "the source or dest dir for rebase_path. It would be nice to add this "
    126         "if you're so inclined!");
    127     return result;
    128   }
    129 
    130   result = Value(function, Value::STRING);
    131   if (looks_like_dir) {
    132     result.string_value() = RebaseSourceAbsolutePath(
    133         from_dir.ResolveRelativeDir(string_value).value(),
    134         to_dir);
    135     MakeSlashEndingMatchInput(string_value, &result.string_value());
    136   } else {
    137     result.string_value() = RebaseSourceAbsolutePath(
    138         from_dir.ResolveRelativeFile(string_value).value(),
    139         to_dir);
    140   }
    141 
    142   ConvertSlashes(&result.string_value(), separator_conversion);
    143   return result;
    144 }
    145 
    146 }  // namespace
    147 
    148 const char kRebasePath[] = "rebase_path";
    149 const char kRebasePath_Help[] =
    150     "rebase_path: Rebase a file or directory to another location.\n"
    151     "\n"
    152     "  converted = rebase_path(input, current_base, new_base,\n"
    153     "                          [path_separators])\n"
    154     "\n"
    155     "  Takes a string argument representing a file name, or a list of such\n"
    156     "  strings and converts it/them to be relative to a different base\n"
    157     "  directory.\n"
    158     "\n"
    159     "  When invoking the compiler or scripts, GN will automatically convert\n"
    160     "  sources and include directories to be relative to the build directory.\n"
    161     "  However, if you're passing files directly in the \"args\" array or\n"
    162     "  doing other manual manipulations where GN doesn't know something is\n"
    163     "  a file name, you will need to convert paths to be relative to what\n"
    164     "  your tool is expecting.\n"
    165     "\n"
    166     "  The common case is to use this to convert paths relative to the\n"
    167     "  current directory to be relative to the build directory (which will\n"
    168     "  be the current directory when executing scripts).\n"
    169     "\n"
    170     "Arguments\n"
    171     "\n"
    172     "  input\n"
    173     "      A string or list of strings representing file or directory names\n"
    174     "      These can be relative paths (\"foo/bar.txt\", system absolte paths\n"
    175     "      (\"/foo/bar.txt\"), or source absolute paths (\"//foo/bar.txt\").\n"
    176     "\n"
    177     "  current_base\n"
    178     "      Directory representing the base for relative paths in the input.\n"
    179     "      If this is not an absolute path, it will be treated as being\n"
    180     "      relative to the current build file. Use \".\" to convert paths\n"
    181     "      from the current BUILD-file's directory.\n"
    182     "\n"
    183     "  new_base\n"
    184     "      The directory to convert the paths to be relative to. As with the\n"
    185     "      current_base, this can be a relative path, which will be treated\n"
    186     "      as being relative to the current BUILD-file's directory.\n"
    187     "\n"
    188     "      As a special case, if new_base is the empty string, all paths\n"
    189     "      will be converted to system-absolute native style paths with\n"
    190     "      system path separators. This is useful for invoking external\n"
    191     "      programs.\n"
    192     "\n"
    193     "  path_separators\n"
    194     "      On Windows systems, indicates whether and how path separators\n"
    195     "      should be converted as part of the transformation. It can be one\n"
    196     "      of the following strings:\n"
    197     "       - \"none\" Perform no changes on path separators. This is the\n"
    198     "         default if this argument is unspecified.\n"
    199     "       - \"to_system\" Convert to the system path separators\n"
    200     "         (backslashes on Windows).\n"
    201     "       - \"from_system\" Convert system path separators to forward\n"
    202     "         slashes.\n"
    203     "\n"
    204     "      On Posix systems there are no path separator transformations\n"
    205     "      applied. If the new_base is empty (specifying absolute output)\n"
    206     "      this parameter should not be supplied since paths will always be\n"
    207     "      converted,\n"
    208     "\n"
    209     "Return value\n"
    210     "\n"
    211     "  The return value will be the same type as the input value (either a\n"
    212     "  string or a list of strings). All relative and source-absolute file\n"
    213     "  names will be converted to be relative to the requested output\n"
    214     "  System-absolute paths will be unchanged.\n"
    215     "\n"
    216     "Example\n"
    217     "\n"
    218     "  # Convert a file in the current directory to be relative to the build\n"
    219     "  # directory (the current dir when executing compilers and scripts).\n"
    220     "  foo = rebase_path(\"myfile.txt\", \".\", root_build_dir)\n"
    221     "  # might produce \"../../project/myfile.txt\".\n"
    222     "\n"
    223     "  # Convert a file to be system absolute:\n"
    224     "  foo = rebase_path(\"myfile.txt\", \".\", \"\")\n"
    225     "  # Might produce \"D:\\source\\project\\myfile.txt\" on Windows or\n"
    226     "  # \"/home/you/source/project/myfile.txt\" on Linux.\n"
    227     "\n"
    228     "  # Convert a file's path separators from forward slashes to system\n"
    229     "  # slashes.\n"
    230     "  foo = rebase_path(\"source/myfile.txt\", \".\", \".\", \"to_system\")\n"
    231     "\n"
    232     "  # Typical usage for converting to the build directory for a script.\n"
    233     "  custom(\"myscript\") {\n"
    234     "    # Don't convert sources, GN will automatically convert these to be\n"
    235     "    # relative to the build directory when it contructs the command\n"
    236     "    # line for your script.\n"
    237     "    sources = [ \"foo.txt\", \"bar.txt\" ]\n"
    238     "\n"
    239     "    # Extra file args passed manually need to be explicitly converted\n"
    240     "    # to be relative to the build directory:\n"
    241     "    args = [\n"
    242     "      \"--data\",\n"
    243     "      rebase_path(\"//mything/data/input.dat\", \".\", root_build_dir),\n"
    244     "      \"--rel\",\n"
    245     "      rebase_path(\"relative_path.txt\", \".\", root_build_dir)\n"
    246     "    ]\n"
    247     "  }\n";
    248 
    249 Value RunRebasePath(Scope* scope,
    250                     const FunctionCallNode* function,
    251                     const std::vector<Value>& args,
    252                     Err* err) {
    253   Value result;
    254 
    255   // Inputs.
    256   if (args.size() != 3 && args.size() != 4) {
    257     *err = Err(function->function(), "rebase_path takes 3 or 4 args.");
    258     return result;
    259   }
    260   const Value& inputs = args[0];
    261 
    262   // From path.
    263   if (!args[1].VerifyTypeIs(Value::STRING, err))
    264     return result;
    265   const SourceDir& current_dir = scope->GetSourceDir();
    266   SourceDir from_dir = current_dir.ResolveRelativeDir(args[1].string_value());
    267 
    268   // To path.
    269   if (!args[2].VerifyTypeIs(Value::STRING, err))
    270     return result;
    271   bool convert_to_system_absolute = false;
    272   SourceDir to_dir;
    273   if (args[2].string_value().empty()) {
    274     convert_to_system_absolute = true;
    275   } else {
    276     to_dir = current_dir.ResolveRelativeDir(args[2].string_value());
    277   }
    278 
    279   // Path conversion.
    280   SeparatorConversion sep_conversion = SEP_NO_CHANGE;
    281   if (args.size() == 4) {
    282     if (convert_to_system_absolute) {
    283       *err = Err(function, "Can't specify slash conversion.",
    284           "You specified absolute system path output by using an empty string "
    285           "for the desination directory on rebase_path(). In this case, you "
    286           "can't specify slash conversion.");
    287       return result;
    288     }
    289 
    290     if (!args[3].VerifyTypeIs(Value::STRING, err))
    291       return result;
    292     const std::string& sep_string = args[3].string_value();
    293     if (sep_string == "to_system") {
    294       sep_conversion = SEP_TO_SYSTEM;
    295     } else if (sep_string == "from_system") {
    296       sep_conversion = SEP_FROM_SYSTEM;
    297     } else if (sep_string != "none") {
    298       *err = Err(args[3], "Invalid path separator conversion mode.",
    299           "I was expecting \"none\",  \"to_system\", or \"from_system\" and\n"
    300           "you gave me \"" + args[3].string_value() + "\".");
    301       return result;
    302     }
    303   }
    304 
    305   if (inputs.type() == Value::STRING) {
    306     return ConvertOnePath(scope, function, inputs,
    307                           from_dir, to_dir, convert_to_system_absolute,
    308                           sep_conversion, err);
    309 
    310   } else if (inputs.type() == Value::LIST) {
    311     result = Value(function, Value::LIST);
    312     result.list_value().reserve(inputs.list_value().size());
    313 
    314     for (size_t i = 0; i < inputs.list_value().size(); i++) {
    315       result.list_value().push_back(
    316           ConvertOnePath(scope, function, inputs.list_value()[i],
    317                          from_dir, to_dir, convert_to_system_absolute,
    318                          sep_conversion, err));
    319       if (err->has_error()) {
    320         result = Value();
    321         return result;
    322       }
    323     }
    324     return result;
    325   }
    326 
    327   *err = Err(function->function(),
    328              "rebase_path requires a list or a string.");
    329   return result;
    330 }
    331 
    332 }  // namespace functions
    333