/* * Copyright 2015-present Facebook, Inc. * * 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.facebook.buck.rules.macros; import static java.nio.charset.StandardCharsets.UTF_8; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.model.MacroException; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.CellPathResolver; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.hash.Hashing; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import javax.annotation.Nullable; public class OutputToFileExpander implements MacroExpander { private final MacroExpander delegate; public OutputToFileExpander(MacroExpander delegate) { this.delegate = delegate; } @Override public String expand( BuildTarget target, CellPathResolver cellNames, BuildRuleResolver resolver, ImmutableList<String> input) throws MacroException { try { String expanded; if (delegate instanceof MacroExpanderWithCustomFileOutput) { expanded = ((MacroExpanderWithCustomFileOutput) delegate) .expandForFile(target, cellNames, resolver, input); } else { expanded = delegate.expand(target, cellNames, resolver, input); } Optional<BuildRule> rule = resolver.getRuleOptional(target); if (!rule.isPresent()) { throw new MacroException(String.format("no rule %s", target)); } ProjectFilesystem filesystem = rule.get().getProjectFilesystem(); Path tempFile = createTempFile(filesystem, target, input.get(0)); filesystem.writeContentsToPath(expanded, tempFile); return "@" + filesystem.resolve(tempFile); } catch (IOException e) { throw new MacroException("Unable to create file to hold expanded results", e); } } @Override public ImmutableList<BuildRule> extractBuildTimeDeps( BuildTarget target, CellPathResolver cellNames, BuildRuleResolver resolver, ImmutableList<String> input) throws MacroException { return delegate.extractBuildTimeDeps(target, cellNames, resolver, input); } @Override public void extractParseTimeDeps( BuildTarget target, CellPathResolver cellNames, ImmutableList<String> input, ImmutableCollection.Builder<BuildTarget> buildDepsBuilder, ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) throws MacroException { delegate.extractParseTimeDeps( target, cellNames, input, buildDepsBuilder, targetGraphOnlyDepsBuilder); } @Override @Nullable public Object extractRuleKeyAppendables( BuildTarget target, CellPathResolver cellNames, BuildRuleResolver resolver, ImmutableList<String> input) throws MacroException { return delegate.extractRuleKeyAppendables(target, cellNames, resolver, input); } /** @return The absolute path to the temp file. */ private Path createTempFile(ProjectFilesystem filesystem, BuildTarget target, String input) throws IOException { Path directory = BuildTargets.getScratchPath(filesystem, target, "%s/tmp"); filesystem.mkdirs(directory); // "prefix" should give a stable name, so that the same delegate with the same input can output // the same file. We won't optimise for this case, since it's actually unlikely to happen within // a single run, but using a random name would cause 'buck-out' to expand in an uncontrolled // manner. String prefix = Hashing.sha1() .newHasher() .putString(delegate.getClass().getName(), UTF_8) .putString(input, UTF_8) .hash() .toString(); return filesystem.createTempFile(directory, prefix, ".macro"); } }