aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/analysis/config/DefaultsPackage.java
blob: 90e74b38df34a1632686341f9d022eb3e4f880e0 (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
// 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.config;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * A helper class to compute and inject a defaults package into the package cache.
 *
 * <p>The <code>//tools/defaults</code> package provides a mechanism let tool locations be specified
 * over the commandline, without requiring any special support in the rule code. As such, it can be
 * used in genrule <code>$(location)</code> substitutions.
 *
 * <p>It works as follows:
 *
 * <ul>
 *   <li>SomeLanguage.createCompileAction will refer to a host-configured target for the compiler by
 *       looking for <code>env.getHostPrerequisiteArtifact("$somelanguage_compiler")</code>.
 *   <li>the attribute <code>$somelanguage_compiler</code> is defined in the {@link RuleDefinition}
 *       subclass for that language.
 *   <li>if the attribute cannot be set on the command-line, its value may be a normal label.
 *   <li>if the attribute can be set on the command-line, its value will be <code>
 *       //tools/defaults:somelanguage_compiler</code>.
 *   <li>in the latter case, the {@link BuildConfiguration.Fragment} subclass will define the option
 *       (with an existing target, eg. <code>//third_party/somelanguage:compiler</code>), and return
 *       the name in its implementation of {@link FragmentOptions#getDefaultsLabels}.
 *   <li>On startup, the rule is wired up with <code>//tools/defaults:somelanguage_compiler</code>.
 *   <li>On starting a build, the <code>//tools/defaults</code> package is synthesized, using the
 *       values as specified on the command-line. The contents of <code>tools/defaults/BUILD</code>
 *       is ignored.
 *   <li>Hence, changes in the command line values for tools are now handled exactly as if they were
 *       changes in a BUILD file.
 *   <li>The file <code>tools/defaults/BUILD</code> must exist, so we create a package in that
 *       location.
 *   <li>The code in {@link DefaultsPackage} can dump the synthesized package as a BUILD file, so
 *       external tooling does not need to understand the intricacies of handling command-line
 *       options.
 * </ul>
 *
 * <p>For built-in rules (as opposed to genrules), late-bound labels provide an alternative method
 * of depending on command-line values. These work by declaring attribute default values to be
 * {@link LateBoundDefault} instances, whose <code>resolve(Rule rule, AttributeMap attributes,
 * FragmentT configuration)</code> method will have access to a {@link BuildConfiguration.Fragment},
 * which in turn may depend on command line flag values.
 */
public final class DefaultsPackage {

  // The template contents are broken into lines such that the resulting file has no more than 80
  // characters per line.
  private static final String HEADER = ""
      + "# DO NOT EDIT THIS FILE!\n"
      + "#\n"
      + "# Bazel does not read this file. Instead, it internally replaces the targets in\n"
      + "# this package with the correct packages as given on the command line.\n"
      + "#\n"
      + "# If these options are not given on the command line, Bazel will use the exact\n"
      + "# same targets as given here."
      + "\n"
      + "package(default_visibility = ['//visibility:public'])\n";

  /**
   * The map from entries to their values.
   */
  private ImmutableMap<String, ImmutableSet<Label>> values;

  private DefaultsPackage(BuildOptions buildOptions) {
    values = buildOptions.getDefaultsLabels();
  }

  private String labelsToString(Set<Label> labels) {
    StringBuilder result = new StringBuilder();
    for (Label label : labels) {
      if (result.length() != 0) {
        result.append(", ");
      }
      result.append("'").append(label).append("'");
    }
    return result.toString();
  }

  /**
   * Returns a string of the defaults package with the given settings.
   */
  private String getContent() {
    Preconditions.checkState(!values.isEmpty());
    StringBuilder result = new StringBuilder(HEADER);
    for (Map.Entry<String, ImmutableSet<Label>> entry : values.entrySet()) {
      result
          .append("filegroup(name = '")
          .append(entry.getKey().toLowerCase(Locale.US)).append("',\n")
          .append("          srcs = [")
          .append(labelsToString(entry.getValue())).append("])\n");
    }

    return result.toString();
  }

  /**
   * Returns the defaults package for the default settings.
   */
  public static String getDefaultsPackageContent(
      Iterable<Class<? extends FragmentOptions>> options, InvocationPolicy invocationPolicy) {
    return getDefaultsPackageContent(BuildOptions.createDefaults(options, invocationPolicy));
  }

  /**
   * Returns the defaults package for the given options.
   */
  public static String getDefaultsPackageContent(BuildOptions buildOptions) {
    return new DefaultsPackage(buildOptions).getContent();
  }

  public static void parseAndAdd(Set<Label> labels, String optionalLabel) {
    if (optionalLabel != null) {
      Label label = parseOptionalLabel(optionalLabel);
      if (label != null) {
        labels.add(label);
      }
    }
  }

  public static Label parseOptionalLabel(String value) {
    try {
      return Label.parseAbsolute(value, ImmutableMap.of());
    } catch (LabelSyntaxException e) {
      // We ignore this exception here - it will cause an error message at a later time.
      return null;
    }
  }
}