/*
* 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.cxx;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.AddToRuleKey;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.ExplicitBuildTargetSourcePath;
import com.facebook.buck.rules.HasRuntimeDeps;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepExecutionResult;
import com.facebook.buck.step.fs.MkdirStep;
import com.facebook.buck.util.ObjectMappers;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class CxxCompilationDatabase extends AbstractBuildRule implements HasRuntimeDeps {
private static final Logger LOG = Logger.get(CxxCompilationDatabase.class);
public static final Flavor COMPILATION_DATABASE = InternalFlavor.of("compilation-database");
public static final Flavor UBER_COMPILATION_DATABASE =
InternalFlavor.of("uber-compilation-database");
@AddToRuleKey private final ImmutableSortedSet<CxxPreprocessAndCompile> compileRules;
@AddToRuleKey(stringify = true)
private final Path outputJsonFile;
private final ImmutableSortedSet<BuildRule> runtimeDeps;
public static CxxCompilationDatabase createCompilationDatabase(
BuildRuleParams params, Iterable<CxxPreprocessAndCompile> compileAndPreprocessRules) {
ImmutableSortedSet.Builder<BuildRule> deps = ImmutableSortedSet.naturalOrder();
ImmutableSortedSet.Builder<CxxPreprocessAndCompile> compileRules =
ImmutableSortedSet.naturalOrder();
for (CxxPreprocessAndCompile compileRule : compileAndPreprocessRules) {
compileRules.add(compileRule);
deps.addAll(compileRule.getBuildDeps());
}
return new CxxCompilationDatabase(
params.copyReplacingDeclaredAndExtraDeps(
Suppliers.ofInstance(ImmutableSortedSet.of()),
Suppliers.ofInstance(ImmutableSortedSet.of())),
compileRules.build(),
deps.build());
}
CxxCompilationDatabase(
BuildRuleParams buildRuleParams,
ImmutableSortedSet<CxxPreprocessAndCompile> compileRules,
ImmutableSortedSet<BuildRule> runtimeDeps) {
super(buildRuleParams);
LOG.debug(
"Creating compilation database %s with runtime deps %s",
buildRuleParams.getBuildTarget(), runtimeDeps);
this.compileRules = compileRules;
this.outputJsonFile =
BuildTargets.getGenPath(
getProjectFilesystem(), buildRuleParams.getBuildTarget(), "__%s.json");
this.runtimeDeps = runtimeDeps;
}
@Override
public ImmutableList<Step> getBuildSteps(
BuildContext context, BuildableContext buildableContext) {
ImmutableList.Builder<Step> steps = ImmutableList.builder();
steps.add(MkdirStep.of(getProjectFilesystem(), outputJsonFile.getParent()));
steps.add(
new GenerateCompilationCommandsJson(
context.getSourcePathResolver(),
context.getSourcePathResolver().getRelativePath(getSourcePathToOutput())));
return steps.build();
}
@Override
public boolean isCacheable() {
// We don't want to cache the output of this rule because it contains absolute paths.
return false;
}
@Override
public SourcePath getSourcePathToOutput() {
return new ExplicitBuildTargetSourcePath(getBuildTarget(), outputJsonFile);
}
@Override
public Stream<BuildTarget> getRuntimeDeps() {
// The compilation database contains commands which refer to a
// particular state of generated header symlink trees/header map
// files.
//
// Ensure even if this rule doesn't need to be built due to a
// cache hit on the (empty) output of the rule, we still fetch and
// lay out the headers so the resulting compilation database can
// be used.
return runtimeDeps.stream().map(BuildRule::getBuildTarget);
}
class GenerateCompilationCommandsJson extends AbstractExecutionStep {
private final SourcePathResolver pathResolver;
private final Path outputRelativePath;
public GenerateCompilationCommandsJson(
SourcePathResolver pathResolver, Path outputRelativePath) {
super("generate compile_commands.json");
this.pathResolver = pathResolver;
this.outputRelativePath = outputRelativePath;
}
@Override
public StepExecutionResult execute(ExecutionContext context) {
Iterable<CxxCompilationDatabaseEntry> entries = createEntries();
return StepExecutionResult.of(writeOutput(entries, context));
}
@VisibleForTesting
Iterable<CxxCompilationDatabaseEntry> createEntries() {
List<CxxCompilationDatabaseEntry> entries = new ArrayList<>();
for (CxxPreprocessAndCompile compileRule : compileRules) {
entries.add(createEntry(compileRule));
}
return entries;
}
private CxxCompilationDatabaseEntry createEntry(CxxPreprocessAndCompile compileRule) {
SourcePath inputSourcePath = compileRule.getInput();
ProjectFilesystem inputFilesystem = compileRule.getProjectFilesystem();
String fileToCompile =
inputFilesystem.resolve(pathResolver.getAbsolutePath(inputSourcePath)).toString();
ImmutableList<String> arguments = compileRule.getCommand(pathResolver);
return CxxCompilationDatabaseEntry.of(
inputFilesystem.getRootPath().toString(), fileToCompile, arguments);
}
private int writeOutput(
Iterable<CxxCompilationDatabaseEntry> entries, ExecutionContext context) {
try (OutputStream outputStream =
getProjectFilesystem().newFileOutputStream(outputRelativePath)) {
ObjectMappers.WRITER.writeValue(outputStream, entries);
} catch (IOException e) {
logError(e, context);
return 1;
}
return 0;
}
private void logError(Throwable throwable, ExecutionContext context) {
context.logError(
throwable, "Failed writing to %s in %s.", outputRelativePath, getBuildTarget());
}
}
}