Skip to content

Commit

Permalink
Add platform rule to define a platform as a collection of constraint
Browse files Browse the repository at this point in the history
values.

Part of ongoing work on bazelbuild#2219.

Change-Id: Ie4e842a5d8218e47f41a954c2b955ab24237aa65
  • Loading branch information
katre committed Mar 1, 2017
1 parent ee61191 commit 3e45c00
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
import com.google.devtools.build.lib.rules.objc.XcTestAppProvider;
import com.google.devtools.build.lib.rules.platform.ConstraintSettingRule;
import com.google.devtools.build.lib.rules.platform.ConstraintValueRule;
import com.google.devtools.build.lib.rules.platform.PlatformRule;
import com.google.devtools.build.lib.rules.proto.BazelProtoLibraryRule;
import com.google.devtools.build.lib.rules.proto.ProtoConfiguration;
import com.google.devtools.build.lib.rules.proto.ProtoLangToolchainRule;
Expand All @@ -176,25 +177,22 @@
import com.google.devtools.build.lib.util.ResourceFileLoader;
import java.io.IOException;

/**
* A rule class provider implementing the rules Bazel knows.
*/
/** A rule class provider implementing the rules Bazel knows. */
public class BazelRuleClassProvider {
public static final String TOOLS_REPOSITORY = "@bazel_tools";

/** Used by the build encyclopedia generator. */
public static ConfiguredRuleClassProvider create() {
ConfiguredRuleClassProvider.Builder builder =
new ConfiguredRuleClassProvider.Builder();
ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
builder.setToolsRepository(TOOLS_REPOSITORY);
setup(builder);
return builder.build();
}

private static class BazelPrerequisiteValidator implements PrerequisiteValidator {
@Override
public void validate(RuleContext.Builder context,
ConfiguredTarget prerequisite, Attribute attribute) {
public void validate(
RuleContext.Builder context, ConfiguredTarget prerequisite, Attribute attribute) {
validateDirectPrerequisiteVisibility(context, prerequisite, attribute.getName());
validateDirectPrerequisiteForTestOnly(context, prerequisite);
DeprecationValidator.validateDirectPrerequisiteForDeprecation(
Expand All @@ -205,21 +203,27 @@ private void validateDirectPrerequisiteVisibility(
RuleContext.Builder context, ConfiguredTarget prerequisite, String attrName) {
Rule rule = context.getRule();
Target prerequisiteTarget = prerequisite.getTarget();
if (!context.getRule().getLabel().getPackageIdentifier().equals(
AliasProvider.getDependencyLabel(prerequisite).getPackageIdentifier())
if (!context
.getRule()
.getLabel()
.getPackageIdentifier()
.equals(AliasProvider.getDependencyLabel(prerequisite).getPackageIdentifier())
&& !context.isVisible(prerequisite)) {
if (!context.getConfiguration().checkVisibility()) {
context.ruleWarning(String.format("Target '%s' violates visibility of target "
+ "%s. Continuing because --nocheck_visibility is active",
rule.getLabel(), AliasProvider.printLabelWithAliasChain(prerequisite)));
context.ruleWarning(
String.format(
"Target '%s' violates visibility of target "
+ "%s. Continuing because --nocheck_visibility is active",
rule.getLabel(), AliasProvider.printLabelWithAliasChain(prerequisite)));
} else {
// Oddly enough, we use reportError rather than ruleError here.
context.reportError(rule.getLocation(),
String.format("Target %s is not visible from target '%s'. Check "
+ "the visibility declaration of the former target if you think "
+ "the dependency is legitimate",
AliasProvider.printLabelWithAliasChain(prerequisite),
rule.getLabel()));
context.reportError(
rule.getLocation(),
String.format(
"Target %s is not visible from target '%s'. Check "
+ "the visibility declaration of the former target if you think "
+ "the dependency is legitimate",
AliasProvider.printLabelWithAliasChain(prerequisite), rule.getLabel()));
}
}

Expand Down Expand Up @@ -253,9 +257,12 @@ private void validateDirectPrerequisiteForTestOnly(
String thisPackage = rule.getLabel().getPackageName();

if (isTestOnlyRule(prerequisiteTarget) && !isTestOnlyRule(rule)) {
String message = "non-test target '" + rule.getLabel() + "' depends on testonly target "
+ AliasProvider.printLabelWithAliasChain(prerequisite)
+ " and doesn't have testonly attribute set";
String message =
"non-test target '"
+ rule.getLabel()
+ "' depends on testonly target "
+ AliasProvider.printLabelWithAliasChain(prerequisite)
+ " and doesn't have testonly attribute set";
if (thisPackage.startsWith("experimental/")) {
context.ruleWarning(message);
} else {
Expand Down Expand Up @@ -343,6 +350,7 @@ public ImmutableList<RuleSet> requires() {
public void init(Builder builder) {
builder.addRuleDefinition(new ConstraintSettingRule());
builder.addRuleDefinition(new ConstraintValueRule());
builder.addRuleDefinition(new PlatformRule());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.google.devtools.build.lib.rules.platform;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;

/** Defines a platform for execution contexts. */
public class Platform implements RuleConfiguredTargetFactory {
@Override
public ConfiguredTarget create(RuleContext ruleContext)
throws InterruptedException, RuleErrorException {

Iterable<ConstraintValueProvider> constraintValues =
ruleContext.getPrerequisites(
PlatformRule.CONSTRAINTS_ATTR, Mode.DONT_CHECK, ConstraintValueProvider.class);

// Verify the constraints - no two values for the same setting, and construct the map.
ImmutableMap<ConstraintSettingProvider, ConstraintValueProvider> constraints =
validateConstraints(ruleContext, constraintValues);
if (constraints == null) {
// An error occurred, return null.
return null;
}

return new RuleConfiguredTargetBuilder(ruleContext)
.addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
.addProvider(FileProvider.class, FileProvider.EMPTY)
.addProvider(FilesToRunProvider.class, FilesToRunProvider.EMPTY)
.addProvider(PlatformProvider.class, PlatformProvider.create(constraints))
.build();
}

private ImmutableMap<ConstraintSettingProvider, ConstraintValueProvider> validateConstraints(
RuleContext ruleContext, Iterable<ConstraintValueProvider> constraintValues) {
Multimap<ConstraintSettingProvider, ConstraintValueProvider> constraints =
ArrayListMultimap.create();

for (ConstraintValueProvider constraintValue : constraintValues) {
constraints.put(constraintValue.constraint(), constraintValue);
}

// Are there any settings with more than one value?
boolean foundError = false;
for (ConstraintSettingProvider constraintSetting : constraints.keySet()) {
if (constraints.get(constraintSetting).size() > 1) {
foundError = true;
// error
StringBuilder constraintValuesDescription = new StringBuilder();
for (ConstraintValueProvider constraintValue : constraints.get(constraintSetting)) {
if (constraintValuesDescription.length() > 0) {
constraintValuesDescription.append(", ");
}
constraintValuesDescription.append(constraintValue.value());
}
ruleContext.attributeError(
PlatformRule.CONSTRAINTS_ATTR,
String.format(
"Duplicate constraint_values for constraint_setting %s: %s",
constraintSetting.constraintSetting(), constraintValuesDescription.toString()));
}
}

if (foundError) {
return null;
}

// Convert to a flat map.
ImmutableMap.Builder<ConstraintSettingProvider, ConstraintValueProvider> builder =
new ImmutableMap.Builder<>();
for (ConstraintSettingProvider constraintSetting : constraints.keySet()) {
ConstraintValueProvider constraintValue =
Iterables.getOnlyElement(constraints.get(constraintSetting));
builder.put(constraintSetting, constraintValue);
}

return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2017 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.rules.platform;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;

/** Provider for a platform, which is a group of constraints and values. */
@AutoValue
@Immutable
public abstract class PlatformProvider implements TransitiveInfoProvider {
public abstract ImmutableMap<ConstraintSettingProvider, ConstraintValueProvider> constraints();

public static PlatformProvider create(
ImmutableMap<ConstraintSettingProvider, ConstraintValueProvider> constraints) {
return new AutoValue_PlatformProvider(constraints);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.google.devtools.build.lib.rules.platform;

import static com.google.devtools.build.lib.packages.Attribute.attr;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.FileTypeSet;

/** Rule definition for {@link Platform}. */
public class PlatformRule implements RuleDefinition {
public static final String RULE_NAME = "platform";
public static final String CONSTRAINTS_ATTR = "constraints";

@Override
public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
/* <!-- #BLAZE_RULE(platform).NAME -->
<!-- #END_BLAZE_RULE.NAME --> */
return builder
.override(
attr("tags", Type.STRING_LIST)
// No need to show up in ":all", etc. target patterns.
.value(ImmutableList.of("manual"))
.nonconfigurable("low-level attribute, used in platform configuration"))
.add(
attr(CONSTRAINTS_ATTR, BuildType.LABEL_LIST)
.allowedFileTypes(FileTypeSet.NO_FILE)
.mandatoryNativeProviders(
ImmutableList.<Class<? extends TransitiveInfoProvider>>of(
ConstraintValueProvider.class)))
.exemptFromConstraintChecking("this rule is part of constraint definition")
.build();
}

@Override
public Metadata getMetadata() {
return Metadata.builder()
.name(RULE_NAME)
.ancestors(BaseRuleClasses.RuleBase.class)
.factoryClass(Platform.class)
.build();
}
}
/*<!-- #BLAZE_RULE (NAME = platform, TYPE = OTHER, FAMILY = Platform)[GENERIC_RULE] -->
<p>This rule defines a platform, as a collection of constraint_values.
<h4 id="platform_examples">Examples</h4>
<p>
This defines two possible platforms, each targeting a different CPU type.
</p>
<pre class="code">
constraint_setting(name="cpu")
constraint_value(
name="arm64",
constraint=":cpu")
constraint_value(
name="k8",
constraint=":cpu")
platform(
name="mobile_device",
constraints = [
":arm64",
])
platform(
name="devel",
constraints = [
":k8",
])
</pre>
<!-- #END_BLAZE_RULE -->*/
Original file line number Diff line number Diff line change
Expand Up @@ -1237,7 +1237,7 @@ protected void assertSrcsValidity(String ruleType, String targetName, boolean ex
}
}

private static Label makeLabel(String label) {
public static Label makeLabel(String label) {
try {
return Label.parseAbsolute(label);
} catch (LabelSyntaxException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ filegroup(
)

java_test(
name = "PlatformTests",
name = "PlatformRulesTests",
srcs = glob(["*.java"]),
test_class = "com.google.devtools.build.lib.AllTests",
deps = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2017 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.rules.platform;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests of {@link Platform}. */
@RunWith(JUnit4.class)
public class PlatformTest extends BuildViewTestCase {

@Test
public void testPlatform() throws Exception {
scratch.file(
"constraint/BUILD",
"constraint_setting(name = 'basic')",
"constraint_value(name = 'foo',",
" constraint_setting = ':basic',",
" )",
"platform(name = 'plat1',",
" constraints = [",
" ':foo',",
" ])");

ConfiguredTarget platform = getConfiguredTarget("//constraint:plat1");
assertThat(platform).isNotNull();

PlatformProvider provider = platform.getProvider(PlatformProvider.class);
assertThat(provider).isNotNull();
assertThat(provider.constraints()).hasSize(1);
ConstraintSettingProvider constraintSettingProvider =
ConstraintSettingProvider.create(makeLabel("//constraint:basic"));
ConstraintValueProvider constraintValueProvider =
ConstraintValueProvider.create(constraintSettingProvider, makeLabel("//constraint:foo"));
assertThat(provider.constraints())
.containsExactlyEntriesIn(
ImmutableMap.of(constraintSettingProvider, constraintValueProvider));
}

@Test
public void testPlatform_overlappingConstraintValueError() throws Exception {
checkError(
"constraint",
"plat1",
"Duplicate constraint_values for constraint_setting //constraint:basic: //constraint:foo, //constraint:bar",
"constraint_setting(name = 'basic')",
"constraint_value(name = 'foo',",
" constraint_setting = ':basic',",
" )",
"constraint_value(name = 'bar',",
" constraint_setting = ':basic',",
" )",
"platform(name = 'plat1',",
" constraints = [",
" ':foo',",
" ':bar',",
" ])");
}
}

0 comments on commit 3e45c00

Please sign in to comment.