aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/LoadStatement.java
blob: 33c98d30819a8b82512e087a22d58feb5f6334f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.syntax;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.syntax.compiler.DebugInfo;
import com.google.devtools.build.lib.syntax.compiler.LoopLabels;
import com.google.devtools.build.lib.syntax.compiler.VariableScope;
import com.google.devtools.build.lib.vfs.PathFragment;

import net.bytebuddy.implementation.bytecode.ByteCodeAppender;

import java.util.Map;

/**
 * Syntax node for an import statement.
 */
public final class LoadStatement extends Statement {

  public static final String PATH_ERROR_MSG = "Path '%s' is not valid. "
      + "It should either start with a slash or refer to a file in the current directory.";
  private final ImmutableMap<Identifier, String> symbols;
  private final ImmutableList<Identifier> cachedSymbols; // to save time
  private final PathFragment importPath;
  private final StringLiteral pathString;

  /**
   * Constructs an import statement.
   *
   * <p>Symbols maps a symbol to its original name under which it was defined in
   * the bzl file that should be loaded.
   * If aliasing is used, the value differs from it's key's symbol#getName().
   * Otherwise, both values are identical.
   */
  LoadStatement(StringLiteral path, Map<Identifier, String> symbols) {
    this.symbols = ImmutableMap.copyOf(symbols);
    this.cachedSymbols = ImmutableList.copyOf(symbols.keySet());
    this.importPath = new PathFragment(path.getValue() + ".bzl");
    this.pathString = path;
  }

  public ImmutableList<Identifier> getSymbols() {
    return cachedSymbols;
  }

  public PathFragment getImportPath() {
    return importPath;
  }

  @Override
  public String toString() {
    return String.format("load(\"%s\", %s)", importPath, Joiner.on(", ").join(cachedSymbols));
  }

  @Override
  void doExec(Environment env) throws EvalException, InterruptedException {
    for (Map.Entry<Identifier, String> entry : symbols.entrySet()) {
      try {
        Identifier current = entry.getKey();

        if (current.isPrivate()) {
          throw new EvalException(
              getLocation(), "symbol '" + current + "' is private and cannot be imported");
        }
        // The key is the original name that was used to define the symbol
        // in the loaded bzl file
        env.importSymbol(getImportPath(), current, entry.getValue());
      } catch (Environment.NoSuchVariableException | Environment.LoadFailedException e) {
        throw new EvalException(getLocation(), e.getMessage());
      }
    }
  }

  @Override
  public void accept(SyntaxTreeVisitor visitor) {
    visitor.visit(this);
  }

  @Override
  void validate(ValidationEnvironment env) throws EvalException {
    validatePath();

    if (!importPath.isAbsolute() && importPath.segmentCount() > 1) {
      throw new EvalException(getLocation(), String.format(PATH_ERROR_MSG, importPath));
    }
    for (Identifier symbol : cachedSymbols) {
      env.declare(symbol.getName(), getLocation());
    }
  }

  public StringLiteral getPath() {
    return pathString;
  }

  /**
   * Throws an exception if the path argument to load() starts with more than one forward
   * slash ('/')
   */
  public void validatePath() throws EvalException {
    String error = null;

    if (pathString.getValue().isEmpty()) {
      error = "Path argument to load() must not be empty";
    } else if (pathString.getValue().startsWith("//")) {
      error =
          "First argument of load() is a path, not a label. "
          + "It should start with a single slash if it is an absolute path.";
    }

    if (error != null) {
      throw new EvalException(getLocation(), error);
    }
  }

  @Override
  ByteCodeAppender compile(
      VariableScope scope, Optional<LoopLabels> loopLabels, DebugInfo debugInfo) {
    throw new UnsupportedOperationException(
        "load statements should never appear in method bodies and"
            + " should never be compiled in general");
  }
}