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() <= 2) {
     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(const SourceDir& dir, const base::StringPiece& name)
    204     : dir_(dir) {
    205   name_.assign(name.data(), name.size());
    206 }
    207 
    208 Label::~Label() {
    209 }
    210 
    211 // static
    212 Label Label::Resolve(const SourceDir& current_dir,
    213                      const Label& current_toolchain,
    214                      const Value& input,
    215                      Err* err) {
    216   Label ret;
    217   if (input.type() != Value::STRING) {
    218     *err = Err(input, "Dependency is not a string.");
    219     return ret;
    220   }
    221   const std::string& input_string = input.string_value();
    222   if (input_string.empty()) {
    223     *err = Err(input, "Dependency string is empty.");
    224     return ret;
    225   }
    226 
    227   if (!::Resolve(current_dir, current_toolchain, input, input_string,
    228                  &ret.dir_, &ret.name_,
    229                  &ret.toolchain_dir_, &ret.toolchain_name_,
    230                  err))
    231     return Label();
    232   return ret;
    233 }
    234 
    235 Label Label::GetToolchainLabel() const {
    236   return Label(toolchain_dir_, toolchain_name_);
    237 }
    238 
    239 Label Label::GetWithNoToolchain() const {
    240   return Label(dir_, name_);
    241 }
    242 
    243 std::string Label::GetUserVisibleName(bool include_toolchain) const {
    244   std::string ret;
    245   ret.reserve(dir_.value().size() + name_.size() + 1);
    246 
    247   if (dir_.is_null())
    248     return ret;
    249 
    250   ret = DirWithNoTrailingSlash(dir_);
    251   ret.push_back(':');
    252   ret.append(name_);
    253 
    254   if (include_toolchain) {
    255     ret.push_back('(');
    256     if (!toolchain_dir_.is_null() && !toolchain_name_.empty()) {
    257       ret.append(DirWithNoTrailingSlash(toolchain_dir_));
    258       ret.push_back(':');
    259       ret.append(toolchain_name_);
    260     }
    261     ret.push_back(')');
    262   }
    263   return ret;
    264 }
    265 
    266 std::string Label::GetUserVisibleName(const Label& default_toolchain) const {
    267   bool include_toolchain =
    268       default_toolchain.dir() != toolchain_dir_ ||
    269       default_toolchain.name() != toolchain_name_;
    270   return GetUserVisibleName(include_toolchain);
    271 }
    272