Java tutorial
/* * Copyright 2014-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.model.BuildTargets; import com.facebook.buck.rules.AbstractBuildRuleWithResolver; import com.facebook.buck.rules.AddToRuleKey; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.RuleKeyAppendable; import com.facebook.buck.rules.RuleKeyObjectSink; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SymlinkTree; import com.facebook.buck.rules.keys.SupportsDependencyFileRuleKey; import com.facebook.buck.rules.keys.SupportsInputBasedRuleKey; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.step.fs.MkdirStep; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; /** * A build rule which preprocesses and/or compiles a C/C++ source in a single step. */ public class CxxPreprocessAndCompile extends AbstractBuildRuleWithResolver implements RuleKeyAppendable, SupportsInputBasedRuleKey, SupportsDependencyFileRuleKey { @AddToRuleKey private final CxxPreprocessAndCompileStep.Operation operation; @AddToRuleKey private final Optional<PreprocessorDelegate> preprocessDelegate; @AddToRuleKey private final CompilerDelegate compilerDelegate; @AddToRuleKey(stringify = true) private final Path output; @AddToRuleKey private final SourcePath input; private final Optional<PrecompiledHeaderReference> precompiledHeaderRef; private final CxxSource.Type inputType; private final DebugPathSanitizer compilerSanitizer; private final DebugPathSanitizer assemblerSanitizer; private final SourcePathResolver pathResolver; private final Optional<SymlinkTree> sandboxTree; @VisibleForTesting public CxxPreprocessAndCompile(BuildRuleParams params, SourcePathResolver resolver, CxxPreprocessAndCompileStep.Operation operation, Optional<PreprocessorDelegate> preprocessDelegate, CompilerDelegate compilerDelegate, Path output, SourcePath input, CxxSource.Type inputType, Optional<PrecompiledHeaderReference> precompiledHeaderRef, DebugPathSanitizer compilerSanitizer, DebugPathSanitizer assemblerSanitizer, Optional<SymlinkTree> sandboxTree) { super(params, resolver); this.pathResolver = resolver; this.sandboxTree = sandboxTree; Preconditions.checkState(operation.isPreprocess() == preprocessDelegate.isPresent()); if (precompiledHeaderRef.isPresent()) { Preconditions.checkState(operation == CxxPreprocessAndCompileStep.Operation.PREPROCESS_AND_COMPILE, "Precompiled headers can only be used for compile operations."); } this.operation = operation; this.preprocessDelegate = preprocessDelegate; this.compilerDelegate = compilerDelegate; this.output = output; this.input = input; this.inputType = inputType; this.precompiledHeaderRef = precompiledHeaderRef; this.compilerSanitizer = compilerSanitizer; this.assemblerSanitizer = assemblerSanitizer; performChecks(params); } private void performChecks(BuildRuleParams params) { Preconditions.checkArgument( !params.getBuildTarget().getFlavors().contains(CxxStrip.RULE_FLAVOR) || !StripStyle.FLAVOR_DOMAIN.containsAnyOf(params.getBuildTarget().getFlavors()), "CxxPreprocessAndCompile should not be created with CxxStrip flavors"); Preconditions.checkArgument( !LinkerMapMode.FLAVOR_DOMAIN.containsAnyOf(params.getBuildTarget().getFlavors()), "CxxPreprocessAndCompile %s should not be created with LinkerMapMode flavor (%s)", this, LinkerMapMode.FLAVOR_DOMAIN); } /** * @return a {@link CxxPreprocessAndCompile} step that compiles the given preprocessed source. */ public static CxxPreprocessAndCompile compile(BuildRuleParams params, SourcePathResolver resolver, CompilerDelegate compilerDelegate, Path output, SourcePath input, CxxSource.Type inputType, DebugPathSanitizer compilerSanitizer, DebugPathSanitizer assemblerSanitizer, Optional<SymlinkTree> sandboxTree) { return new CxxPreprocessAndCompile(params, resolver, CxxPreprocessAndCompileStep.Operation.COMPILE, Optional.empty(), compilerDelegate, output, input, inputType, Optional.empty(), compilerSanitizer, assemblerSanitizer, sandboxTree); } /** * @return a {@link CxxPreprocessAndCompile} step that preprocesses and compiles the given source. */ public static CxxPreprocessAndCompile preprocessAndCompile(BuildRuleParams params, SourcePathResolver resolver, PreprocessorDelegate preprocessorDelegate, CompilerDelegate compilerDelegate, Path output, SourcePath input, CxxSource.Type inputType, Optional<PrecompiledHeaderReference> precompiledHeaderRef, DebugPathSanitizer compilerSanitizer, DebugPathSanitizer assemblerSanitizer, Optional<SymlinkTree> sandboxTree) { return new CxxPreprocessAndCompile(params, resolver, CxxPreprocessAndCompileStep.Operation.PREPROCESS_AND_COMPILE, Optional.of(preprocessorDelegate), compilerDelegate, output, input, inputType, precompiledHeaderRef, compilerSanitizer, assemblerSanitizer, sandboxTree); } @Override public void appendToRuleKey(RuleKeyObjectSink sink) { // If a sanitizer is being used for compilation, we need to record the working directory in // the rule key, as changing this changes the generated object file. if (operation == CxxPreprocessAndCompileStep.Operation.PREPROCESS_AND_COMPILE) { sink.setReflectively("compilationDirectory", compilerSanitizer.getCompilationDirectory()); } if (sandboxTree.isPresent()) { ImmutableMap<Path, SourcePath> links = sandboxTree.get().getLinks(); for (Path path : ImmutableSortedSet.copyOf(links.keySet())) { SourcePath source = links.get(path); sink.setReflectively("sandbox(" + path.toString() + ")", source); } } } private Path getDepFilePath() { return output.getFileSystem().getPath(output.toString() + ".dep"); } @VisibleForTesting CxxPreprocessAndCompileStep makeMainStep(SourcePathResolver resolver, Path scratchDir, boolean useArgfile) { // If we're compiling, this will just be empty. HeaderPathNormalizer headerPathNormalizer = preprocessDelegate.isPresent() ? preprocessDelegate.get().getHeaderPathNormalizer() : HeaderPathNormalizer.empty(resolver); Optional<CxxPreprocessAndCompileStep.ToolCommand> preprocessorCommand; if (operation.isPreprocess()) { preprocessorCommand = Optional .of(new CxxPreprocessAndCompileStep.ToolCommand(preprocessDelegate.get().getCommandPrefix(), preprocessDelegate.get().getArguments(compilerDelegate.getCompilerFlags(), Optional.empty()), preprocessDelegate.get().getEnvironment(), preprocessDelegate.get().getFlagsForColorDiagnostics())); } else { preprocessorCommand = Optional.empty(); } Optional<CxxPreprocessAndCompileStep.ToolCommand> compilerCommand; if (operation.isCompile()) { ImmutableList<String> arguments; if (operation == CxxPreprocessAndCompileStep.Operation.PREPROCESS_AND_COMPILE) { Optional<CxxPrecompiledHeader> pch; if (precompiledHeaderRef.isPresent()) { pch = Optional.of(precompiledHeaderRef.get().getPrecompiledHeader()); } else { pch = Optional.empty(); } arguments = compilerDelegate.getArguments(preprocessDelegate.get().getFlagsWithSearchPaths(pch)); } else { arguments = compilerDelegate.getArguments(CxxToolFlags.of()); } compilerCommand = Optional .of(new CxxPreprocessAndCompileStep.ToolCommand(compilerDelegate.getCommandPrefix(), arguments, compilerDelegate.getEnvironment(), compilerDelegate.getFlagsForColorDiagnostics())); } else { compilerCommand = Optional.empty(); } // TODO(10194465): This uses relative paths where possible so as to get relative paths in // the dep file Path inputPath; try { inputPath = resolver.getRelativePath(input); } catch (IllegalStateException e) { inputPath = resolver.getAbsolutePath(input); } return new CxxPreprocessAndCompileStep(getProjectFilesystem(), operation, output, getDepFilePath(), inputPath, inputType, preprocessorCommand, compilerCommand, headerPathNormalizer, compilerSanitizer, assemblerSanitizer, preprocessDelegate.isPresent() ? preprocessDelegate.get().getHeaderVerification() : HeaderVerification.of(HeaderVerification.Mode.IGNORE), scratchDir, useArgfile, compilerDelegate.getCompiler()); } @Override public String getType() { return "cxx_preprocess_compile"; } @Override public ImmutableList<Step> getBuildSteps(BuildContext context, BuildableContext buildableContext) { buildableContext.recordArtifact(output); return ImmutableList.of(new MkdirStep(getProjectFilesystem(), output.getParent()), new MakeCleanDirectoryStep(getProjectFilesystem(), getScratchPath()), makeMainStep( context.getSourcePathResolver(), getScratchPath(), compilerDelegate.isArgFileSupported())); } private Path getScratchPath() { return BuildTargets.getScratchPath(getProjectFilesystem(), getBuildTarget(), "%s-tmp"); } @VisibleForTesting Optional<PreprocessorDelegate> getPreprocessorDelegate() { return preprocessDelegate; } // Used for compdb public ImmutableList<String> getCommand() { if (operation == CxxPreprocessAndCompileStep.Operation.PREPROCESS_AND_COMPILE) { return makeMainStep(pathResolver, getScratchPath(), false).getCommand(); } PreprocessorDelegate effectivePreprocessorDelegate = preprocessDelegate.get(); ImmutableList.Builder<String> cmd = ImmutableList.builder(); cmd.addAll(compilerDelegate .getCommand(effectivePreprocessorDelegate.getFlagsWithSearchPaths(/*pch*/ Optional.empty()))); // use the input of the preprocessor, since the fact that this is going through preprocessor is // hidden to compdb. cmd.add("-x", inputType.getLanguage()); cmd.add("-c"); cmd.add("-o", output.toString()); cmd.add(pathResolver.getAbsolutePath(input).toString()); return cmd.build(); } @Override public Path getPathToOutput() { return output; } public SourcePath getInput() { return input; } @Override public boolean useDependencyFileRuleKeys() { return compilerDelegate.isDependencyFileSupported(); } @Override public Optional<ImmutableSet<SourcePath>> getPossibleInputSourcePaths() { if (preprocessDelegate.isPresent()) { return preprocessDelegate.get().getPossibleInputSourcePaths(); } return Optional.empty(); } @Override public ImmutableList<SourcePath> getInputsAfterBuildingLocally() throws IOException { ImmutableList.Builder<SourcePath> inputs = ImmutableList.builder(); // If present, include all inputs coming from the preprocessor tool. if (preprocessDelegate.isPresent()) { Iterable<String> depFileLines = readDepFileLines(); if (precompiledHeaderRef.isPresent()) { depFileLines = Iterables.concat(precompiledHeaderRef.get().getDepFileLines().get(), depFileLines); } inputs.addAll(preprocessDelegate.get().getInputsAfterBuildingLocally(depFileLines)); } // If present, include all inputs coming from the compiler tool. if (operation.isCompile()) { inputs.addAll(compilerDelegate.getInputsAfterBuildingLocally()); } // Add the input. inputs.add(input); return inputs.build(); } private ImmutableList<String> readDepFileLines() throws IOException { return ImmutableList.copyOf(getProjectFilesystem().readLines(getDepFilePath())); } }