// Copyright 2016 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.util; import static com.google.common.base.Preconditions.checkNotNull; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.testutil.Scratch; import java.io.IOException; import java.util.Arrays; /** * A writer for a scratch build target and associated source files. Can be parameterized with a rule * type for which to write a mock target. * * <p>For example, the snippet: * * <pre>{@code * new ScratchAttributeWriter(testCase, "cc_library", "//x:x") * .setList("srcs", "a.cc", "b.cc") * .setList("hdrs", "hdr.h") * .write(); * }</pre> * * <p>Would create the BUILD file "x/BUILD" with contents: * * <pre>{@code * cc_library( * name = 'x', * srcs = ['a.cc', 'b.cc'], * hdrs = ['hdr.h'], * ) * }</pre> */ public class ScratchAttributeWriter { private abstract static class ScratchAttribute<T> { protected String attributeName; protected T attributeValue; abstract StringBuilder appendLine(StringBuilder builder); } /** A plain string attribute. */ private static class StringAttribute extends ScratchAttribute<String> { public StringAttribute(String attributeName, String attributeValue) { this.attributeName = attributeName; this.attributeValue = attributeValue; } @Override StringBuilder appendLine(StringBuilder builder) { return builder.append(String.format("%s=%s,", attributeName, attributeValue)); } } /** An integer attribute, such as "alwayslink" */ private static class IntegerAttribute extends ScratchAttribute<Integer> { public IntegerAttribute(String attributeName, Integer attributeValue) { this.attributeName = attributeName; this.attributeValue = attributeValue; } @Override StringBuilder appendLine(StringBuilder builder) { return builder.append(String.format("%s=%d,", attributeName, attributeValue)); } } /** A list attribute, such as "srcs" */ private static class StringListAttribute extends ScratchAttribute<Iterable<String>> { public StringListAttribute(String attributeName, Iterable<String> attributeValue) { this.attributeName = attributeName; this.attributeValue = attributeValue; } @Override StringBuilder appendLine(StringBuilder builder) { builder.append(String.format("%s=[", attributeName)); for (String value : attributeValue) { builder.append(String.format("'%s',", value)); } builder.append("],"); return builder; } } /** The name of the package. */ private final String packageName; /** The name of the target. */ private final String targetName; /** The test case for which to write this target. */ private final BuildViewTestCase testCase; /** The name of the rule for this target */ private final String ruleName; /** An ordered list of the attributes to be written for this scratch target */ StringBuilder buildString; /** * Creates a ScratchAttributeWriter for a given test case, package name, and target name. The * provided rule name will determine the type of the target written. */ private ScratchAttributeWriter( BuildViewTestCase testCase, String ruleName, String packageName, String targetName) { this.testCase = checkNotNull(testCase); this.ruleName = checkNotNull(ruleName); this.packageName = checkNotNull(packageName); this.targetName = checkNotNull(targetName); this.buildString = new StringBuilder() .append(String.format("%s(", this.ruleName)) .append(String.format("name='%s',", this.targetName)); } /** * Creates a ScratchAttributeWriter for a given test case and label. The provided rule name will * determine the type of the target written. */ public static ScratchAttributeWriter fromLabel( BuildViewTestCase testCase, String ruleName, Label label) { return new ScratchAttributeWriter(testCase, ruleName, label.getPackageName(), label.getName()); } /** * Creates a ScratchAttributeWriter for a given test case and label string. The provided rule name * will determine the type of the target written. */ public static ScratchAttributeWriter fromLabelString( BuildViewTestCase testCase, String ruleName, String labelString) { return fromLabel(testCase, ruleName, Label.parseAbsoluteUnchecked(labelString)); } /** * Writes this scratch target to this ScratchAttributeWriter's Scratch instance, and returns the * target in the given configuration. */ public ConfiguredTarget write(BuildConfiguration config) throws Exception { Scratch scratch = testCase.getScratch(); buildString.append(")"); scratch.file(String.format("%s/BUILD", packageName), buildString.toString()); return testCase.getConfiguredTarget(String.format("//%s:%s", packageName, targetName), config); } /** * Writes this scratch target to this ScratchAttributeWriter's Scratch instance, and returns the * target in the target configuration. */ public ConfiguredTarget write() throws Exception { return write(testCase.getTargetConfiguration()); } private void createSource(String source) throws IOException { testCase.getScratch().file(String.format("%s/%s", packageName, source)); } /** Sets a string attribute (like ios_application.app_icon) for this target. */ public ScratchAttributeWriter set(String name, String value) { new StringAttribute(name, value).appendLine(this.buildString); return this; } /** Sets a list attribute (like cc_library.srcs) for this target. */ public ScratchAttributeWriter setList(String name, Iterable<String> value) { new StringListAttribute(name, value).appendLine(this.buildString); return this; } /** Sets a list attribute (like cc_library.srcs) for this target */ public ScratchAttributeWriter setList(String name, String... value) { return setList(name, Arrays.asList(value)); } /** Sets an integer attribute (like cc_binary.linkstatic) for this target. */ public ScratchAttributeWriter set(String name, int value) { new IntegerAttribute(name, value).appendLine(this.buildString); return this; } /** * Sets a list attribute (link cc_library.srcs) for this target. For each string in 'value', * writes an empty file to this writer's package with that name. * * <p>Usually, an analysis-time should not require that referenced files actually be written, in * which case ScratchAttributeWriter#set should be used instead. */ public ScratchAttributeWriter setAndCreateFiles(String name, Iterable<String> value) throws IOException { for (String source : value) { createSource(source); } return setList(name, value); } /** * Sets a list attribute (link cc_library.srcs) for this target. For each string in 'value', * writes an empty file to this writer's package with that name. * * <p>Usually, an analysis-time should not require that referenced files actually be written, in * which case ScratchAttributeWriter#set should be used instead. */ public ScratchAttributeWriter setAndCreateFiles(String name, String... value) throws IOException { return setAndCreateFiles(name, Arrays.asList(value)); } }