| // Copyright 2023 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.buildtool; |
| |
| import static com.google.common.collect.ImmutableMap.toImmutableMap; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.devtools.build.lib.packages.Attribute.attr; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; |
| import static com.google.devtools.build.lib.packages.Type.STRING; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.analysis.actions.FileWriteActionContext; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.analysis.config.BuildOptionsView; |
| import com.google.devtools.build.lib.analysis.config.Fragment; |
| import com.google.devtools.build.lib.analysis.config.FragmentOptions; |
| import com.google.devtools.build.lib.analysis.config.RequiresOptions; |
| import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition; |
| import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition; |
| import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory; |
| import com.google.devtools.build.lib.analysis.util.MockRule; |
| import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase; |
| import com.google.devtools.build.lib.buildtool.util.TestRuleModule; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.exec.FileWriteStrategy; |
| import com.google.devtools.build.lib.exec.ModuleActionContextRegistry; |
| import com.google.devtools.build.lib.packages.AttributeTransitionData; |
| import com.google.devtools.build.lib.packages.ImplicitOutputsFunction; |
| import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; |
| import com.google.devtools.build.lib.packages.RuleTransitionData; |
| import com.google.devtools.build.lib.runtime.BlazeModule; |
| import com.google.devtools.build.lib.runtime.CommandEnvironment; |
| import com.google.devtools.build.lib.testutil.TestConstants; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.Symlinks; |
| import com.google.devtools.common.options.Option; |
| import com.google.devtools.common.options.OptionDocumentationCategory; |
| import com.google.devtools.common.options.OptionEffectTag; |
| import com.google.testing.junit.testparameterinjector.TestParameter; |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector; |
| import java.io.IOException; |
| import java.util.Optional; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| @RunWith(TestParameterInjector.class) |
| public final class ConvenienceSymlinkTest extends BuildIntegrationTestCase { |
| |
| /** test options to cause the output directory to change */ |
| public static final class PathTestOptions extends FragmentOptions { |
| @Option( |
| name = "output_directory_name", |
| documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, |
| effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, |
| defaultValue = "default") |
| public String outputDirectoryName; |
| |
| @Option( |
| name = "useless_option", |
| documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, |
| effectTags = {OptionEffectTag.NO_OP}, |
| defaultValue = "default") |
| public String uselessOption; |
| } |
| |
| /** Test fragment. */ |
| @RequiresOptions(options = {PathTestOptions.class}) |
| public static final class PathTestConfiguration extends Fragment { |
| private final String outputDirectoryName; |
| |
| public PathTestConfiguration(BuildOptions buildOptions) { |
| this.outputDirectoryName = buildOptions.get(PathTestOptions.class).outputDirectoryName; |
| } |
| |
| @Override |
| public void processForOutputPathMnemonic(OutputDirectoriesContext ctx) |
| throws Fragment.OutputDirectoriesContext.AddToMnemonicException { |
| ctx.markAsExplicitInOutputPathFor("output_directory_name"); |
| ctx.addToMnemonic(outputDirectoryName); |
| } |
| } |
| |
| private static final class PathAttributeTransitionFactory |
| implements TransitionFactory<AttributeTransitionData> { |
| private final String newPath; |
| |
| PathAttributeTransitionFactory(String newPath) { |
| this.newPath = newPath; |
| } |
| |
| @Override |
| public ConfigurationTransition create(AttributeTransitionData data) { |
| return new PatchTransition() { |
| @Override |
| public ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments() { |
| return ImmutableSet.of(PathTestOptions.class); |
| } |
| |
| @Override |
| public BuildOptions patch(BuildOptionsView options, EventHandler eventHandler) { |
| BuildOptionsView clone = options.clone(); |
| clone.get(PathTestOptions.class).outputDirectoryName = newPath; |
| return clone.underlying(); |
| } |
| }; |
| } |
| |
| @Override |
| public TransitionType transitionType() { |
| return TransitionType.ATTRIBUTE; |
| } |
| } |
| |
| private static final class PathRuleTransitionFactory |
| implements TransitionFactory<RuleTransitionData> { |
| @Override |
| public PatchTransition create(RuleTransitionData ruleData) { |
| String newPath = NonconfigurableAttributeMapper.of(ruleData.rule()).get("path", STRING); |
| return new PatchTransition() { |
| @Override |
| public ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments() { |
| return ImmutableSet.of(PathTestOptions.class); |
| } |
| |
| @Override |
| public BuildOptions patch(BuildOptionsView options, EventHandler eventHandler) { |
| BuildOptionsView clone = options.clone(); |
| clone.get(PathTestOptions.class).outputDirectoryName = newPath; |
| return clone.underlying(); |
| } |
| }; |
| } |
| |
| @Override |
| public TransitionType transitionType() { |
| return TransitionType.RULE; |
| } |
| } |
| |
| private static final class UselessOptionTransition implements PatchTransition { |
| private final String newValue; |
| |
| UselessOptionTransition(String newValue) { |
| this.newValue = newValue; |
| } |
| |
| @Override |
| public ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments() { |
| return ImmutableSet.of(PathTestOptions.class); |
| } |
| |
| @Override |
| public BuildOptions patch(BuildOptionsView options, EventHandler eventHandler) { |
| BuildOptionsView clone = options.clone(); |
| clone.get(PathTestOptions.class).uselessOption = newValue; |
| return clone.underlying(); |
| } |
| } |
| |
| private static final class UselessOptionTransitionFactory |
| implements TransitionFactory<RuleTransitionData> { |
| @Override |
| public PatchTransition create(RuleTransitionData ruleData) { |
| return new UselessOptionTransition( |
| NonconfigurableAttributeMapper.of(ruleData.rule()).get("value", STRING)); |
| } |
| |
| @Override |
| public TransitionType transitionType() { |
| return TransitionType.RULE; |
| } |
| } |
| |
| private static final class PathTestRulesModule extends BlazeModule { |
| @Override |
| public void registerActionContexts( |
| ModuleActionContextRegistry.Builder registryBuilder, |
| CommandEnvironment env, |
| BuildRequest buildRequest) { |
| // we need an implementation of FileWriteActionContext to get our file writes to succeed |
| registryBuilder.register(FileWriteActionContext.class, new FileWriteStrategy()); |
| // we need something to consume FileWriteActionContext or the registration will have no effect |
| registryBuilder.restrictTo(FileWriteActionContext.class, "local"); |
| } |
| |
| @Override |
| public void initializeRuleClasses(ConfiguredRuleClassProvider.Builder builder) { |
| TestRuleModule.getModule().initializeRuleClasses(builder); |
| |
| MockRule basicRule = |
| () -> |
| MockRule.define( |
| "basic_rule", |
| (ruleBuilder, env) -> |
| ruleBuilder |
| .add(attr("deps", LABEL_LIST).allowedFileTypes()) |
| .setImplicitOutputsFunction( |
| ImplicitOutputsFunction.fromTemplates("%{name}.bin"))); |
| MockRule incomingTransitionRule = |
| () -> |
| MockRule.define( |
| "incoming_transition_rule", |
| (ruleBuilder, env) -> |
| ruleBuilder |
| .add( |
| attr("path", STRING) |
| .mandatory() |
| .nonconfigurable("used in transition")) |
| .add(attr("deps", LABEL_LIST).allowedFileTypes()) |
| .setImplicitOutputsFunction( |
| ImplicitOutputsFunction.fromTemplates("%{name}.bin")) |
| .cfg(new PathRuleTransitionFactory())); |
| MockRule incomingUnrelatedTransitionRule = |
| () -> |
| MockRule.define( |
| "incoming_unrelated_transition_rule", |
| (ruleBuilder, env) -> |
| ruleBuilder |
| .add( |
| attr("value", STRING) |
| .mandatory() |
| .nonconfigurable("used in transition")) |
| .add(attr("deps", LABEL_LIST).allowedFileTypes()) |
| .setImplicitOutputsFunction( |
| ImplicitOutputsFunction.fromTemplates("%{name}.bin")) |
| .cfg(new UselessOptionTransitionFactory())); |
| |
| MockRule outgoingTransitionRule = |
| () -> |
| MockRule.define( |
| "outgoing_transition_rule", |
| (ruleBuilder, env) -> |
| ruleBuilder |
| .add( |
| attr("deps", LABEL_LIST) |
| .allowedFileTypes() |
| .cfg( |
| new PathAttributeTransitionFactory( |
| "set_by_outgoing_transition_rule"))) |
| .setImplicitOutputsFunction( |
| ImplicitOutputsFunction.fromTemplates("%{name}.bin"))); |
| |
| builder |
| .addConfigurationFragment(PathTestConfiguration.class) |
| .addRuleDefinition(basicRule) |
| .addRuleDefinition(incomingTransitionRule) |
| .addRuleDefinition(incomingUnrelatedTransitionRule) |
| .addRuleDefinition(outgoingTransitionRule); |
| } |
| } |
| |
| @TestParameter boolean mergedAnalysisExecution; |
| |
| @Override |
| protected void setupOptions() throws Exception { |
| super.setupOptions(); |
| |
| addOptions("--experimental_merged_skyframe_analysis_execution=" + mergedAnalysisExecution); |
| } |
| |
| @Override |
| protected BlazeModule getRulesModule() { |
| return new PathTestRulesModule(); |
| } |
| |
| private Path getExecRoot() { |
| return getBlazeWorkspace().getDirectories().getExecRoot(TestConstants.WORKSPACE_NAME); |
| } |
| |
| private Path getOutputPath() { |
| return getBlazeWorkspace().getDirectories().getOutputPath(TestConstants.WORKSPACE_NAME); |
| } |
| |
| /** Gets a mapping from the workspace-relative paths of symlinks to the paths they point to. */ |
| private ImmutableMap<String, Path> getConvenienceSymlinks() throws IOException { |
| return getWorkspace().getDirectoryEntries().stream() |
| .filter(Path::isSymbolicLink) |
| .collect( |
| toImmutableMap( |
| (path) -> path.relativeTo(getWorkspace()).toString(), |
| (path) -> { |
| try { |
| return getWorkspace().getRelative(path.readSymbolicLinkUnchecked()); |
| } catch (IOException ex) { |
| throw new RuntimeException(ex); |
| } |
| })); |
| } |
| |
| @Test |
| public void sanityCheckFilesHaveNullConfigurations() throws Exception { |
| // Other tests in this file expect that files will have a null configuration. |
| write("files/BUILD", "exports_files(['foo.txt', 'bar.txt'])"); |
| write("files/foo.txt", "This is just a test file to pretend to build."); |
| write("files/bar.txt", "This is just a test file to pretend to build."); |
| BuildResult result = buildTarget("//files:foo.txt", "//files:bar.txt"); |
| |
| assertThat( |
| result.getActualTargets().stream() |
| .collect( |
| toImmutableMap( |
| target -> target.getLabel().toString(), |
| target -> Optional.ofNullable(target.getConfigurationKey())))) |
| .containsExactly( |
| "//files:foo.txt", Optional.empty(), |
| "//files:bar.txt", Optional.empty()); |
| } |
| |
| @Test |
| public void sanityCheckOutputDirectory() throws Exception { |
| // Other tests in this file expect that changing the output_directory_name flag changes the |
| // output directory of the configuration to the same value. |
| |
| // This test relies on hard-coded paths for intermediate artifacts so |
| // must force output directory naming into legacy behaviors for now. |
| addOptions( |
| "--output_directory_name=set_by_flag", |
| "--compilation_mode=fastbuild", |
| "--experimental_exec_configuration_distinguisher=legacy"); |
| |
| write( |
| "path/BUILD", |
| """ |
| basic_rule(name = "from_flag") |
| |
| incoming_transition_rule( |
| name = "from_transition", |
| path = "set_by_transition", |
| ) |
| |
| incoming_unrelated_transition_rule( |
| name = "unrelated_transition", |
| value = "whatever", |
| ) |
| |
| outgoing_transition_rule(name = "outgoing_transition") |
| """); |
| BuildResult result = |
| buildTarget( |
| "//path:from_flag", |
| "//path:from_transition", |
| "//path:unrelated_transition", |
| "//path:outgoing_transition"); |
| |
| assertThat( |
| result.getActualTargets().stream() |
| .collect( |
| toImmutableMap( |
| (target) -> target.getLabel().toString(), |
| (target) -> |
| getConfigurationFromLastBuildResult(target) |
| .getOutputDirectory(RepositoryName.MAIN) |
| .getRoot() |
| .asPath() |
| .relativeTo(getOutputPath()) |
| .toString()))) |
| .containsExactly( |
| "//path:from_flag", getTargetConfiguration().getCpu() + "-fastbuild-set_by_flag", |
| "//path:from_transition", |
| getTargetConfiguration().getCpu() + "-fastbuild-set_by_transition", |
| "//path:unrelated_transition", |
| getTargetConfiguration().getCpu() + "-fastbuild-set_by_flag-ST-040655c91309", |
| "//path:outgoing_transition", |
| getTargetConfiguration().getCpu() + "-fastbuild-set_by_flag"); |
| } |
| |
| @Test |
| public void buildingNothing_unsetsSymlinks() throws Exception { |
| addOptions("--symlink_prefix=nothing-", "--incompatible_skip_genfiles_symlink=false"); |
| |
| Path config = getOutputBase().getRelative("some-imaginary-config"); |
| // put symlinks at the convenience symlinks spots to simulate a prior build |
| Path binLink = getWorkspace().getChild("nothing-bin"); |
| binLink.createSymbolicLink(config.getChild("bin")); |
| Path genfilesLink = getWorkspace().getChild("nothing-genfiles"); |
| genfilesLink.createSymbolicLink(config.getChild("genfiles")); |
| Path testlogsLink = getWorkspace().getChild("nothing-testlogs"); |
| testlogsLink.createSymbolicLink(config.getChild("testlogs")); |
| |
| buildTarget(); |
| |
| // there should be nothing at any of the convenience symlinks which depend on configuration - |
| // the symlinks put there during the simulated prior build should have been deleted |
| assertThat(binLink.exists(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(genfilesLink.exists(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(testlogsLink.exists(Symlinks.NOFOLLOW)).isTrue(); |
| |
| // the execroot and output path symlinks should have been created because they don't depend on |
| // configuration, but no other symlinks should have been created |
| assertThat(getConvenienceSymlinks()) |
| .containsExactly( |
| // notably absent: nothing-bin, nothing-genfiles, nothing-testlogs |
| // these were also not created under other names |
| "nothing-bin", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-default/bin"), |
| "nothing-genfiles", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-default/bin"), |
| "nothing-testlogs", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-default/testlogs"), |
| "nothing-" + TestConstants.WORKSPACE_NAME, |
| getExecRoot(), |
| "nothing-out", |
| getOutputPath()); |
| } |
| |
| @Test |
| public void buildingOnlyTargetsWithNullConfigurations_unsetsSymlinks() throws Exception { |
| addOptions("--symlink_prefix=nulled-", "--incompatible_skip_genfiles_symlink=false"); |
| |
| Path config = getOutputBase().getRelative("some-imaginary-config"); |
| // put symlinks at the convenience symlinks spots to simulate a prior build |
| Path binLink = getWorkspace().getChild("nulled-bin"); |
| binLink.createSymbolicLink(config.getChild("bin")); |
| Path genfilesLink = getWorkspace().getChild("nulled-genfiles"); |
| genfilesLink.createSymbolicLink(config.getChild("genfiles")); |
| Path testlogsLink = getWorkspace().getChild("nulled-testlogs"); |
| testlogsLink.createSymbolicLink(config.getChild("testlogs")); |
| |
| write("files/BUILD", "exports_files(['foo.txt', 'bar.txt'])"); |
| write("files/foo.txt", "This is just a test file to pretend to build."); |
| write("files/bar.txt", "This is just a test file to pretend to build."); |
| buildTarget("//files:foo.txt", "//files:bar.txt"); |
| |
| // there should be nothing at any of the convenience symlinks which depend on configuration - |
| // the symlinks put there during the simulated prior build should have been deleted |
| assertThat(binLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(genfilesLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(testlogsLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| |
| // the execroot and output path symlinks should have been created because they don't depend on |
| // configuration, but no other symlinks should have been created |
| assertThat(getConvenienceSymlinks()) |
| .containsExactly( |
| // notably absent: nulled-bin, nulled-genfiles, nulled-testlogs |
| // these were also not created under other names |
| "nulled-" + TestConstants.WORKSPACE_NAME, getExecRoot(), "nulled-out", getOutputPath()); |
| } |
| |
| @Test |
| public void buildingTargetsWithDifferentOutputDirectories_unsetsSymlinksIfNoneAreTopLevel() |
| throws Exception { |
| addOptions("--symlink_prefix=ambiguous-", "--incompatible_skip_genfiles_symlink=false"); |
| |
| Path config = getOutputPath().getRelative("some-imaginary-config"); |
| // put symlinks at the convenience symlinks spots to simulate a prior build |
| Path binLink = getWorkspace().getChild("ambiguous-bin"); |
| binLink.createSymbolicLink(config.getChild("bin")); |
| Path genfilesLink = getWorkspace().getChild("ambiguous-genfiles"); |
| genfilesLink.createSymbolicLink(config.getChild("genfiles")); |
| Path testlogsLink = getWorkspace().getChild("ambiguous-testlogs"); |
| testlogsLink.createSymbolicLink(config.getChild("testlogs")); |
| |
| write( |
| "targets/BUILD", |
| """ |
| incoming_transition_rule( |
| name = "config1", |
| path = "set_from_config1", |
| ) |
| |
| incoming_transition_rule( |
| name = "config2", |
| path = "set_from_config2", |
| ) |
| """); |
| buildTarget("//targets:config1", "//targets:config2"); |
| |
| // there should be nothing at any of the convenience symlinks which depend on configuration - |
| // the symlinks put there during the simulated prior build should have been deleted |
| assertThat(binLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(genfilesLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(testlogsLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| |
| // the execroot and output path symlinks should have been created because they don't depend on |
| // configuration, but no other symlinks should have been created |
| assertThat(getConvenienceSymlinks()) |
| .containsExactly( |
| // notably absent: ambiguous-bin, ambiguous-genfiles, ambiguous-testlogs |
| // these were also not created under other names |
| "ambiguous-" + TestConstants.WORKSPACE_NAME, |
| getExecRoot(), |
| "ambiguous-out", |
| getOutputPath()); |
| } |
| |
| @Test |
| public void buildingTargetsWithDifferentOutputDirectories_setsSymlinksIfAnyAreTopLevel() |
| throws Exception { |
| addOptions( |
| "--symlink_prefix=ambiguous-", |
| "--incompatible_skip_genfiles_symlink=false", |
| "--incompatible_merge_genfiles_directory=false", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| Path config = getOutputPath().getRelative("some-imaginary-config"); |
| // put symlinks at the convenience symlinks spots to simulate a prior build |
| Path binLink = getWorkspace().getChild("ambiguous-bin"); |
| binLink.createSymbolicLink(config.getChild("bin")); |
| Path genfilesLink = getWorkspace().getChild("ambiguous-genfiles"); |
| genfilesLink.createSymbolicLink(config.getChild("genfiles")); |
| Path testlogsLink = getWorkspace().getChild("ambiguous-testlogs"); |
| testlogsLink.createSymbolicLink(config.getChild("testlogs")); |
| |
| write( |
| "targets/BUILD", |
| """ |
| basic_rule(name = "default") |
| |
| incoming_transition_rule( |
| name = "config1", |
| path = "set_from_config1", |
| ) |
| """); |
| buildTarget("//targets:default", "//targets:config1"); |
| |
| assertThat(getConvenienceSymlinks()) |
| .containsExactly( |
| "ambiguous-bin", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-default/bin"), |
| "ambiguous-genfiles", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-default/genfiles"), |
| "ambiguous-testlogs", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-default/testlogs"), |
| "ambiguous-" + TestConstants.WORKSPACE_NAME, |
| getExecRoot(), |
| "ambiguous-out", |
| getOutputPath()); |
| } |
| |
| @Test |
| public void buildingTargetsWithSameConfiguration_setsSymlinks() throws Exception { |
| addOptions( |
| "--symlink_prefix=same-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_merge_genfiles_directory=false", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| write( |
| "targets/BUILD", |
| """ |
| incoming_transition_rule( |
| name = "configged1", |
| path = "configured", |
| ) |
| |
| incoming_transition_rule( |
| name = "configged2", |
| path = "configured", |
| ) |
| """); |
| buildTarget("//targets:configged1", "//targets:configged2"); |
| |
| assertThat(getConvenienceSymlinks()) |
| .containsExactly( |
| "same-bin", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-configured/bin"), |
| "same-genfiles", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-configured/genfiles"), |
| "same-testlogs", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-configured/testlogs"), |
| "same-" + TestConstants.WORKSPACE_NAME, |
| getExecRoot(), |
| "same-out", |
| getOutputPath()); |
| } |
| |
| @Test |
| public void buildingSameConfigurationTargetsWithDifferentConfigurationDeps_setsSymlinks() |
| throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=united-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_merge_genfiles_directory=false", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| write( |
| "targets/BUILD", |
| """ |
| outgoing_transition_rule( |
| name = "configged1", |
| deps = [":alternate1"], |
| ) |
| |
| outgoing_transition_rule( |
| name = "configged2", |
| deps = [":alternate2"], |
| ) |
| |
| basic_rule(name = "alternate1") |
| |
| incoming_transition_rule( |
| name = "alternate2", |
| path = "alternate_transition", |
| ) |
| """); |
| buildTarget("//targets:configged1", "//targets:configged2"); |
| |
| assertThat(getConvenienceSymlinks()) |
| .containsExactly( |
| "united-bin", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/bin"), |
| "united-genfiles", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/genfiles"), |
| "united-testlogs", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/testlogs"), |
| "united-" + TestConstants.WORKSPACE_NAME, |
| getExecRoot(), |
| "united-out", |
| getOutputPath()); |
| } |
| |
| @Test |
| public void differentConfigurationSameOutputDirectory_setsSymlinks() throws Exception { |
| // TODO(blaze-configurability-team): Remove when `--experimental_output_directory_naming_scheme` |
| // is universally set to `diff_from_baseline` |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=unchanged-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_merge_genfiles_directory=false", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| write( |
| "targets/BUILD", |
| """ |
| basic_rule(name = "from_flag") |
| |
| incoming_unrelated_transition_rule( |
| name = "configged1", |
| value = "one_transition", |
| ) |
| |
| incoming_unrelated_transition_rule( |
| name = "configged2", |
| value = "alternate_transition", |
| ) |
| """); |
| buildTarget("//targets:from_flag", "//targets:configged1", "//targets:configged2"); |
| |
| assertThat(getConvenienceSymlinks()) |
| .containsExactly( |
| "unchanged-bin", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/bin"), |
| "unchanged-genfiles", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/genfiles"), |
| "unchanged-testlogs", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/testlogs"), |
| "unchanged-" + TestConstants.WORKSPACE_NAME, |
| getExecRoot(), |
| "unchanged-out", |
| getOutputPath()); |
| } |
| |
| @Test |
| public void nullConfigurationWithOtherMatchingOutputDir_setsSymlinks() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=mixed-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_merge_genfiles_directory=false", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| write( |
| "targets/BUILD", |
| """ |
| exports_files(["null"]) |
| |
| basic_rule(name = "configured1") |
| |
| basic_rule(name = "configured2") |
| """); |
| write("targets/null", "This is just a test file to pretend to build."); |
| buildTarget("//targets:null", "//targets:configured1", "//targets:configured2"); |
| |
| assertThat(getConvenienceSymlinks()) |
| .containsExactly( |
| "mixed-bin", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/bin"), |
| "mixed-genfiles", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/genfiles"), |
| "mixed-testlogs", |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/testlogs"), |
| "mixed-" + TestConstants.WORKSPACE_NAME, |
| getExecRoot(), |
| "mixed-out", |
| getOutputPath()); |
| } |
| |
| @Test |
| public void settingSymlinksReplacesSymlinksAlreadyPresent() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=replaced-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| Path binLink = getWorkspace().getChild("replaced-bin"); |
| Path genfilesLink = getWorkspace().getChild("replaced-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("replaced-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("replaced-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("replaced-out"); |
| |
| PathFragment original = getOutputPath().getRelative("original/destination").asFragment(); |
| binLink.createSymbolicLink(original); |
| genfilesLink.createSymbolicLink(original); |
| testlogsLink.createSymbolicLink(original); |
| workspaceLink.createSymbolicLink(original); |
| outLink.createSymbolicLink(original); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| // Implicitly test for symlink-ness; readSymbolicLink would throw if they are not symlinks. |
| assertThat(binLink.readSymbolicLink()).isNotEqualTo(original); |
| assertThat(genfilesLink.readSymbolicLink()).isNotEqualTo(original); |
| assertThat(testlogsLink.readSymbolicLink()).isNotEqualTo(original); |
| assertThat(workspaceLink.readSymbolicLink()).isNotEqualTo(original); |
| assertThat(outLink.readSymbolicLink()).isNotEqualTo(original); |
| } |
| |
| @Test |
| public void settingSymlinksCreatesSymlinksIfNotAlreadyPresent() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=created-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| assertThat(getWorkspace().getChild("created-bin").isSymbolicLink()).isTrue(); |
| assertThat(getWorkspace().getChild("created-genfiles").isSymbolicLink()).isTrue(); |
| assertThat(getWorkspace().getChild("created-testlogs").isSymbolicLink()).isTrue(); |
| assertThat(getWorkspace().getChild("created-" + TestConstants.WORKSPACE_NAME).isSymbolicLink()) |
| .isTrue(); |
| assertThat(getWorkspace().getChild("created-out").isSymbolicLink()).isTrue(); |
| } |
| |
| @Test |
| public void genfilesLink_omittedWithIncompatibleFlag() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=prefix-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_skip_genfiles_symlink=true"); |
| |
| // Simulate leftover symlink from prior build. |
| Path config = getOutputPath().getRelative("some-imaginary-config"); |
| Path genfilesLink = getWorkspace().getChild("prefix-genfiles"); |
| genfilesLink.createSymbolicLink(config.getChild("genfiles")); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| assertThat(getWorkspace().getChild("prefix-genfiles").isSymbolicLink()).isFalse(); |
| } |
| |
| @Test |
| public void genfilesLink_presentWithoutIncompatibleFlag() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=prefix-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| assertThat(getWorkspace().getChild("prefix-genfiles").isSymbolicLink()).isTrue(); |
| } |
| |
| @Test |
| public void settingSymlinksDoesNotReplaceNormalFilesAlreadyPresent() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=blocked-", |
| "--compilation_mode=fastbuild"); |
| |
| Path binLink = getWorkspace().getChild("blocked-bin"); |
| Path genfilesLink = getWorkspace().getChild("blocked-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("blocked-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("blocked-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("blocked-out"); |
| |
| FileSystemUtils.writeIsoLatin1(binLink, "this file is not a symlink"); |
| FileSystemUtils.writeIsoLatin1(genfilesLink, "this file is not a symlink"); |
| FileSystemUtils.writeIsoLatin1(testlogsLink, "this file is not a symlink"); |
| FileSystemUtils.writeIsoLatin1(workspaceLink, "this file is not a symlink"); |
| FileSystemUtils.writeIsoLatin1(outLink, "this file is not a symlink"); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| assertThat(binLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(genfilesLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(testlogsLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(workspaceLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(outLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| } |
| |
| @Test |
| public void settingSymlinksDoesNotReplaceDirectoriesAlreadyPresent() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=blocked-", |
| "--compilation_mode=fastbuild"); |
| |
| Path binLink = getWorkspace().getChild("blocked-bin"); |
| Path genfilesLink = getWorkspace().getChild("blocked-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("blocked-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("blocked-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("blocked-out"); |
| |
| binLink.createDirectory(); |
| genfilesLink.createDirectory(); |
| testlogsLink.createDirectory(); |
| workspaceLink.createDirectory(); |
| outLink.createDirectory(); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| assertThat(binLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(genfilesLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(testlogsLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(workspaceLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(outLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| } |
| |
| @Test |
| public void settingSymlinksReplacesSymlinksEvenIfNotPointingInsideExecRoot() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=replaced-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_merge_genfiles_directory=false", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| Path binLink = getWorkspace().getChild("replaced-bin"); |
| Path genfilesLink = getWorkspace().getChild("replaced-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("replaced-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("replaced-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("replaced-out"); |
| |
| Path original = getWorkspace().getRelative("/arbitrary/somewhere/else/in/the/filesystem"); |
| binLink.createSymbolicLink(original); |
| genfilesLink.createSymbolicLink(original); |
| testlogsLink.createSymbolicLink(original); |
| workspaceLink.createSymbolicLink(original); |
| outLink.createSymbolicLink(original); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| // Implicitly test for symlink-ness; readSymbolicLink would throw if they are not symlinks. |
| assertThat(binLink.readSymbolicLink()) |
| .isEqualTo( |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/bin") |
| .asFragment()); |
| assertThat(genfilesLink.readSymbolicLink()) |
| .isEqualTo( |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/genfiles") |
| .asFragment()); |
| assertThat(testlogsLink.readSymbolicLink()) |
| .isEqualTo( |
| getOutputPath() |
| .getRelative(getTargetConfiguration().getCpu() + "-fastbuild-from_flag/testlogs") |
| .asFragment()); |
| assertThat(workspaceLink.readSymbolicLink()).isEqualTo(getExecRoot().asFragment()); |
| assertThat(outLink.readSymbolicLink()).isEqualTo(getOutputPath().asFragment()); |
| } |
| |
| @Test |
| public void settingSymlinksCreatesDirectoriesIfNeeded() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=created/", |
| "--compilation_mode=fastbuild", |
| "--incompatible_skip_genfiles_symlink=false"); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| assertThat(getWorkspace().getChild("created").isDirectory()).isTrue(); |
| assertThat(getWorkspace().getRelative("created/bin").isSymbolicLink()).isTrue(); |
| assertThat(getWorkspace().getRelative("created/genfiles").isSymbolicLink()).isTrue(); |
| assertThat(getWorkspace().getRelative("created/testlogs").isSymbolicLink()).isTrue(); |
| assertThat( |
| getWorkspace().getRelative("created/" + TestConstants.WORKSPACE_NAME).isSymbolicLink()) |
| .isTrue(); |
| assertThat(getWorkspace().getRelative("created/out").isSymbolicLink()).isTrue(); |
| } |
| |
| @Test |
| public void settingSymlinksDoesNothingWhenParentExistsAndIsNotADirectory() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=blocked/", |
| "--compilation_mode=fastbuild"); |
| |
| Path parentDir = getWorkspace().getChild("blocked"); |
| FileSystemUtils.writeIsoLatin1(parentDir, "this file is not a directory"); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| assertThat(getWorkspace().getChild("blocked").isFile(Symlinks.NOFOLLOW)).isTrue(); |
| } |
| |
| @Test |
| public void settingSymlinksUsesExistingOrPopulatedParentDirectoryAsNormal() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=cooperating/", |
| "--compilation_mode=fastbuild", |
| "--incompatible_skip_genfiles_symlink=false"); |
| write("target/BUILD", "basic_rule(name='target')"); |
| write("cooperating/original", "this file makes the directory come to life"); |
| buildTarget("//target:target"); |
| |
| assertThat(getWorkspace().getChild("cooperating").isDirectory()).isTrue(); |
| assertThat(getWorkspace().getRelative("cooperating/bin").isSymbolicLink()).isTrue(); |
| assertThat(getWorkspace().getRelative("cooperating/genfiles").isSymbolicLink()).isTrue(); |
| assertThat(getWorkspace().getRelative("cooperating/testlogs").isSymbolicLink()).isTrue(); |
| assertThat( |
| getWorkspace() |
| .getRelative("cooperating/" + TestConstants.WORKSPACE_NAME) |
| .isSymbolicLink()) |
| .isTrue(); |
| assertThat(getWorkspace().getRelative("cooperating/out").isSymbolicLink()).isTrue(); |
| assertThat(getWorkspace().getRelative("cooperating/original").isFile()).isTrue(); |
| } |
| |
| @Test |
| public void settingSymlinksIgnoresSymlinksWithDifferentPrefix() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=new-prefix-", |
| "--compilation_mode=fastbuild"); |
| Path binLink = getWorkspace().getChild("other-prefix-bin"); |
| Path genfilesLink = getWorkspace().getChild("other-prefix-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("other-prefix-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("other-prefix-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("other-prefix-out"); |
| |
| PathFragment original = getOutputPath().getRelative("original/destination").asFragment(); |
| binLink.createSymbolicLink(original); |
| genfilesLink.createSymbolicLink(original); |
| testlogsLink.createSymbolicLink(original); |
| workspaceLink.createSymbolicLink(original); |
| outLink.createSymbolicLink(original); |
| |
| write("target/BUILD", "basic_rule(name='target')"); |
| buildTarget("//target:target"); |
| |
| // Implicitly test for symlink-ness; readSymbolicLink would throw if they are not symlinks. |
| assertThat(binLink.readSymbolicLink()).isEqualTo(original); |
| assertThat(genfilesLink.readSymbolicLink()).isEqualTo(original); |
| assertThat(testlogsLink.readSymbolicLink()).isEqualTo(original); |
| assertThat(workspaceLink.readSymbolicLink()).isEqualTo(original); |
| assertThat(outLink.readSymbolicLink()).isEqualTo(original); |
| } |
| |
| @Test |
| public void unsettingSymlinksRemovesConfigurationSymlinksIfAlreadyPresent() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=deleted-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_skip_genfiles_symlink=false"); |
| Path binLink = getWorkspace().getChild("deleted-bin"); |
| Path genfilesLink = getWorkspace().getChild("deleted-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("deleted-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("deleted-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("deleted-out"); |
| |
| Path config = getOutputPath().getRelative("some-imaginary-config"); |
| // put symlinks at the convenience symlinks spots to simulate a prior build |
| binLink.createSymbolicLink(config.getChild("bin")); |
| genfilesLink.createSymbolicLink(config.getChild("genfiles")); |
| testlogsLink.createSymbolicLink(config.getChild("testlogs")); |
| |
| write("file/BUILD", "exports_files(['file'])"); |
| write("file/file", "this is just a file to pretend to build"); |
| buildTarget("//file:file"); |
| |
| assertThat(binLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(genfilesLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(testlogsLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| |
| assertThat(workspaceLink.isSymbolicLink()).isTrue(); |
| assertThat(outLink.isSymbolicLink()).isTrue(); |
| } |
| |
| @Test |
| public void unsettingSymlinksSucceedsIfNotAlreadyPresent() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=already-absent-", |
| "--compilation_mode=fastbuild"); |
| Path binLink = getWorkspace().getChild("already-absent-bin"); |
| Path genfilesLink = getWorkspace().getChild("already-absent-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("already-absent-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("already-absent-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("already-absent-out"); |
| |
| write("file/BUILD", "exports_files(['file'])"); |
| write("file/file", "this is just a file to pretend to build"); |
| buildTarget("//file:file"); |
| |
| assertThat(binLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(genfilesLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(testlogsLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| |
| assertThat(workspaceLink.isSymbolicLink()).isTrue(); |
| assertThat(outLink.isSymbolicLink()).isTrue(); |
| } |
| |
| @Test |
| public void unsettingSymlinksDoesNotRemoveNormalFilesAlreadyPresent() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=blocked-", |
| "--compilation_mode=fastbuild"); |
| Path binLink = getWorkspace().getChild("blocked-bin"); |
| Path genfilesLink = getWorkspace().getChild("blocked-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("blocked-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("blocked-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("blocked-out"); |
| |
| FileSystemUtils.writeIsoLatin1(binLink, "this file is not a symlink"); |
| FileSystemUtils.writeIsoLatin1(genfilesLink, "this file is not a symlink"); |
| FileSystemUtils.writeIsoLatin1(testlogsLink, "this file is not a symlink"); |
| FileSystemUtils.writeIsoLatin1(workspaceLink, "this file is not a symlink"); |
| FileSystemUtils.writeIsoLatin1(outLink, "this file is not a symlink"); |
| |
| write("file/BUILD", "exports_files(['file'])"); |
| write("file/file", "this is just a file to pretend to build"); |
| buildTarget("//file:file"); |
| |
| assertThat(binLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(genfilesLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(testlogsLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(workspaceLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(outLink.isFile(Symlinks.NOFOLLOW)).isTrue(); |
| } |
| |
| @Test |
| public void unsettingSymlinksDoesNotRemoveDirectoriesAlreadyPresent() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=blocked-", |
| "--compilation_mode=fastbuild"); |
| Path binLink = getWorkspace().getChild("blocked-bin"); |
| Path genfilesLink = getWorkspace().getChild("blocked-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("blocked-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("blocked-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("blocked-out"); |
| |
| binLink.createDirectory(); |
| genfilesLink.createDirectory(); |
| testlogsLink.createDirectory(); |
| workspaceLink.createDirectory(); |
| outLink.createDirectory(); |
| |
| write("file/BUILD", "exports_files(['file'])"); |
| write("file/file", "this is just a file to pretend to build"); |
| buildTarget("//file:file"); |
| |
| assertThat(binLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(genfilesLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(testlogsLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(workspaceLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| assertThat(outLink.isDirectory(Symlinks.NOFOLLOW)).isTrue(); |
| } |
| |
| @Test |
| public void unsettingSymlinksRemovesSymlinksEvenIfNotPointingInsideExecRoot() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=deleted-", |
| "--compilation_mode=fastbuild", |
| "--incompatible_skip_genfiles_symlink=false"); |
| Path binLink = getWorkspace().getChild("deleted-bin"); |
| Path genfilesLink = getWorkspace().getChild("deleted-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("deleted-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("deleted-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("deleted-out"); |
| |
| Path original = getWorkspace().getRelative("/arbitrary/somewhere/else/in/the/filesystem"); |
| binLink.createSymbolicLink(original); |
| genfilesLink.createSymbolicLink(original); |
| testlogsLink.createSymbolicLink(original); |
| workspaceLink.createSymbolicLink(original); |
| outLink.createSymbolicLink(original); |
| |
| write("file/BUILD", "exports_files(['file'])"); |
| write("file/file", "this is just a file to pretend to build"); |
| buildTarget("//file:file"); |
| |
| assertThat(binLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(genfilesLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| assertThat(testlogsLink.exists(Symlinks.NOFOLLOW)).isFalse(); |
| |
| assertThat(workspaceLink.isSymbolicLink()).isTrue(); |
| assertThat(outLink.isSymbolicLink()).isTrue(); |
| } |
| |
| @Test |
| public void unsettingSymlinksIgnoresSymlinksWithDifferentPrefix() throws Exception { |
| addOptions( |
| "--output_directory_name=from_flag", |
| "--symlink_prefix=new-prefix-", |
| "--compilation_mode=fastbuild"); |
| Path binLink = getWorkspace().getChild("other-prefix-bin"); |
| Path genfilesLink = getWorkspace().getChild("other-prefix-genfiles"); |
| Path testlogsLink = getWorkspace().getChild("other-prefix-testlogs"); |
| Path workspaceLink = getWorkspace().getChild("other-prefix-" + TestConstants.WORKSPACE_NAME); |
| Path outLink = getWorkspace().getChild("other-prefix-out"); |
| |
| PathFragment original = getOutputPath().getRelative("original/destination").asFragment(); |
| binLink.createSymbolicLink(original); |
| genfilesLink.createSymbolicLink(original); |
| testlogsLink.createSymbolicLink(original); |
| workspaceLink.createSymbolicLink(original); |
| outLink.createSymbolicLink(original); |
| |
| write("file/BUILD", "exports_files(['file'])"); |
| write("file/file", "this is just a file to pretend to build"); |
| buildTarget("//file:file"); |
| |
| // Implicitly test for symlink-ness; readSymbolicLink would throw if they are not symlinks. |
| assertThat(binLink.readSymbolicLink()).isEqualTo(original); |
| assertThat(genfilesLink.readSymbolicLink()).isEqualTo(original); |
| assertThat(testlogsLink.readSymbolicLink()).isEqualTo(original); |
| assertThat(workspaceLink.readSymbolicLink()).isEqualTo(original); |
| assertThat(outLink.readSymbolicLink()).isEqualTo(original); |
| } |
| |
| @Test |
| public void symlinkPrefix_specialNoCreateValue_doesNotCreateOrDeleteSymlinks() throws Exception { |
| addOptions("--symlink_prefix=/"); |
| |
| write("foo/BUILD", "exports_files(['bar.txt'])"); |
| write("foo/bar.txt", "This is just a test file to pretend to build."); |
| |
| // This will be a preexisting symlink and when --symlink_prefix=/ is used, assert that this |
| // preexisting symlink still exists. |
| Path binLink = getWorkspace().getChild("blaze-" + TestConstants.WORKSPACE_NAME); |
| binLink.createSymbolicLink(PathFragment.create("foo/")); |
| |
| buildTarget("//foo:bar.txt"); |
| |
| ImmutableMap<String, Path> symlinks = getConvenienceSymlinks(); |
| assertThat(symlinks).containsKey("blaze-" + TestConstants.WORKSPACE_NAME); |
| } |
| |
| @Test |
| public void convenienceSymlinks_ignore_leaveSymlinksUntouched() throws Exception { |
| addOptions("--experimental_convenience_symlinks=ignore"); |
| |
| write("foo/BUILD", "exports_files(['bar.txt'])"); |
| write("foo/bar.txt", "This is just a test file to pretend to build."); |
| buildTarget("//foo:bar.txt"); |
| |
| // This will be a preexisting symlink that will remain after the build |
| Path binLink = getWorkspace().getChild("blaze-" + TestConstants.WORKSPACE_NAME); |
| binLink.createSymbolicLink(PathFragment.create("foo/")); |
| |
| ImmutableMap<String, Path> symlinks = getConvenienceSymlinks(); |
| assertThat(symlinks).containsKey("blaze-" + TestConstants.WORKSPACE_NAME); |
| } |
| |
| @Test |
| public void convenienceSymlinks_normal_createSymlinks() throws Exception { |
| addOptions("--symlink_prefix=test-", "--experimental_convenience_symlinks=normal"); |
| |
| write("foo/BUILD", "exports_files(['bar.txt'])"); |
| write("foo/bar.txt", "This is just a test file to pretend to build."); |
| buildTarget("//foo:bar.txt"); |
| |
| ImmutableMap<String, Path> symlinks = getConvenienceSymlinks(); |
| assertThat(symlinks).containsKey("test-" + TestConstants.WORKSPACE_NAME); |
| assertThat(symlinks).containsKey("test-out"); |
| } |
| |
| @Test |
| public void convenienceSymlinks_clean_deletesAndDoesNotCreateSymlinks() throws Exception { |
| addOptions("--symlink_prefix=test-", "--experimental_convenience_symlinks=clean"); |
| |
| write("foo/BUILD", "exports_files(['bar.txt'])"); |
| write("foo/bar.txt", "This is just a test file to pretend to build."); |
| |
| // This will be a preexisting symlink that will be deleted after the build |
| Path binLink = getWorkspace().getChild("test-" + TestConstants.WORKSPACE_NAME); |
| binLink.createSymbolicLink(PathFragment.create("foo")); |
| |
| buildTarget("//foo:bar.txt"); |
| |
| ImmutableMap<String, Path> symlinks = getConvenienceSymlinks(); |
| assertThat(symlinks).doesNotContainKey("test-" + TestConstants.WORKSPACE_NAME); |
| assertThat(symlinks).doesNotContainKey("test-out"); |
| } |
| } |