| // Copyright 2015 The Bazel Authors. 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.config; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.testing.EqualsTester; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions.MapBackedChecksumCache; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions.OptionsChecksumCache; |
| import com.google.devtools.build.lib.analysis.util.ConfigurationTestCase; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.rules.cpp.CppConfiguration; |
| import com.google.devtools.build.lib.rules.objc.J2ObjcConfiguration; |
| import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester; |
| import com.google.devtools.build.lib.vfs.FileSystem; |
| import com.google.devtools.common.options.Options; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for {@link BuildConfigurationValue}. */ |
| @RunWith(JUnit4.class) |
| public final class BuildConfigurationValueTest extends ConfigurationTestCase { |
| |
| @Test |
| public void testBasics() throws Exception { |
| if (analysisMock.isThisBazel()) { |
| return; |
| } |
| |
| BuildConfigurationValue config = create("--cpu=piii"); |
| String outputDirPrefix = |
| outputBase + "/execroot/" + config.getWorkspaceName() + "/blaze-out/.*piii-fastbuild"; |
| |
| assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(outputDirPrefix); |
| assertThat(config.getBinDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(outputDirPrefix + "/bin"); |
| assertThat(config.getBuildInfoDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(outputDirPrefix + "/include"); |
| assertThat(config.getTestLogsDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(outputDirPrefix + "/testlogs"); |
| } |
| |
| @Test |
| public void testPlatformSuffix() throws Exception { |
| if (analysisMock.isThisBazel()) { |
| return; |
| } |
| |
| BuildConfigurationValue config = create("--platform_suffix=test"); |
| assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches( |
| outputBase |
| + "/execroot/" |
| + config.getWorkspaceName() |
| + "/blaze-out/.*k8-fastbuild-test"); |
| } |
| |
| @Test |
| public void testEnvironment() throws Exception { |
| if (analysisMock.isThisBazel()) { |
| return; |
| } |
| |
| ImmutableMap<String, String> env = create().getLocalShellEnvironment(); |
| assertThat(env).containsEntry("LANG", "en_US"); |
| assertThat(env).containsKey("PATH"); |
| assertThat(env.get("PATH")).contains("/bin:/usr/bin"); |
| } |
| |
| @Test |
| public void testHostCrosstoolTop() throws Exception { |
| if (analysisMock.isThisBazel()) { |
| return; |
| } |
| |
| BuildConfigurationValue config = |
| createConfiguration("--cpu=piii", "--noincompatible_enable_cc_toolchain_resolution"); |
| assertThat(config.getFragment(CppConfiguration.class).getRuleProvidingCcToolchainProvider()) |
| .isEqualTo(Label.parseCanonicalUnchecked("//tools/cpp:toolchain")); |
| |
| BuildConfigurationValue execConfig = |
| createExec("--noincompatible_enable_cc_toolchain_resolution"); |
| assertThat(execConfig.getFragment(CppConfiguration.class).getRuleProvidingCcToolchainProvider()) |
| .isEqualTo(Label.parseCanonicalUnchecked("//tools/cpp:toolchain")); |
| } |
| |
| @Test |
| public void testCaching() { |
| CoreOptions a = Options.getDefaults(CoreOptions.class); |
| CoreOptions b = Options.getDefaults(CoreOptions.class); |
| // The String representations of the CoreOptions must be equal even if these are |
| // different objects, if they were created with the same options (no options in this case). |
| assertThat(b.toString()).isEqualTo(a.toString()); |
| assertThat(b.cacheKey()).isEqualTo(a.cacheKey()); |
| } |
| |
| @Test |
| public void testTargetEnvironment() throws Exception { |
| BuildConfigurationValue oneEnvConfig = create("--target_environment=//foo"); |
| assertThat(oneEnvConfig.getTargetEnvironments()).containsExactly(Label.parseCanonical("//foo")); |
| |
| BuildConfigurationValue twoEnvsConfig = |
| create("--target_environment=//foo", "--target_environment=//bar"); |
| assertThat(twoEnvsConfig.getTargetEnvironments()) |
| .containsExactly(Label.parseCanonical("//foo"), Label.parseCanonical("//bar")); |
| |
| BuildConfigurationValue noEnvsConfig = create(); |
| assertThat(noEnvsConfig.getTargetEnvironments()).isEmpty(); |
| } |
| |
| @Test |
| public void testGlobalMakeVariableOverride() throws Exception { |
| assertThat(create().getMakeEnvironment()).containsEntry("COMPILATION_MODE", "fastbuild"); |
| BuildConfigurationValue config = create("--define", "COMPILATION_MODE=fluttershy"); |
| assertThat(config.getMakeEnvironment()).containsEntry("COMPILATION_MODE", "fluttershy"); |
| } |
| |
| @Test |
| public void testGetBuildOptionDetails() throws Exception { |
| // Directly defined options: |
| assertThat(create("-c", "dbg").getBuildOptionDetails().getOptionValue("compilation_mode")) |
| .isEqualTo(CompilationMode.DBG); |
| assertThat(create("-c", "opt").getBuildOptionDetails().getOptionValue("compilation_mode")) |
| .isEqualTo(CompilationMode.OPT); |
| |
| // Options defined in a fragment: |
| assertThat(create("--force_pic").getBuildOptionDetails().getOptionValue("force_pic")) |
| .isEqualTo(Boolean.TRUE); |
| assertThat(create("--noforce_pic").getBuildOptionDetails().getOptionValue("force_pic")) |
| .isEqualTo(Boolean.FALSE); |
| |
| // Legitimately null option: |
| assertThat(create().getBuildOptionDetails().getOptionValue("test_filter")).isNull(); |
| } |
| |
| @Test |
| public void testConfigFragmentsAreShareableAcrossConfigurations() throws Exception { |
| // Note we can't use any fragments that load files (e.g. CROSSTOOL) because those get |
| // Skyframe-invalidated between create() calls. |
| BuildConfigurationValue config1 = create("--javacopt=foo"); |
| BuildConfigurationValue config2 = create("--javacopt=bar"); |
| BuildConfigurationValue config3 = create("--j2objc_translation_flags=baz"); |
| // Shared because all j2objc options are the same: |
| assertThat(config1.getFragment(J2ObjcConfiguration.class)) |
| .isSameInstanceAs(config2.getFragment(J2ObjcConfiguration.class)); |
| // Distinct because the j2objc options differ: |
| assertThat(config1.getFragment(J2ObjcConfiguration.class)) |
| .isNotSameInstanceAs(config3.getFragment(J2ObjcConfiguration.class)); |
| } |
| |
| @Test |
| public void testCommandLineVariables() throws Exception { |
| BuildConfigurationValue config = |
| create("--define", "a=b/c:d", "--define", "b=FOO", "--define", "DEFUN=Nope"); |
| assertThat(config.getCommandLineBuildVariables().get("a")).isEqualTo("b/c:d"); |
| assertThat(config.getCommandLineBuildVariables().get("b")).isEqualTo("FOO"); |
| assertThat(config.getCommandLineBuildVariables().get("DEFUN")).isEqualTo("Nope"); |
| } |
| |
| // Regression test for bug #2518997: |
| // "--define in blazerc overrides --define from command line" |
| @Test |
| public void testCommandLineVariablesOverride() throws Exception { |
| BuildConfigurationValue config = create("--define", "a=b", "--define", "a=c"); |
| assertThat(config.getCommandLineBuildVariables().get("a")).isEqualTo("c"); |
| } |
| |
| @Test |
| public void testNormalization_definesWithDifferentNames() throws Exception { |
| BuildConfigurationValue config = create("--define", "a=1", "--define", "b=2"); |
| CoreOptions options = config.getOptions().get(CoreOptions.class); |
| assertThat(ImmutableMap.copyOf(options.commandLineBuildVariables)) |
| .containsExactly("a", "1", "b", "2"); |
| } |
| |
| @Test |
| public void testNormalization_definesWithSameName() throws Exception { |
| BuildConfigurationValue config = create("--define", "a=1", "--define", "a=2"); |
| CoreOptions options = config.getOptions().get(CoreOptions.class); |
| assertThat(ImmutableMap.copyOf(options.commandLineBuildVariables)).containsExactly("a", "2"); |
| assertThat(config).isEqualTo(create("--define", "a=2")); |
| } |
| |
| // This is really a test of option parsing, not command-line variable |
| // semantics. |
| @Test |
| public void testCommandLineVariablesWithFunnyCharacters() throws Exception { |
| BuildConfigurationValue config = |
| create( |
| "--define", "foo=#foo", |
| "--define", "comma=a,b", |
| "--define", "space=foo bar", |
| "--define", "thing=a \"quoted\" thing", |
| "--define", "qspace=a\\ quoted\\ space", |
| "--define", "#a=pounda"); |
| assertThat(config.getCommandLineBuildVariables().get("foo")).isEqualTo("#foo"); |
| assertThat(config.getCommandLineBuildVariables().get("comma")).isEqualTo("a,b"); |
| assertThat(config.getCommandLineBuildVariables().get("space")).isEqualTo("foo bar"); |
| assertThat(config.getCommandLineBuildVariables().get("thing")).isEqualTo("a \"quoted\" thing"); |
| assertThat(config.getCommandLineBuildVariables().get("qspace")).isEqualTo("a\\ quoted\\ space"); |
| assertThat(config.getCommandLineBuildVariables().get("#a")).isEqualTo("pounda"); |
| } |
| |
| @Test |
| public void testExecDefine() throws Exception { |
| BuildConfigurationValue cfg = createExec("--define=foo=bar"); |
| assertThat(cfg.getCommandLineBuildVariables().get("foo")).isEqualTo("bar"); |
| } |
| |
| @Test |
| public void testHostCompilationModeDefault() throws Exception { |
| BuildConfigurationValue cfg = createExec(); |
| assertThat(cfg.getCompilationMode()).isEqualTo(CompilationMode.OPT); |
| } |
| |
| @Test |
| public void testHostCompilationModeNonDefault() throws Exception { |
| BuildConfigurationValue cfg = createExec("--host_compilation_mode=dbg"); |
| assertThat(cfg.getCompilationMode()).isEqualTo(CompilationMode.DBG); |
| } |
| |
| @Test |
| public void testIncompatibleMergeGenfilesDirectory() throws Exception { |
| BuildConfigurationValue target = create("--incompatible_merge_genfiles_directory"); |
| BuildConfigurationValue exec = createExec("--incompatible_merge_genfiles_directory"); |
| assertThat(target.getGenfilesDirectory(RepositoryName.MAIN)) |
| .isEqualTo(target.getBinDirectory(RepositoryName.MAIN)); |
| assertThat(exec.getGenfilesDirectory(RepositoryName.MAIN)) |
| .isEqualTo(exec.getBinDirectory(RepositoryName.MAIN)); |
| } |
| |
| private ImmutableList<BuildConfigurationValue> getTestConfigurations() throws Exception { |
| return ImmutableList.of( |
| create(), |
| create("--cpu=piii"), |
| create("--javacopt=foo"), |
| create("--platform_suffix=-test"), |
| create("--target_environment=//foo", "--target_environment=//bar"), |
| create("--incompatible_merge_genfiles_directory"), |
| create( |
| "--define", |
| "foo=#foo", |
| "--define", |
| "comma=a,b", |
| "--define", |
| "space=foo bar", |
| "--define", |
| "thing=a \"quoted\" thing", |
| "--define", |
| "qspace=a\\ quoted\\ space", |
| "--define", |
| "#a=pounda")); |
| } |
| |
| @Test |
| public void testCodec() throws Exception { |
| // Unnecessary ImmutableList.copyOf apparently necessary to choose non-varargs constructor. |
| new SerializationTester(ImmutableList.copyOf(getTestConfigurations())) |
| .addDependency(FileSystem.class, getScratch().getFileSystem()) |
| .addDependency(OptionsChecksumCache.class, new MapBackedChecksumCache()) |
| .setVerificationFunction(BuildConfigurationValueTest::verifyDeserialized) |
| .runTests(); |
| } |
| |
| @Test |
| public void testKeyCodec() throws Exception { |
| new SerializationTester( |
| getTestConfigurations().stream() |
| .map(BuildConfigurationValue::getKey) |
| .collect(ImmutableList.toImmutableList())) |
| .addDependency(OptionsChecksumCache.class, new MapBackedChecksumCache()) |
| .runTests(); |
| } |
| |
| @Test |
| public void testPlatformInOutputDir_legacy_defaultPlatform() throws Exception { |
| BuildConfigurationValue config = |
| create( |
| "--experimental_platform_in_output_dir", |
| "--experimental_use_platforms_in_output_dir_legacy_heuristic", |
| "--cpu=k8"); |
| |
| assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(".*/[^/]+-out/k8-fastbuild"); |
| } |
| |
| @Test |
| public void testPlatformInOutputDir_legacy_withPlatform() throws Exception { |
| BuildConfigurationValue config = |
| create( |
| "--experimental_platform_in_output_dir", |
| "--experimental_use_platforms_in_output_dir_legacy_heuristic", |
| "--platforms=//platform:alpha"); |
| |
| assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(".*/[^/]+-out/alpha-fastbuild"); |
| } |
| |
| @Test |
| public void testPlatformInOutputDir_defaultPlatform() throws Exception { |
| BuildConfigurationValue config = |
| create( |
| "--experimental_platform_in_output_dir", |
| "--noexperimental_use_platforms_in_output_dir_legacy_heuristic", |
| "--cpu=k8"); |
| // See tests of these flags with platform_mappings for more realistic results. |
| assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(".*/[^/]+-out/platform-\\w*-fastbuild"); |
| } |
| |
| @Test |
| public void testPlatformInOutputDir_withPlatform() throws Exception { |
| BuildConfigurationValue config = |
| create( |
| "--experimental_platform_in_output_dir", |
| "--noexperimental_use_platforms_in_output_dir_legacy_heuristic", |
| "--platforms=//platform:alpha"); |
| |
| assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(".*/[^/]+-out/platform-\\w*-fastbuild"); |
| } |
| |
| @Test |
| public void testPlatformInOutputDir_withPlatformAndMatchingOverride() throws Exception { |
| BuildConfigurationValue config = |
| create( |
| "--experimental_platform_in_output_dir", |
| "--noexperimental_use_platforms_in_output_dir_legacy_heuristic", |
| "--experimental_override_name_platform_in_output_dir=//platform:alpha=alpha", |
| "--platforms=//platform:alpha"); |
| |
| assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(".*/[^/]+-out/alpha-fastbuild"); |
| } |
| |
| @Test |
| public void testPlatformInOutputDir_withPlatformAndNonMatchingOverride() throws Exception { |
| BuildConfigurationValue config = |
| create( |
| "--experimental_platform_in_output_dir", |
| "--noexperimental_use_platforms_in_output_dir_legacy_heuristic", |
| "--experimental_override_name_platform_in_output_dir=//platform:beta=beta", |
| "--platforms=//platform:alpha"); |
| |
| assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString()) |
| .matches(".*/[^/]+-out/platform-\\w*-fastbuild"); |
| } |
| |
| @Test |
| public void testConfigurationEquality() throws Exception { |
| // Note that, in practice, test_arg should not be used as a no-op argument; however, |
| // these configurations are never trimmed nor even used to build targets so not an issue. |
| new EqualsTester() |
| .addEqualityGroup( |
| createRaw(parseBuildOptions("--test_arg=1a"), "k8", "testrepo", false), |
| createRaw(parseBuildOptions("--test_arg=1a"), "k8", "testrepo", false)) |
| // Different BuildOptions means non-equal |
| .addEqualityGroup(createRaw(parseBuildOptions("--test_arg=1b"), "k8", "testrepo", false)) |
| // Different --experimental_sibling_repository_layout means non-equal |
| .addEqualityGroup(createRaw(parseBuildOptions("--test_arg=2"), "k8", "testrepo", true)) |
| .addEqualityGroup(createRaw(parseBuildOptions("--test_arg=2"), "k8", "testrepo", false)) |
| // Different repositoryName means non-equal |
| .addEqualityGroup(createRaw(parseBuildOptions("--test_arg=3"), "k8", "testrepo1", false)) |
| .addEqualityGroup(createRaw(parseBuildOptions("--test_arg=3"), "k8", "testrepo2", false)) |
| // Different transitionDirectoryNameFragment means non-equal |
| .addEqualityGroup(createRaw(parseBuildOptions("--test_arg=3"), "k8", "testrepo", false)) |
| .addEqualityGroup(createRaw(parseBuildOptions("--test_arg=3"), "arm", "testrepo", false)) |
| .addEqualityGroup(createRaw(parseBuildOptions("--test_arg=3"), "risc", "testrepo", false)) |
| .testEquals(); |
| } |
| |
| /** |
| * Partial verification of deserialized BuildConfigurationValue. |
| * |
| * <p>Direct comparison of deserialized to subject doesn't work because Fragment classes do not |
| * implement equals. This runs the part of BuildConfigurationValue.equals that has equals |
| * definitions. |
| */ |
| private static void verifyDeserialized( |
| BuildConfigurationValue subject, BuildConfigurationValue deserialized) { |
| assertThat(deserialized.getOptions()).isEqualTo(subject.getOptions()); |
| } |
| } |