aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/packages/util/SubincludePreprocessor.java
blob: 06d350a6170afc77b2c066b9fb0b3908bf7e4440 (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright 2015 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.packages.util;

import com.google.common.primitives.Chars;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.CachingPackageLocator;
import com.google.devtools.build.lib.packages.Globber;
import com.google.devtools.build.lib.packages.Preprocessor;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Expands subinclude() statements, and returns an error if ERROR is
 * present in the end-result.  It does not run python, and is intended
 * for testing
 */
public class SubincludePreprocessor implements Preprocessor {
  /** Creates SubincludePreprocessor factories. */
  public static class FactorySupplier implements Preprocessor.Factory.Supplier {
    @Override
    public Factory getFactory(CachingPackageLocator loc, Path outputBase) {
      final SubincludePreprocessor preprocessor =
          new SubincludePreprocessor(outputBase.getFileSystem(), loc);
      return new Factory() {
        @Override
        public boolean isStillValid() {
          return true;
        }

        @Override
        public boolean considersGlobs() {
          return false;
        }

        @Override
        public Preprocessor getPreprocessor() {
          return preprocessor;
        }
      };
    }
  }

  private static final Pattern SUBINCLUDE_REGEX =
      Pattern.compile("\\bsubinclude\\(['\"]([^'\"=]*)['\"]\\)", Pattern.MULTILINE);
  public static final String TRANSIENT_ERROR = "TRANSIENT_ERROR";

  private final FileSystem fileSystem;
  private final CachingPackageLocator packageLocator;

  /**
   * Constructs a SubincludePreprocessor using the specified package
   * path for resolving subincludes.
   */
  public SubincludePreprocessor(FileSystem fileSystem, CachingPackageLocator packageLocator) {
    this.fileSystem = fileSystem;
    this.packageLocator = packageLocator;
  }

  // Cut & paste from PythonPreprocessor#resolveSubinclude.
  public String resolveSubinclude(String labelString) throws IOException {
    Label label;
    try {
      label = Label.parseAbsolute(labelString);
    } catch (LabelSyntaxException e) {
      throw new IOException("Cannot parse label: '" + labelString + "'");
    }

    Path buildFile = packageLocator.getBuildFileForPackage(label.getPackageIdentifier());
    if (buildFile == null) {
      return "";
    }

    Path subinclude = buildFile.getParentDirectory().getRelative(new PathFragment(label.getName()));
    return subinclude.getPathString();
  }

  @Override
  public Preprocessor.Result preprocess(
      Path buildFilePath,
      byte[] buildFileBytes,
      String packageName,
      Globber globber,
      Environment.Frame globals,
      Set<String> ruleNames)
      throws IOException, InterruptedException {
    StoredEventHandler eventHandler = new StoredEventHandler();
    char content[] = FileSystemUtils.convertFromLatin1(buildFileBytes);
    while (true) {
      Matcher matcher = SUBINCLUDE_REGEX.matcher(CharBuffer.wrap(content));
      if (!matcher.find()) {
        break;
      }
      String name = matcher.group(1);
      String path = resolveSubinclude(name);

      char subContent[];
      if (path.isEmpty()) {
        // This location is not correct, but will do for testing purposes.
        eventHandler.handle(
            Event.error(
                Location.fromFile(buildFilePath), "Cannot find subincluded file \'" + name + "\'"));
        // Emit a mocksubinclude(), so we know to preprocess again if the file becomes
        // visible. We cannot fail the preprocess here, as it would drop the content.
        subContent = new char[0];
      } else {
        // TODO(bazel-team): figure out the correct behavior for a non-existent file from an
        // existent package.
        subContent = FileSystemUtils.readContentAsLatin1(fileSystem.getPath(path));
      }

      String mock = "\nmocksubinclude('" + name + "', '" + path + "')\n";

      content =
          Chars.concat(
              Arrays.copyOf(content, matcher.start()),
              mock.toCharArray(),
              subContent,
              Arrays.copyOfRange(content, matcher.end(), content.length));
    }

    if (Chars.indexOf(content, TRANSIENT_ERROR.toCharArray()) >= 0) {
      throw new IOException("transient error requested in " + buildFilePath.asFragment());
    }

    return Preprocessor.Result.success(
        ParserInputSource.create(content, buildFilePath.asFragment()),
        eventHandler.hasErrors(),
        eventHandler.getEvents());
  }
}