root/tools/gn/visibility.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. dir_
  2. Matches
  3. Set
  4. SetPublic
  5. SetPrivate
  6. CanSeeMe
  7. Describe
  8. GetPattern
  9. CheckItemVisibility
  10. FillItemVisibility

// Copyright 2014 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/visibility.h"

#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "tools/gn/err.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/item.h"
#include "tools/gn/label.h"
#include "tools/gn/scope.h"
#include "tools/gn/value.h"
#include "tools/gn/variables.h"

Visibility::VisPattern::VisPattern() : type_(MATCH) {
}

Visibility::VisPattern::VisPattern(Type type,
                                   const SourceDir& dir,
                                   const base::StringPiece& name)
    : type_(type),
      dir_(dir) {
  name.CopyToString(&name_);
}

Visibility::VisPattern::~VisPattern() {
}

bool Visibility::VisPattern::Matches(const Label& label) const {
  switch (type_) {
    case MATCH:
      return label.name() == name_ && label.dir() == dir_;
    case DIRECTORY:
      // The directories must match exactly for private visibility.
      return label.dir() == dir_;
    case RECURSIVE_DIRECTORY:
      // Our directory must be a prefix of the input label for recursive
      // private visibility.
      return label.dir().value().compare(0, dir_.value().size(), dir_.value())
          == 0;
    default:
      NOTREACHED();
      return false;
  }
}

Visibility::Visibility() {
}

Visibility::~Visibility() {
}

bool Visibility::Set(const SourceDir& current_dir,
                     const Value& value,
                     Err* err) {
  patterns_.clear();

  // Allow a single string to be passed in to make the common case (just one
  // pattern) easier to specify.
  if (value.type() == Value::STRING) {
    patterns_.push_back(GetPattern(current_dir, value, err));
    return !err->has_error();
  }

  // If it's not a string, it should be a list of strings.
  if (!value.VerifyTypeIs(Value::LIST, err))
    return false;

  const std::vector<Value>& list = value.list_value();
  for (size_t i = 0; i < list.size(); i++) {
    patterns_.push_back(GetPattern(current_dir, list[i], err));
    if (err->has_error())
      return false;
  }
  return true;
}

void Visibility::SetPublic() {
  patterns_.push_back(
      VisPattern(VisPattern::RECURSIVE_DIRECTORY, SourceDir(), std::string()));
}

void Visibility::SetPrivate(const SourceDir& current_dir) {
  patterns_.push_back(
      VisPattern(VisPattern::DIRECTORY, current_dir, std::string()));
}

bool Visibility::CanSeeMe(const Label& label) const {
  for (size_t i = 0; i < patterns_.size(); i++) {
    if (patterns_[i].Matches(label))
      return true;
  }
  return false;
}

std::string Visibility::Describe() const {
  if (patterns_.empty())
    return std::string("[] (no visibility)");
  std::string result = "[\n";

  for (size_t i = 0; i < patterns_.size(); i++) {
    switch (patterns_[i].type()) {
      case VisPattern::MATCH:
        result += "  " + patterns_[i].dir().value() + ":" +
            patterns_[i].name() + "\n";
        break;
      case VisPattern::DIRECTORY:
        result += "  " + DirectoryWithNoLastSlash(patterns_[i].dir()) +
            ":*\n";
        break;
      case VisPattern::RECURSIVE_DIRECTORY:
        result += "  " + patterns_[i].dir().value() + "*\n";
        break;
    }
  }

  result += "]";
  return result;
}

// static
Visibility::VisPattern Visibility::GetPattern(const SourceDir& current_dir,
                                              const Value& value,
                                              Err* err) {
  if (!value.VerifyTypeIs(Value::STRING, err))
    return VisPattern();

  const std::string& str = value.string_value();
  if (str.empty()) {
    *err = Err(value, "Visibility pattern must not be empty.");
    return VisPattern();
  }

  // If there's no wildcard, this is specifying an exact label, use the
  // label resolution code to get all the implicit name stuff.
  size_t star = str.find('*');
  if (star == std::string::npos) {
    Label label = Label::Resolve(current_dir, Label(), value, err);
    if (err->has_error())
      return VisPattern();

    // There should be no toolchain specified.
    if (!label.toolchain_dir().is_null() || !label.toolchain_name().empty()) {
      *err = Err(value, "Visibility label specified a toolchain.",
          "Visibility names and patterns don't use toolchains, erase the\n"
          "stuff in the ().");
      return VisPattern();
    }

    return VisPattern(VisPattern::MATCH, label.dir(), label.name());
  }

  // Wildcard case, need to split apart the label to see what it specifies.
  base::StringPiece path;
  base::StringPiece name;
  size_t colon = str.find(':');
  if (colon == std::string::npos) {
    path = base::StringPiece(str);
  } else {
    path = base::StringPiece(&str[0], colon);
    name = base::StringPiece(&str[colon + 1], str.size() - colon - 1);
  }

  // The path can have these forms:
  //   1. <empty>  (use current dir)
  //   2. <non wildcard stuff>  (send through directory resolution)
  //   3. <non wildcard stuff>*  (send stuff through dir resolution, note star)
  //   4. *  (matches anything)
  SourceDir dir;
  bool has_path_star = false;
  if (path.empty()) {
    // Looks like ":foo".
    dir = current_dir;
  } else if (path[path.size() - 1] == '*') {
    // Case 3 or 4 above.
    has_path_star = true;

    // Adjust path to contain everything but the star.
    path = path.substr(0, path.size() - 1);

    if (!path.empty() && path[path.size() - 1] != '/') {
      // The input was "foo*" which is invalid.
      *err = Err(value, "'*' must match full directories in visibility.",
          "You did \"foo*\" but visibility doesn't do general pattern\n"
          "matching. Instead, you have to add a slash: \"foo/*\".");
      return VisPattern();
    }
  }

  // Resolve the part of the path that's not the wildcard.
  if (!path.empty()) {
    // The non-wildcard stuff better not have a wildcard.
    if (path.find('*') != base::StringPiece::npos) {
      *err = Err(value, "Visibility patterns only support wildcard suffixes.",
          "The visibility pattern contained a '*' that wasn't at tne end.");
      return VisPattern();
    }

    // Resolve the non-wildcard stuff.
    dir = current_dir.ResolveRelativeDir(path);
    if (dir.is_null()) {
      *err = Err(value, "Visibility pattern didn't resolve to a dir.",
          "The directory name \"" + path.as_string() + "\" didn't\n"
          "resolve to a directory.");
      return VisPattern();
    }
  }

  // Resolve the name. At this point, we're doing wildcard matches so the
  // name should either be empty ("foo/*") or a wildcard ("foo:*");
  if (colon != std::string::npos && name != "*") {
    *err = Err(value, "Invalid visibility pattern.",
        "You seem to be using the wildcard more generally that is supported.\n"
        "Did you mean \"foo:*\" to match everything in the current file, or\n"
        "\"./*\" to recursively match everything in the currend subtree.");
    return VisPattern();
  }

  VisPattern::Type type;
  if (has_path_star) {
    // We know there's a wildcard, so if the name is empty it looks like
    // "foo/*".
    type = VisPattern::RECURSIVE_DIRECTORY;
  } else {
    // Everything else should be of the form "foo:*".
    type = VisPattern::DIRECTORY;
  }

  // When we're doing wildcard matching, the name is always empty.
  return VisPattern(type, dir, base::StringPiece());
}

// static
bool Visibility::CheckItemVisibility(const Item* from,
                                     const Item* to,
                                     Err* err) {
  if (!to->visibility().CanSeeMe(from->label())) {
    std::string to_label = to->label().GetUserVisibleName(false);
    *err = Err(from->defined_from(), "Dependency not allowed.",
        "The item " + from->label().GetUserVisibleName(false) + "\n"
        "can not depend on " + to_label + "\n"
        "because it is not in " + to_label + "'s visibility list: " +
        to->visibility().Describe());
    return false;
  }
  return true;
}

// static
bool Visibility::FillItemVisibility(Item* item, Scope* scope, Err* err) {
  const Value* vis_value = scope->GetValue(variables::kVisibility, true);
  if (vis_value)
    item->visibility().Set(scope->GetSourceDir(), *vis_value, err);
  else  // Default to public.
    item->visibility().SetPublic();
  return !err->has_error();
}

/* [<][>][^][v][top][bottom][index][help] */