/* * 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 static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.CommandTool; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.FakeBuildContext; import com.facebook.buck.rules.FakeBuildRuleParamsBuilder; import com.facebook.buck.rules.FakeSourcePath; import com.facebook.buck.rules.HashedFileTool; import com.facebook.buck.rules.PathSourcePath; import com.facebook.buck.rules.RuleKey; import com.facebook.buck.rules.RuleKeyObjectSink; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.Tool; import com.facebook.buck.rules.args.RuleKeyAppendableFunction; import com.facebook.buck.rules.coercer.FrameworkPath; import com.facebook.buck.rules.keys.DefaultRuleKeyFactory; import com.facebook.buck.testutil.FakeFileHashCache; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import org.junit.Test; public class CxxPreprocessAndCompileTest { private static class PreprocessorWithColorSupport extends GccPreprocessor { static final String COLOR_FLAG = "-use-color-in-preprocessor"; public PreprocessorWithColorSupport(Tool tool) { super(tool); } @Override public Optional<ImmutableList<String>> getFlagsForColorDiagnostics() { return Optional.of(ImmutableList.of(COLOR_FLAG)); } } private static class CompilerWithColorSupport extends DefaultCompiler { static final String COLOR_FLAG = "-use-color-in-compiler"; public CompilerWithColorSupport(Tool tool) { super(tool); } @Override public Optional<ImmutableList<String>> getFlagsForColorDiagnostics() { return Optional.of(ImmutableList.of(COLOR_FLAG)); } } private static final Preprocessor DEFAULT_PREPROCESSOR = new GccPreprocessor(new HashedFileTool(Paths.get("preprocessor"))); private static final Compiler DEFAULT_COMPILER = new GccCompiler(new HashedFileTool(Paths.get("compiler"))); private static final Preprocessor PREPROCESSOR_WITH_COLOR_SUPPORT = new PreprocessorWithColorSupport(new HashedFileTool(Paths.get("preprocessor"))); private static final Compiler COMPILER_WITH_COLOR_SUPPORT = new CompilerWithColorSupport(new HashedFileTool(Paths.get("compiler"))); private static final CxxToolFlags DEFAULT_TOOL_FLAGS = CxxToolFlags.explicitBuilder() .addPlatformFlags("-fsanitize=address") .addRuleFlags("-O3") .build(); private static final Path DEFAULT_OUTPUT = Paths.get("test.o"); private static final SourcePath DEFAULT_INPUT = new FakeSourcePath("test.cpp"); private static final CxxSource.Type DEFAULT_INPUT_TYPE = CxxSource.Type.CXX; private static final Path DEFAULT_WORKING_DIR = Paths.get(System.getProperty("user.dir")); private static final RuleKeyAppendableFunction<FrameworkPath, Path> DEFAULT_FRAMEWORK_PATH_SEARCH_PATH_FUNCTION = new RuleKeyAppendableFunction<FrameworkPath, Path>() { @Override public void appendToRuleKey(RuleKeyObjectSink sink) { // Do nothing. } @Override public Path apply(FrameworkPath input) { return Paths.get("test", "framework", "path", input.toString()); } }; @Test public void inputChangesCauseRuleKeyChangesForCompilation() throws Exception { SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); BuildTarget target = BuildTargetFactory.newInstance("//foo:bar"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(target).build(); FakeFileHashCache hashCache = FakeFileHashCache.createFromStrings( ImmutableMap.<String, String>builder() .put("preprocessor", Strings.repeat("a", 40)) .put("compiler", Strings.repeat("a", 40)) .put("test.o", Strings.repeat("b", 40)) .put("test.cpp", Strings.repeat("c", 40)) .put("different", Strings.repeat("d", 40)) .put("foo/test.h", Strings.repeat("e", 40)) .put("path/to/a/plugin.so", Strings.repeat("f", 40)) .put("path/to/a/different/plugin.so", Strings.repeat("a0", 40)) .build()); // Generate a rule key for the defaults. RuleKey defaultRuleKey = new DefaultRuleKeyFactory(0, hashCache, pathResolver, ruleFinder) .build( CxxPreprocessAndCompile.compile( params, new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, DEFAULT_COMPILER, DEFAULT_TOOL_FLAGS), DEFAULT_OUTPUT, DEFAULT_INPUT, DEFAULT_INPUT_TYPE, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty())); // Verify that changing the compiler causes a rulekey change. RuleKey compilerChange = new DefaultRuleKeyFactory(0, hashCache, pathResolver, ruleFinder) .build( CxxPreprocessAndCompile.compile( params, new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, new GccCompiler(new HashedFileTool(Paths.get("different"))), DEFAULT_TOOL_FLAGS), DEFAULT_OUTPUT, DEFAULT_INPUT, DEFAULT_INPUT_TYPE, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty())); assertNotEquals(defaultRuleKey, compilerChange); // Verify that changing the operation causes a rulekey change. RuleKey operationChange = new DefaultRuleKeyFactory(0, hashCache, pathResolver, ruleFinder) .build( CxxPreprocessAndCompile.preprocessAndCompile( params, new PreprocessorDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, CxxPlatformUtils.DEFAULT_PLATFORM.getHeaderVerification(), DEFAULT_WORKING_DIR, DEFAULT_PREPROCESSOR, PreprocessorFlags.builder().build(), DEFAULT_FRAMEWORK_PATH_SEARCH_PATH_FUNCTION, Optional.empty(), /* leadingIncludePaths */ Optional.empty()), new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, DEFAULT_COMPILER, DEFAULT_TOOL_FLAGS), DEFAULT_OUTPUT, DEFAULT_INPUT, DEFAULT_INPUT_TYPE, Optional.empty(), CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty())); assertNotEquals(defaultRuleKey, operationChange); // Verify that changing the platform flags causes a rulekey change. RuleKey platformFlagsChange = new DefaultRuleKeyFactory(0, hashCache, pathResolver, ruleFinder) .build( CxxPreprocessAndCompile.compile( params, new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, DEFAULT_COMPILER, CxxToolFlags.explicitBuilder() .addPlatformFlags("-different") .setRuleFlags(DEFAULT_TOOL_FLAGS.getRuleFlags()) .build()), DEFAULT_OUTPUT, DEFAULT_INPUT, DEFAULT_INPUT_TYPE, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty())); assertNotEquals(defaultRuleKey, platformFlagsChange); // Verify that changing the rule flags causes a rulekey change. RuleKey ruleFlagsChange = new DefaultRuleKeyFactory(0, hashCache, pathResolver, ruleFinder) .build( CxxPreprocessAndCompile.compile( params, new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, DEFAULT_COMPILER, CxxToolFlags.explicitBuilder() .setPlatformFlags(DEFAULT_TOOL_FLAGS.getPlatformFlags()) .addRuleFlags("-other", "flags") .build()), DEFAULT_OUTPUT, DEFAULT_INPUT, DEFAULT_INPUT_TYPE, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty())); assertNotEquals(defaultRuleKey, ruleFlagsChange); // Verify that changing the input causes a rulekey change. RuleKey inputChange = new DefaultRuleKeyFactory(0, hashCache, pathResolver, ruleFinder) .build( CxxPreprocessAndCompile.compile( params, new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, DEFAULT_COMPILER, DEFAULT_TOOL_FLAGS), DEFAULT_OUTPUT, new FakeSourcePath("different"), DEFAULT_INPUT_TYPE, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty())); assertNotEquals(defaultRuleKey, inputChange); } @Test public void preprocessorFlagsRuleKeyChangesCauseRuleKeyChangesForPreprocessing() throws Exception { SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())); final SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); BuildTarget target = BuildTargetFactory.newInstance("//foo:bar"); final BuildRuleParams params = new FakeBuildRuleParamsBuilder(target).build(); final FakeFileHashCache hashCache = FakeFileHashCache.createFromStrings( ImmutableMap.<String, String>builder() .put("preprocessor", Strings.repeat("a", 40)) .put("compiler", Strings.repeat("a", 40)) .put("test.o", Strings.repeat("b", 40)) .put("test.cpp", Strings.repeat("c", 40)) .put("different", Strings.repeat("d", 40)) .put("foo/test.h", Strings.repeat("e", 40)) .put("path/to/a/plugin.so", Strings.repeat("f", 40)) .put("path/to/a/different/plugin.so", Strings.repeat("a0", 40)) .build()); class TestData { public RuleKey generate(PreprocessorFlags flags) throws Exception { return new DefaultRuleKeyFactory(0, hashCache, pathResolver, ruleFinder) .build( CxxPreprocessAndCompile.preprocessAndCompile( params, new PreprocessorDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, CxxPlatformUtils.DEFAULT_PLATFORM.getHeaderVerification(), DEFAULT_WORKING_DIR, DEFAULT_PREPROCESSOR, flags, DEFAULT_FRAMEWORK_PATH_SEARCH_PATH_FUNCTION, Optional.empty(), /* leadingIncludePaths */ Optional.empty()), new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, DEFAULT_COMPILER, CxxToolFlags.of()), DEFAULT_OUTPUT, DEFAULT_INPUT, DEFAULT_INPUT_TYPE, Optional.empty(), CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty())); } } TestData testData = new TestData(); PreprocessorFlags defaultFlags = PreprocessorFlags.builder().build(); PreprocessorFlags alteredFlags = defaultFlags.withFrameworkPaths( FrameworkPath.ofSourcePath(new FakeSourcePath("different"))); assertNotEquals(testData.generate(defaultFlags), testData.generate(alteredFlags)); } @Test public void usesCorrectCommandForCompile() { // Setup some dummy values for inputs to the CxxPreprocessAndCompile. SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); BuildTarget target = BuildTargetFactory.newInstance("//foo:bar"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(target).build(); CxxToolFlags flags = CxxToolFlags.explicitBuilder() .addPlatformFlags("-ffunction-sections") .addRuleFlags("-O3") .build(); Path output = Paths.get("test.o"); Path input = Paths.get("test.ii"); Path scratchDir = Paths.get("scratch"); CxxPreprocessAndCompile buildRule = CxxPreprocessAndCompile.compile( params, new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, DEFAULT_COMPILER, flags), output, new FakeSourcePath(input.toString()), DEFAULT_INPUT_TYPE, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty()); ImmutableList<String> expectedCompileCommand = ImmutableList.<String>builder() .add("compiler") .add("-ffunction-sections") .add("-O3") .add("-x", "c++") .add("-c") .add(input.toString()) .add("-o", output.toString()) .build(); ImmutableList<String> actualCompileCommand = buildRule.makeMainStep(pathResolver, scratchDir, false).getCommand(); assertEquals(expectedCompileCommand, actualCompileCommand); } @Test public void compilerAndPreprocessorAreAlwaysReturnedFromGetInputsAfterBuildingLocally() throws Exception { ProjectFilesystem filesystem = FakeProjectFilesystem.createJavaOnlyFilesystem(); SourcePath preprocessor = new PathSourcePath(filesystem, filesystem.getPath("preprocessor")); Tool preprocessorTool = new CommandTool.Builder().addInput(preprocessor).build(); SourcePath compiler = new PathSourcePath(filesystem, filesystem.getPath("compiler")); Tool compilerTool = new CommandTool.Builder().addInput(compiler).build(); SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); BuildTarget target = BuildTargetFactory.newInstance("//foo:bar"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(target).setProjectFilesystem(filesystem).build(); BuildContext context = FakeBuildContext.withSourcePathResolver(pathResolver); filesystem.writeContentsToPath( "test.o: " + pathResolver.getRelativePath(DEFAULT_INPUT) + " ", filesystem.getPath("test.o.dep")); FakeSourcePath fakeInput = new FakeSourcePath(filesystem, "test.cpp"); CxxPreprocessAndCompile cxxPreprocess = CxxPreprocessAndCompile.preprocessAndCompile( params, new PreprocessorDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, CxxPlatformUtils.DEFAULT_PLATFORM.getHeaderVerification(), DEFAULT_WORKING_DIR, new GccPreprocessor(preprocessorTool), PreprocessorFlags.builder().build(), DEFAULT_FRAMEWORK_PATH_SEARCH_PATH_FUNCTION, Optional.empty(), /* leadingIncludePaths */ Optional.empty()), new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, DEFAULT_COMPILER, CxxToolFlags.of()), DEFAULT_OUTPUT, fakeInput, DEFAULT_INPUT_TYPE, Optional.empty(), CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty()); assertThat(cxxPreprocess.getInputsAfterBuildingLocally(context), hasItem(preprocessor)); CxxPreprocessAndCompile cxxCompile = CxxPreprocessAndCompile.compile( params, new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, new GccCompiler(compilerTool), CxxToolFlags.of()), DEFAULT_OUTPUT, fakeInput, DEFAULT_INPUT_TYPE, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty()); assertThat(cxxCompile.getInputsAfterBuildingLocally(context), hasItem(compiler)); } @Test public void usesColorFlagForCompilationWhenRequested() { SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); BuildTarget target = BuildTargetFactory.newInstance("//foo:bar"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(target).build(); Path output = Paths.get("test.o"); Path input = Paths.get("test.ii"); Path scratchDir = Paths.get("scratch"); CompilerDelegate compilerDelegate = new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, COMPILER_WITH_COLOR_SUPPORT, CxxToolFlags.of()); CxxPreprocessAndCompile buildRule = CxxPreprocessAndCompile.compile( params, compilerDelegate, output, new FakeSourcePath(input.toString()), DEFAULT_INPUT_TYPE, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty()); ImmutableList<String> command = buildRule .makeMainStep(pathResolver, buildRule.getProjectFilesystem().getRootPath(), false) .getArguments(/* allowColorsInDiagnostics */ false); assertThat(command, not(hasItem(CompilerWithColorSupport.COLOR_FLAG))); command = buildRule .makeMainStep(pathResolver, scratchDir, false) .getArguments(/* allowColorsInDiagnostics */ true); assertThat(command, hasItem(CompilerWithColorSupport.COLOR_FLAG)); } @Test public void usesColorFlagForPreprocessingWhenRequested() throws Exception { SourcePathResolver pathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); BuildTarget target = BuildTargetFactory.newInstance("//foo:bar"); BuildRuleParams params = new FakeBuildRuleParamsBuilder(target).build(); Path output = Paths.get("test.ii"); Path input = Paths.get("test.cpp"); Path scratchDir = Paths.get("scratch"); CxxPreprocessAndCompile buildRule = CxxPreprocessAndCompile.preprocessAndCompile( params, new PreprocessorDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, CxxPlatformUtils.DEFAULT_PLATFORM.getHeaderVerification(), DEFAULT_WORKING_DIR, PREPROCESSOR_WITH_COLOR_SUPPORT, PreprocessorFlags.builder().build(), DEFAULT_FRAMEWORK_PATH_SEARCH_PATH_FUNCTION, Optional.empty(), /* leadingIncludePaths */ Optional.empty()), new CompilerDelegate( pathResolver, CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, COMPILER_WITH_COLOR_SUPPORT, CxxToolFlags.of()), output, new FakeSourcePath(input.toString()), DEFAULT_INPUT_TYPE, Optional.empty(), CxxPlatformUtils.DEFAULT_COMPILER_DEBUG_PATH_SANITIZER, Optional.empty()); ImmutableList<String> command = buildRule .makeMainStep(pathResolver, scratchDir, false) .getArguments(/* allowColorsInDiagnostics */ false); assertThat(command, not(hasItem(PreprocessorWithColorSupport.COLOR_FLAG))); command = buildRule .makeMainStep(pathResolver, scratchDir, false) .getArguments(/* allowColorsInDiagnostics */ true); assertThat(command, hasItem(CompilerWithColorSupport.COLOR_FLAG)); } @Test public void testGetGcnoFile() throws Exception { Path input = Paths.get("foo/bar.m.o"); Path output = CxxPreprocessAndCompile.getGcnoPath(input); assertEquals(Paths.get("foo/bar.m.gcno"), output); } }