diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java new file mode 100644 index 0000000000..b2c83fbfb7 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java @@ -0,0 +1,335 @@ +// Copyright 2014 Google Inc. 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.actions; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Executor; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.util.Fingerprint; +import com.google.devtools.build.lib.util.ResourceFileLoader; +import com.google.devtools.build.lib.util.StringUtilities; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.Path; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.List; + +/** + * Action to expand a template and write the expanded content to a file. + */ +public class TemplateExpansionAction extends AbstractFileWriteAction { + + private static final String GUID = "786c1fe0-dca8-407a-b108-e1ecd6d1bc7f"; + + /** + * A pair of a string to be substituted and a string to substitute it with. + * For simplicity, these are called key and value. All implementations must + * be immutable, and always return the identical key. The returned values + * must be the same, though they need not be the same object. + * + * <p>It should be assumed that the {@link #getKey} invocation is cheap, and + * that the {@link #getValue} invocation is expensive. + */ + public abstract static class Substitution { + private Substitution() { + } + + public abstract String getKey(); + public abstract String getValue(); + + /** + * Returns an immutable Substitution instance for the given key and value. + */ + public static Substitution of(final String key, final String value) { + return new Substitution() { + @Override + public String getKey() { + return key; + } + + @Override + public String getValue() { + return value; + } + }; + } + + /** + * Returns an immutable Substitution instance for the key and list of values. The + * values will be joined by spaces before substitution. + */ + public static Substitution ofSpaceSeparatedList(final String key, final List<?> value) { + return new Substitution() { + @Override + public String getKey() { + return key; + } + + @Override + public String getValue() { + return Joiner.on(" ").join(value); + } + }; + } + } + + /** + * A substitution with a fixed key, and a computed value. The computed value + * must not change over the lifetime of an instance, though the {@link + * #getValue} method may return different String objects. + * + * <p>It should be assumed that the {@link #getKey} invocation is cheap, and + * that the {@link #getValue} invocation is expensive. + */ + public abstract static class ComputedSubstitution extends Substitution { + private final String key; + + public ComputedSubstitution(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } + } + + /** + * A template that contains text content, or alternatively throws an {@link + * IOException}. + */ + public abstract static class Template { + + /** + * We only allow subclasses in this file. + */ + private Template() { + } + + /** + * Returns the text content of the template. + */ + protected abstract String getContent() throws IOException; + + /** + * Returns a string that is used for the action key. This must change if + * the getContent method returns something different, but is not allowed to + * throw an exception. + */ + protected abstract String getKey(); + + /** + * Loads a template from the given resource. The resource is looked up + * relative to the given class. If the resource cannot be loaded, the returned + * template throws an {@link IOException} when {@link #getContent} is + * called. This makes it safe to use this method in a constant initializer. + */ + public static Template forResource(final Class<?> relativeToClass, final String templateName) { + try { + String content = ResourceFileLoader.loadResource(relativeToClass, templateName); + return forString(content); + } catch (final IOException e) { + return new Template() { + @Override + protected String getContent() throws IOException { + throw new IOException("failed to load resource file '" + templateName + + "' due to I/O error: " + e.getMessage(), e); + } + + @Override + protected String getKey() { + return "ERROR: " + e.getMessage(); + } + }; + } + } + + /** + * Returns a template for the given text string. + */ + public static Template forString(final String templateText) { + return new Template() { + @Override + protected String getContent() { + return templateText; + } + + @Override + protected String getKey() { + return templateText; + } + }; + } + + /** + * Returns a template that loads the given artifact. It is important that + * the artifact is also an input for the action, or this won't work. + * Therefore this method is private, and you should use the corresponding + * {@link TemplateExpansionAction} constructor. + */ + private static Template forArtifact(final Artifact templateArtifact) { + return new Template() { + @Override + protected String getContent() throws IOException { + Path templatePath = templateArtifact.getPath(); + try { + return new String(FileSystemUtils.readContentAsLatin1(templatePath)); + } catch (IOException e) { + throw new IOException("failed to load template file '" + templatePath.getPathString() + + "' due to I/O error: " + e.getMessage(), e); + } + } + + @Override + protected String getKey() { + // This isn't strictly necessary, because the action inputs are automatically considered. + return "ARTIFACT: " + templateArtifact.getExecPathString(); + } + }; + } + } + + private final Template template; + private final List<Substitution> substitutions; + + /** + * Creates a new TemplateExpansionAction instance. + * + * @param owner the action owner. + * @param inputs the Artifacts that this Action depends on + * @param output the Artifact that will be created by executing this Action. + * @param template the template that will be expanded by this Action. + * @param substitutions the substitutions that will be applied to the + * template. All substitutions will be applied in order. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + private TemplateExpansionAction(ActionOwner owner, + Collection<Artifact> inputs, + Artifact output, + Template template, + List<Substitution> substitutions, + boolean makeExecutable) { + super(owner, inputs, output, makeExecutable); + this.template = template; + this.substitutions = ImmutableList.copyOf(substitutions); + } + + /** + * Creates a new TemplateExpansionAction instance for an artifact template. + * + * @param owner the action owner. + * @param templateArtifact the Artifact that will be read as the text template + * file + * @param output the Artifact that will be created by executing this Action. + * @param substitutions the substitutions that will be applied to the + * template. All substitutions will be applied in order. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + public TemplateExpansionAction(ActionOwner owner, + Artifact templateArtifact, + Artifact output, + List<Substitution> substitutions, + boolean makeExecutable) { + this(owner, ImmutableList.of(templateArtifact), output, Template.forArtifact(templateArtifact), + substitutions, makeExecutable); + } + + /** + * Creates a new TemplateExpansionAction instance without inputs. + * + * @param owner the action owner. + * @param output the Artifact that will be created by executing this Action. + * @param template the template + * @param substitutions the substitutions that will be applied to the + * template. All substitutions will be applied in order. + * @param makeExecutable iff true will change the output file to be + * executable. + */ + public TemplateExpansionAction(ActionOwner owner, + Artifact output, + Template template, + List<Substitution> substitutions, + boolean makeExecutable) { + this(owner, Artifact.NO_ARTIFACTS, output, template, substitutions, makeExecutable); + } + + /** + * Expands the template by applying all substitutions. + * @param template + * @return the expanded text. + */ + private String expandTemplate(String template) { + for (Substitution entry : substitutions) { + template = StringUtilities.replaceAllLiteral(template, entry.getKey(), entry.getValue()); + } + return template; + } + + @VisibleForTesting + public String getFileContents() throws IOException { + return expandTemplate(template.getContent()); + } + + @Override + public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, + Executor executor) throws IOException { + final byte[] bytes = getFileContents().getBytes(UTF_8); + return new DeterministicWriter() { + @Override + public void writeOutputFile(OutputStream out) throws IOException { + out.write(bytes); + } + }; + } + + @Override + protected String computeKey() { + Fingerprint f = new Fingerprint(); + f.addString(GUID); + f.addString(String.valueOf(makeExecutable)); + f.addString(template.getKey()); + f.addInt(substitutions.size()); + for (Substitution entry : substitutions) { + f.addString(entry.getKey()); + f.addString(entry.getValue()); + } + return f.hexDigestAndReset(); + } + + @Override + public String getMnemonic() { + return "TemplateExpand"; + } + + @Override + protected String getRawProgressMessage() { + return "Expanding template " + Iterables.getOnlyElement(getOutputs()).prettyPrint(); + } + + public List<Substitution> getSubstitutions() { + return substitutions; + } +} |