aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
blob: 17ab4416fd96e0bb7167e7b665fa0ee6125cf313 (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
// 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.analysis;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactFactory;
import com.google.devtools.build.lib.actions.ArtifactOwner;
import com.google.devtools.build.lib.actions.FailAction;
import com.google.devtools.build.lib.actions.Root;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.ConfigurationFragmentPolicy;
import com.google.devtools.build.lib.packages.ConfigurationFragmentPolicy.MissingFragmentPolicy;
import com.google.devtools.build.lib.packages.ConstantRuleVisibility;
import com.google.devtools.build.lib.packages.EnvironmentGroup;
import com.google.devtools.build.lib.packages.InputFile;
import com.google.devtools.build.lib.packages.OutputFile;
import com.google.devtools.build.lib.packages.PackageGroup;
import com.google.devtools.build.lib.packages.PackageGroupsRuleVisibility;
import com.google.devtools.build.lib.packages.PackageSpecification;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.packages.RuleVisibility;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.rules.SkylarkRuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.rules.fileset.FilesetProvider;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
import com.google.devtools.build.lib.util.OrderedSetMultimap;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * This class creates {@link ConfiguredTarget} instances using a given {@link
 * ConfiguredRuleClassProvider}.
 */
@ThreadSafe
public final class ConfiguredTargetFactory {
  // This class is not meant to be outside of the analysis phase machinery and is only public
  // in order to be accessible from the .view.skyframe package.

  private final ConfiguredRuleClassProvider ruleClassProvider;

  public ConfiguredTargetFactory(ConfiguredRuleClassProvider ruleClassProvider) {
    this.ruleClassProvider = ruleClassProvider;
  }

  /**
   * Returns the visibility of the given target. Errors during package group resolution are reported
   * to the {@code AnalysisEnvironment}.
   */
  private NestedSet<PackageSpecification> convertVisibility(
      OrderedSetMultimap<Attribute, ConfiguredTarget> prerequisiteMap, EventHandler reporter,
      Target target, BuildConfiguration packageGroupConfiguration) {
    RuleVisibility ruleVisibility = target.getVisibility();
    if (ruleVisibility instanceof ConstantRuleVisibility) {
      return ((ConstantRuleVisibility) ruleVisibility).isPubliclyVisible()
          ? NestedSetBuilder.<PackageSpecification>create(
              Order.STABLE_ORDER, PackageSpecification.everything())
          : NestedSetBuilder.<PackageSpecification>emptySet(Order.STABLE_ORDER);
    } else if (ruleVisibility instanceof PackageGroupsRuleVisibility) {
      PackageGroupsRuleVisibility packageGroupsVisibility =
          (PackageGroupsRuleVisibility) ruleVisibility;

      NestedSetBuilder<PackageSpecification> packageSpecifications =
          NestedSetBuilder.stableOrder();
      for (Label groupLabel : packageGroupsVisibility.getPackageGroups()) {
        // PackageGroupsConfiguredTargets are always in the package-group configuration.
        ConfiguredTarget group =
            findPrerequisite(prerequisiteMap, groupLabel, packageGroupConfiguration);
        PackageSpecificationProvider provider = null;
        // group == null can only happen if the package group list comes
        // from a default_visibility attribute, because in every other case,
        // this missing link is caught during transitive closure visitation or
        // if the RuleConfiguredTargetGraph threw out a visibility edge
        // because if would have caused a cycle. The filtering should be done
        // in a single place, ConfiguredTargetGraph, but for now, this is the
        // minimally invasive way of providing a sane error message in case a
        // cycle is created by a visibility attribute.
        if (group != null) {
          provider = group.getProvider(PackageSpecificationProvider.class);
        }
        if (provider != null) {
          packageSpecifications.addTransitive(provider.getPackageSpecifications());
        } else {
          reporter.handle(Event.error(target.getLocation(),
              String.format("Label '%s' does not refer to a package group", groupLabel)));
        }
      }

      packageSpecifications.addAll(packageGroupsVisibility.getDirectPackages());
      return packageSpecifications.build();
    } else {
      throw new IllegalStateException("unknown visibility");
    }
  }

  private ConfiguredTarget findPrerequisite(
      OrderedSetMultimap<Attribute, ConfiguredTarget> prerequisiteMap, Label label,
      BuildConfiguration config) {
    for (ConfiguredTarget prerequisite : prerequisiteMap.get(null)) {
      if (prerequisite.getLabel().equals(label) && (prerequisite.getConfiguration() == config)) {
        return prerequisite;
      }
    }
    return null;
  }

  private Artifact getOutputArtifact(OutputFile outputFile, BuildConfiguration configuration,
      boolean isFileset, ArtifactFactory artifactFactory) {
    Rule rule = outputFile.getAssociatedRule();
    Root root = rule.hasBinaryOutput()
        ? configuration.getBinDirectory(rule.getRepository())
        : configuration.getGenfilesDirectory(rule.getRepository());
    ArtifactOwner owner =
        new ConfiguredTargetKey(rule.getLabel(), configuration.getArtifactOwnerConfiguration());
    PathFragment rootRelativePath = outputFile.getLabel().toPathFragment();
    Artifact result = isFileset
        ? artifactFactory.getFilesetArtifact(rootRelativePath, root, owner)
        : artifactFactory.getDerivedArtifact(rootRelativePath, root, owner);
    // The associated rule should have created the artifact.
    Preconditions.checkNotNull(result, "no artifact for %s", rootRelativePath);
    return result;
  }

  /**
   * Invokes the appropriate constructor to create a {@link ConfiguredTarget} instance.
   * <p>For use in {@code ConfiguredTargetFunction}.
   *
   * <p>Returns null if Skyframe deps are missing or upon certain errors.
   */
  @Nullable
  public final ConfiguredTarget createConfiguredTarget(AnalysisEnvironment analysisEnvironment,
      ArtifactFactory artifactFactory, Target target, BuildConfiguration config,
      BuildConfiguration hostConfig,
      OrderedSetMultimap<Attribute, ConfiguredTarget> prerequisiteMap,
      ImmutableMap<Label, ConfigMatchingProvider> configConditions)
      throws InterruptedException {
    if (target instanceof Rule) {
      return createRule(analysisEnvironment, (Rule) target, config, hostConfig,
          prerequisiteMap, configConditions);
    }

    // Visibility, like all package groups, doesn't have a configuration
    NestedSet<PackageSpecification> visibility = convertVisibility(
        prerequisiteMap, analysisEnvironment.getEventHandler(), target, null);
    TargetContext targetContext = new TargetContext(analysisEnvironment, target, config,
        prerequisiteMap.get(null), visibility);
    if (target instanceof OutputFile) {
      OutputFile outputFile = (OutputFile) target;
      boolean isFileset = outputFile.getGeneratingRule().getRuleClass().equals("Fileset");
      Artifact artifact = getOutputArtifact(outputFile, config, isFileset, artifactFactory);
      TransitiveInfoCollection rule = targetContext.findDirectPrerequisite(
          outputFile.getGeneratingRule().getLabel(), config);
      if (isFileset) {
        return new FilesetOutputConfiguredTarget(
            targetContext,
            outputFile,
            rule,
            artifact,
            rule.getProvider(FilesetProvider.class).getTraversals());
      } else {
        return new OutputFileConfiguredTarget(targetContext, outputFile, rule, artifact);
      }
    } else if (target instanceof InputFile) {
      InputFile inputFile = (InputFile) target;
      Artifact artifact = artifactFactory.getSourceArtifact(
          inputFile.getLabel().toPathFragment(),
          Root.asSourceRoot(inputFile.getPackage().getSourceRoot(),
              inputFile.getPackage().getPackageIdentifier().getRepository().isMain()),
          new ConfiguredTargetKey(target.getLabel(), config));

      return new InputFileConfiguredTarget(targetContext, inputFile, artifact);
    } else if (target instanceof PackageGroup) {
      PackageGroup packageGroup = (PackageGroup) target;
      return new PackageGroupConfiguredTarget(targetContext, packageGroup);
    } else if (target instanceof EnvironmentGroup) {
      return new EnvironmentGroupConfiguredTarget(targetContext, (EnvironmentGroup) target);
    } else {
      throw new AssertionError("Unexpected target class: " + target.getClass().getName());
    }
  }

  /**
   * Factory method: constructs a RuleConfiguredTarget of the appropriate class, based on the rule
   * class. May return null if an error occurred.
   */
  @Nullable
  private ConfiguredTarget createRule(
      AnalysisEnvironment env, Rule rule, BuildConfiguration configuration,
      BuildConfiguration hostConfiguration,
      OrderedSetMultimap<Attribute, ConfiguredTarget> prerequisiteMap,
      ImmutableMap<Label, ConfigMatchingProvider> configConditions) throws InterruptedException {
    // Visibility computation and checking is done for every rule.
    RuleContext ruleContext =
        new RuleContext.Builder(
                env,
                rule,
                null,
                configuration,
                hostConfiguration,
                ruleClassProvider.getPrerequisiteValidator(),
                rule.getRuleClassObject().getConfigurationFragmentPolicy())
            .setVisibility(convertVisibility(prerequisiteMap, env.getEventHandler(), rule, null))
            .setPrerequisites(prerequisiteMap)
            .setConfigConditions(configConditions)
            .setUniversalFragment(ruleClassProvider.getUniversalFragment())
            .setSkylarkProvidersRegistry(ruleClassProvider.getRegisteredSkylarkProviders())
            .build();
    if (ruleContext.hasErrors()) {
      return null;
    }
    ConfigurationFragmentPolicy configurationFragmentPolicy =
        rule.getRuleClassObject().getConfigurationFragmentPolicy();

    MissingFragmentPolicy missingFragmentPolicy =
        configurationFragmentPolicy.getMissingFragmentPolicy();
    if (missingFragmentPolicy != MissingFragmentPolicy.IGNORE
        && !configuration.hasAllFragments(
            configurationFragmentPolicy.getRequiredConfigurationFragments())) {
      if (missingFragmentPolicy == MissingFragmentPolicy.FAIL_ANALYSIS) {
        ruleContext.ruleError(missingFragmentError(ruleContext, configurationFragmentPolicy));
        return null;
      }
      // Otherwise missingFragmentPolicy == MissingFragmentPolicy.CREATE_FAIL_ACTIONS:
      return createFailConfiguredTarget(ruleContext);
    }

    if (rule.getRuleClassObject().isSkylark()) {
      // TODO(bazel-team): maybe merge with RuleConfiguredTargetBuilder?
      return SkylarkRuleConfiguredTargetBuilder.buildRule(
          ruleContext,
          rule.getRuleClassObject().getConfiguredTargetFunction(),
          ruleClassProvider.getRegisteredSkylarkProviders());
    } else {
      RuleClass.ConfiguredTargetFactory<ConfiguredTarget, RuleContext> factory =
          rule.getRuleClassObject().<ConfiguredTarget, RuleContext>getConfiguredTargetFactory();
      Preconditions.checkNotNull(factory, rule.getRuleClassObject());
      try {
        return factory.create(ruleContext);
      } catch (RuleErrorException ruleErrorException) {
        // Returning null in this method is an indication an error occurred. Exceptions are not
        // propagated, as this would show a nasty stack trace to users, and only provide info
        // on one specific failure with poor messaging. By returning null, the caller can
        // inspect ruleContext for multiple errors and output thorough messaging on each.
        return null;
      }
    }
  }

  private String missingFragmentError(
      RuleContext ruleContext, ConfigurationFragmentPolicy configurationFragmentPolicy) {
    RuleClass ruleClass = ruleContext.getRule().getRuleClassObject();
    Set<Class<?>> missingFragments = new LinkedHashSet<>();
    for (Class<?> fragment : configurationFragmentPolicy.getRequiredConfigurationFragments()) {
      if (!ruleContext.getConfiguration().hasFragment(fragment.asSubclass(Fragment.class))) {
        missingFragments.add(fragment);
      }
    }
    Preconditions.checkState(!missingFragments.isEmpty());
    StringBuilder result = new StringBuilder();
    result.append("all rules of type " + ruleClass.getName() + " require the presence of ");
    List<String> names = new ArrayList<>();
    for (Class<?> fragment : missingFragments) {
      // TODO(bazel-team): Using getSimpleName here is sub-optimal, but we don't have anything
      // better right now.
      names.add(fragment.getSimpleName());
    }
    result.append("all of [");
    Joiner.on(",").appendTo(result, names);
    result.append("], but these were all disabled");
    return result.toString();
  }

  /**
   * Constructs an {@link ConfiguredAspect}. Returns null if an error occurs; in that case,
   * {@code aspectFactory} should call one of the error reporting methods of {@link RuleContext}.
   */
  public ConfiguredAspect createAspect(
      AnalysisEnvironment env,
      RuleConfiguredTarget associatedTarget,
      ConfiguredAspectFactory aspectFactory,
      Aspect aspect,
      OrderedSetMultimap<Attribute, ConfiguredTarget> prerequisiteMap,
      ImmutableMap<Label, ConfigMatchingProvider> configConditions,
      BuildConfiguration aspectConfiguration,
      BuildConfiguration hostConfiguration)
      throws InterruptedException {
    RuleContext.Builder builder = new RuleContext.Builder(env,
        associatedTarget.getTarget(),
        aspect.getAspectClass().getName(),
        aspectConfiguration,
        hostConfiguration,
        ruleClassProvider.getPrerequisiteValidator(),
        aspect.getDefinition().getConfigurationFragmentPolicy());
    RuleContext ruleContext =
        builder
            .setVisibility(
                convertVisibility(
                    prerequisiteMap, env.getEventHandler(), associatedTarget.getTarget(), null))
            .setPrerequisites(prerequisiteMap)
            .setAspectAttributes(aspect.getDefinition().getAttributes())
            .setConfigConditions(configConditions)
            .setUniversalFragment(ruleClassProvider.getUniversalFragment())
            .build();
    if (ruleContext.hasErrors()) {
      return null;
    }

    return aspectFactory.create(associatedTarget, ruleContext, aspect.getParameters());
  }

  /**
   * A pseudo-implementation for configured targets that creates fail actions for all declared
   * outputs, both implicit and explicit.
   */
  private static ConfiguredTarget createFailConfiguredTarget(RuleContext ruleContext) {
    RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext);
    if (!ruleContext.getOutputArtifacts().isEmpty()) {
      ruleContext.registerAction(new FailAction(ruleContext.getActionOwner(),
          ruleContext.getOutputArtifacts(), "Can't build this"));
    }
    builder.add(RunfilesProvider.class, RunfilesProvider.simple(Runfiles.EMPTY));
    return builder.build();
  }
}