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/label.h"
      6 
      7 #include "base/logging.h"
      8 #include "tools/gn/err.h"
      9 #include "tools/gn/parse_tree.h"
     10 #include "tools/gn/value.h"
     11 
     12 namespace {
     13 
     14 // We print user visible label names with no trailing slash after the
     15 // directory name.
     16 std::string DirWithNoTrailingSlash(const SourceDir& dir) {
     17   // Be careful not to trim if the input is just "/" or "//".
     18   if (dir.value().size() > 2)
     19     return dir.value().substr(0, dir.value().size() - 1);
     20   return dir.value();
     21 }
     22 
     23 // Given the separate-out input (everything before the colon) in the dep rule,
     24 // computes the final build rule. Sets err on failure. On success,
     25 // |*used_implicit| will be set to whether the implicit current directory was
     26 // used. The value is used only for generating error messages.
     27 bool ComputeBuildLocationFromDep(const Value& input_value,
     28                                  const SourceDir& current_dir,
     29                                  const base::StringPiece& input,
     30                                  SourceDir* result,
     31                                  Err* err) {
     32   // No rule, use the current locaton.
     33   if (input.empty()) {
     34     *result = current_dir;
     35     return true;
     36   }
     37 
     38   // Don't allow directories to start with a single slash. All labels must be
     39   // in the source root.
     40   if (input[0] == '/' && (input.size() == 1 || input[1] != '/')) {
     41     *err = Err(input_value, "Label can't start with a single slash",
     42         "Labels must be either relative (no slash at the beginning) or be "
     43         "absolute\ninside the source root (two slashes at the beginning).");
     44     return false;
     45   }
     46 
     47   *result = current_dir.ResolveRelativeDir(input);
     48   return true;
     49 }
     50 
     51 // Given the separated-out target name (after the colon) computes the final
     52 // name, using the implicit name from the previously-generated
     53 // computed_location if necessary. The input_value is used only for generating
     54 // error messages.
     55 bool ComputeTargetNameFromDep(const Value& input_value,
     56                               const SourceDir& computed_location,
     57                               const base::StringPiece& input,
     58                               std::string* result,
     59                               Err* err) {
     60   if (!input.empty()) {
     61     // Easy case: input is specified, just use it.
     62     result->assign(input.data(), input.size());
     63     return true;
     64   }
     65 
     66   const std::string& loc = computed_location.value();
     67 
     68   // Use implicit name. The path will be "//", "//base/", "//base/i18n/", etc.
     69   if (loc.size() <= 1) {
     70     *err = Err(input_value, "This dependency name is empty");
     71     return false;
     72   }
     73 
     74   size_t next_to_last_slash = loc.rfind('/', loc.size() - 2);
     75   DCHECK(next_to_last_slash != std::string::npos);
     76   result->assign(&loc[next_to_last_slash + 1],
     77                  loc.size() - next_to_last_slash - 2);
     78   return true;
     79 }
     80 
     81 // The original value is used only for error reporting, use the |input| as the
     82 // input to this function (which may be a substring of the original value when
     83 // we're parsing toolchains.
     84 //
     85 // If the output toolchain vars are NULL, then we'll report an error if we
     86 // find a toolchain specified (this is used when recursively parsing toolchain
     87 // labels which themselves can't have toolchain specs).
     88 //
     89 // We assume that the output variables are initialized to empty so we don't
     90 // write them unless we need them to contain something.
     91 //
     92 // Returns true on success. On failure, the out* variables might be written to
     93 // but shouldn't be used.
     94 bool Resolve(const SourceDir& current_dir,
     95              const Label& current_toolchain,
     96              const Value& original_value,
     97              const base::StringPiece& input,
     98              SourceDir* out_dir,
     99              std::string* out_name,
    100              SourceDir* out_toolchain_dir,
    101              std::string* out_toolchain_name,
    102              Err* err) {
    103   // To workaround the problem that StringPiece operator[] doesn't return a ref.
    104   const char* input_str = input.data();
    105 
    106   size_t path_separator = input.find_first_of(":(");
    107   base::StringPiece location_piece;
    108   base::StringPiece name_piece;
    109   base::StringPiece toolchain_piece;
    110   if (path_separator == std::string::npos) {
    111     location_piece = input;
    112     // Leave name & toolchain piece null.
    113   } else {
    114     location_piece = base::StringPiece(&input_str[0], path_separator);
    115 
    116     size_t toolchain_separator = input.find('(', path_separator);
    117     if (toolchain_separator == std::string::npos) {
    118       name_piece = base::StringPiece(&input_str[path_separator + 1],
    119                                      input.size() - path_separator - 1);
    120       // Leave location piece null.
    121     } else if (!out_toolchain_dir) {
    122       // Toolchain specified but not allows in this context.
    123       *err = Err(original_value, "Toolchain has a toolchain.",
    124           "Your toolchain definition (inside the parens) seems to itself "
    125           "have a\ntoolchain. Don't do this.");
    126       return false;
    127     } else {
    128       // Name piece is everything between the two separators. Note that the
    129       // separators may be the same (e.g. "//foo(bar)" which means empty name.
    130       if (toolchain_separator > path_separator) {
    131         name_piece = base::StringPiece(
    132             &input_str[path_separator + 1],
    133             toolchain_separator - path_separator - 1);
    134       }
    135 
    136       // Toolchain name should end in a ) and this should be the end of the
    137       // string.
    138       if (input[input.size() - 1] != ')') {
    139         *err = Err(original_value, "Bad toolchain name.",
    140             "Toolchain name must end in a \")\" at the end of the label.");
    141         return false;
    142       }
    143 
    144       // Subtract off the two parens to just get the toolchain name.
    145       toolchain_piece = base::StringPiece(
    146           &input_str[toolchain_separator + 1],
    147           input.size() - toolchain_separator - 2);
    148     }
    149   }
    150 
    151   // Everything before the separator is the filename.
    152   // We allow three cases:
    153   //   Absolute:                "//foo:bar" -> /foo:bar
    154   //   Target in current file:  ":foo"     -> <currentdir>:foo
    155   //   Path with implicit name: "/foo"     -> /foo:foo
    156   if (location_piece.empty() && name_piece.empty()) {
    157     // Can't use both implicit filename and name (":").
    158     *err = Err(original_value, "This doesn't specify a dependency.");
    159     return false;
    160   }
    161 
    162   if (!ComputeBuildLocationFromDep(original_value, current_dir, location_piece,
    163                                    out_dir, err))
    164     return false;
    165 
    166   if (!ComputeTargetNameFromDep(original_value, *out_dir, name_piece,
    167                                 out_name, err))
    168     return false;
    169 
    170   // Last, do the toolchains.
    171   if (out_toolchain_dir) {
    172     // Handle empty toolchain strings. We don't allow normal labels to be
    173     // empty so we can't allow the recursive call of this function to do this
    174     // check.
    175     if (toolchain_piece.empty()) {
    176       *out_toolchain_dir = current_toolchain.dir();
    177       *out_toolchain_name = current_toolchain.name();
    178       return true;
    179     } else {
    180       return Resolve(current_dir, current_toolchain,
    181                      original_value, toolchain_piece,
    182                      out_toolchain_dir, out_toolchain_name, NULL, NULL, err);
    183     }
    184   }
    185   return true;
    186 }
    187 
    188 }  // namespace
    189 
    190 Label::Label() {
    191 }
    192 
    193 Label::Label(const SourceDir& dir,
    194              const base::StringPiece& name,
    195              const SourceDir& toolchain_dir,
    196              const base::StringPiece& toolchain_name)
    197     : dir_(dir),
    198       toolchain_dir_(toolchain_dir) {
    199   name_.assign(name.data(), name.size());
    200   toolchain_name_.assign(toolchain_name.data(), toolchain_name.size());
    201 }
    202 
    203 Label::~Label() {
    204 }
    205 
    206 // static
    207 Label Label::Resolve(const SourceDir& current_dir,
    208                      const Label& current_toolchain,
    209                      const Value& input,
    210                      Err* err) {
    211   Label ret;
    212   if (input.type() != Value::STRING) {
    213     *err = Err(input, "Dependency is not a string.");
    214     return ret;
    215   }
    216   const std::string& input_string = input.string_value();
    217   if (input_string.empty()) {
    218     *err = Err(input, "Dependency string is empty.");
    219     return ret;
    220   }
    221 
    222   if (!::Resolve(current_dir, current_toolchain, input, input_string,
    223                  &ret.dir_, &ret.name_,
    224                  &ret.toolchain_dir_, &ret.toolchain_name_,
    225                  err))
    226     return Label();
    227   return ret;
    228 }
    229 
    230 Label Label::GetToolchainLabel() const {
    231   return Label(toolchain_dir_, toolchain_name_,
    232                SourceDir(), base::StringPiece());
    233 }
    234 
    235 std::string Label::GetUserVisibleName(bool include_toolchain) const {
    236   std::string ret;
    237   ret.reserve(dir_.value().size() + name_.size() + 1);
    238 
    239   if (dir_.is_null())
    240     return ret;
    241 
    242   ret = DirWithNoTrailingSlash(dir_);
    243   ret.push_back(':');
    244   ret.append(name_);
    245 
    246   if (include_toolchain) {
    247     ret.push_back('(');
    248     if (!toolchain_dir_.is_null() && !toolchain_name_.empty()) {
    249       ret.append(DirWithNoTrailingSlash(toolchain_dir_));
    250       ret.push_back(':');
    251       ret.append(toolchain_name_);
    252     }
    253     ret.push_back(')');
    254   }
    255   return ret;
    256 }
    257 
    258 std::string Label::GetUserVisibleName(const Label& default_toolchain) const {
    259   bool include_toolchain =
    260       default_toolchain.dir() != toolchain_dir_ ||
    261       default_toolchain.name() != toolchain_name_;
    262   return GetUserVisibleName(include_toolchain);
    263 }
    264