Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
import com.google.devtools.build.lib.packages.StarlarkProviderWrapper;
import com.google.devtools.build.lib.packages.TriState;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.List;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Starlark;

/**
* Utility functions for use during analysis.
Expand Down Expand Up @@ -73,6 +76,44 @@ public static boolean isStampingEnabled(RuleContext ruleContext) {
return isStampingEnabled(ruleContext, ruleContext.getConfiguration());
}

/**
* Returns whether workspace status files ({@code ctx.info_file} / {@code ctx.version_file}) may
* be accessed for the given rule.
*/
public static boolean areWorkspaceStatusFilesAvailable(RuleContext ruleContext) {
BuildConfigurationValue config = ruleContext.getConfiguration();
if (config.isToolConfiguration()) {
return false;
}
if (ruleContext.attributes().has("stamp", BuildType.TRISTATE)
|| ruleContext.attributes().has("stamp", Type.INTEGER)) {
return isStampingEnabled(ruleContext, config);
}
return config.stampBinaries();
}

/**
* Verifies that workspace status files may be accessed, or fails with an error pointing at the
* calling rule implementation.
*/
public static void checkWorkspaceStatusFileAccess(RuleContext ruleContext, String apiName)
throws EvalException {
if (!ruleContext
.getAnalysisEnvironment()
.getStarlarkSemantics()
.getBool(BuildLanguageOptions.INCOMPATIBLE_PREVENT_STATUS_FILES_WITHOUT_STAMP)) {
return;
}
if (areWorkspaceStatusFilesAvailable(ruleContext)) {
return;
}
throw Starlark.errorf(
"%s cannot be accessed without stamping when"
+ " --incompatible_prevent_status_files_without_stamp is enabled. Enable stamping with"
+ " the stamp attribute or --stamp.",
apiName);
}

// TODO(bazel-team): These need Iterable<? extends TransitiveInfoCollection> because they need to
// be called with Iterable<ConfiguredTarget>. Once the configured target lockdown is complete, we
// can eliminate the "extends" clauses.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
import com.google.devtools.build.lib.actions.extra.SpawnInfo;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.BashCommandConstructor;
import com.google.devtools.build.lib.analysis.CommandHelper;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
Expand Down Expand Up @@ -477,6 +478,9 @@ private Artifact transformBuildInfoFile(
StarlarkThread thread)
throws InterruptedException, EvalException {
RuleContext ruleContext = getRuleContext();
String apiName =
isVolatile ? "ctx.actions.transform_version_file" : "ctx.actions.transform_info_file";
AnalysisUtils.checkWorkspaceStatusFileAccess(ruleContext, apiName);
Artifact templateFile = (Artifact) templateObject;
PathFragment fragment =
ruleContext.getPackageDirectory().getRelative(PathFragment.create(outputFileName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.analysis.ActionsProvider;
import com.google.devtools.build.lib.analysis.AliasProvider;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.AspectContext;
import com.google.devtools.build.lib.analysis.BashCommandConstructor;
import com.google.devtools.build.lib.analysis.CommandHelper;
Expand Down Expand Up @@ -980,12 +981,14 @@ public boolean areRunfilesFromDeps(FilesToRunProvider executable) {
@Override
public Artifact getStableWorkspaceStatus() throws InterruptedException, EvalException {
checkMutable("info_file");
AnalysisUtils.checkWorkspaceStatusFileAccess(ruleContext, "ctx.info_file");
return ruleContext.getAnalysisEnvironment().getStableWorkspaceStatusArtifact();
}

@Override
public Artifact getVolatileWorkspaceStatus() throws InterruptedException, EvalException {
checkMutable("version_file");
AnalysisUtils.checkWorkspaceStatusFileAccess(ruleContext, "ctx.version_file");
return ruleContext.getAnalysisEnvironment().getVolatileWorkspaceStatusArtifact();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,20 @@ public final class BuildLanguageOptions extends OptionsBase {
+ "from the top level target instead (No-op in Bazel)")
public boolean incompatibleDisableObjcLibraryTransition;

@Option(
name = "incompatible_prevent_status_files_without_stamp",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
help =
"If enabled, ctx.info_file and ctx.version_file (and the corresponding"
+ " ctx.actions.transform_*_file functions) are only available when stamping is"
+ " enabled for the target (via the stamp attribute or --stamp). Otherwise, rule"
+ " implementations that access these files fail during analysis. See"
+ " https://github.com/bazelbuild/bazel/issues/14341.")
public boolean incompatiblePreventStatusFilesWithoutStamp;

// remove after Bazel LTS in Nov 2023
@Option(
name = "incompatible_fail_on_unknown_attributes",
Expand Down Expand Up @@ -811,6 +825,20 @@ public final class BuildLanguageOptions extends OptionsBase {
+ " attributes of symbolic macros or attribute default values.")
public boolean incompatibleSimplifyUnconditionalSelectsInRuleAttrs;

@Option(
name = "incompatible_prevent_status_files_without_stamp",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
help =
"If enabled, ctx.info_file and ctx.version_file (and the corresponding"
+ " ctx.actions.transform_*_file functions) are only available when stamping is"
+ " enabled for the target (via the stamp attribute or --stamp). Otherwise, rule"
+ " implementations that access these files fail during analysis. See"
+ " https://github.com/bazelbuild/bazel/issues/14341.")
public abstract boolean getIncompatiblePreventStatusFilesWithoutStamp();

@Option(
name = "experimental_enable_starlark_set",
defaultValue = "true",
Expand Down Expand Up @@ -995,6 +1023,9 @@ private void setFlags(FlagConsumer consumer) {
.setBool(
INCOMPATIBLE_DISABLE_OBJC_LIBRARY_TRANSITION,
incompatibleDisableObjcLibraryTransition)
.setBool(
INCOMPATIBLE_PREVENT_STATUS_FILES_WITHOUT_STAMP,
incompatiblePreventStatusFilesWithoutStamp)
.setBool(INCOMPATIBLE_FAIL_ON_UNKNOWN_ATTRIBUTES, incompatibleFailOnUnknownAttributes)
.setBool(
INCOMPATIBLE_ENABLE_PROTO_TOOLCHAIN_RESOLUTION,
Expand Down Expand Up @@ -1144,6 +1175,9 @@ public FlagConsumer setBool(String key, boolean ignored) {
"+incompatible_depset_for_libraries_to_link_getter";
public static final String INCOMPATIBLE_DISABLE_TARGET_PROVIDER_FIELDS =
"-incompatible_disable_target_provider_fields";
public static final String INCOMPATIBLE_PREVENT_STATUS_FILES_WITHOUT_STAMP =
"-incompatible_prevent_status_files_without_stamp";

// Note that INCOMPATIBLE_DISALLOW_EMPTY_GLOB differs in Google and in OSS Bazel.
public static final String INCOMPATIBLE_DISALLOW_EMPTY_GLOB = "+incompatible_disallow_empty_glob";
public static final String INCOMPATIBLE_DISALLOW_STRUCT_PROVIDER_SYNTAX =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ private static BuildLanguageOptions buildRandomOptions(Random rand) throws Excep
"--incompatible_always_check_depset_elements=" + rand.nextBoolean(),
"--incompatible_depset_for_libraries_to_link_getter=" + rand.nextBoolean(),
"--incompatible_disable_target_provider_fields=" + rand.nextBoolean(),
"--incompatible_prevent_status_files_without_stamp=" + rand.nextBoolean(),
"--incompatible_disallow_empty_glob=" + rand.nextBoolean(),
"--incompatible_disallow_struct_provider_syntax=" + rand.nextBoolean(),
"--incompatible_do_not_split_linking_cmdline=" + rand.nextBoolean(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.google.devtools.build.lib.analysis.actions.BuildInfoFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.actions.StarlarkAction;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.configuredtargets.FileConfiguredTarget;
import com.google.devtools.build.lib.analysis.starlark.Args;
import com.google.devtools.build.lib.analysis.starlark.StarlarkExecGroupCollection;
Expand Down Expand Up @@ -4447,4 +4448,114 @@ public void transformFile_cannotBeAccessedOutsideOfAllowlist(
" template = ':template.txt',",
")");
}
}

@Test
public void infoFile_allowedWithoutStampByDefault() throws Exception {
scratch.file(
"test/rules.bzl",
"def _impl(ctx):",
" ctx.actions.write(ctx.outputs.out, ctx.info_file.path)",
" return DefaultInfo(files = depset([ctx.outputs.out]))",
"status_rule = rule(",
" implementation = _impl,",
" attrs = {\"stamp\": attr.int(default = -1)},",
" outputs = {\"out\": \"%{name}.txt\"},",
")",
testingRuleDefinition);
scratch.file(
"test/BUILD",
"load(':rules.bzl', 'status_rule')",
"status_rule(name = 'target')");

getConfiguredTarget("//test:target");
}

@Test
public void infoFile_errorsWithoutStampWhenPrevented() throws Exception {
setBuildLanguageOptions("--incompatible_prevent_status_files_without_stamp");
scratch.file(
"test/rules.bzl",
"def _impl(ctx):",
" ctx.actions.write(ctx.outputs.out, ctx.info_file.path)",
" return DefaultInfo(files = depset([ctx.outputs.out]))",
"status_rule = rule(",
" implementation = _impl,",
" attrs = {\"stamp\": attr.int(default = -1)},",
" outputs = {\"out\": \"%{name}.txt\"},",
")",
testingRuleDefinition);
scratch.file(
"test/BUILD",
"load(':rules.bzl', 'status_rule')",
"status_rule(name = 'target')");

checkError(
"//test:target",
"ctx.info_file cannot be accessed without stamping",
"incompatible_prevent_status_files_without_stamp");
}

@Test
public void infoFile_allowedWithStampWhenPrevented() throws Exception {
setBuildLanguageOptions("--incompatible_prevent_status_files_without_stamp", "--stamp");
scratch.file(
"test/rules.bzl",
"def _impl(ctx):",
" ctx.actions.write(ctx.outputs.out, ctx.info_file.path)",
" return DefaultInfo(files = depset([ctx.outputs.out]))",
"status_rule = rule(",
" implementation = _impl,",
" attrs = {\"stamp\": attr.int(default = 1)},",
" outputs = {\"out\": \"%{name}.txt\"},",
")",
testingRuleDefinition);
scratch.file(
"test/BUILD",
"load(':rules.bzl', 'status_rule')",
"status_rule(name = 'target')");

getConfiguredTarget("//test:target");
}

@Test
public void infoFile_allowedWhenStampEnabledByDependencyTransition() throws Exception {
setBuildLanguageOptions("--incompatible_prevent_status_files_without_stamp");
useConfiguration("--nostamp");
scratch.file(
"test/rules.bzl",
"def _stamp_transition_impl(settings, _attr):",
" return {'//command_line_option:stamp': not settings['//command_line_option:stamp']}",
"stamp_transition = transition(",
" implementation = _stamp_transition_impl,",
" inputs = ['//command_line_option:stamp'],",
" outputs = ['//command_line_option:stamp'],",
")",
"def _stamped_dep_impl(ctx):",
" ctx.actions.write(ctx.outputs.out, ctx.info_file.path)",
" return DefaultInfo(files = depset([ctx.outputs.out]))",
"stamped_dep = rule(",
" implementation = _stamped_dep_impl,",
" outputs = {\"out\": \"%{name}.txt\"},",
")",
"def _consumer_impl(ctx):",
" return DefaultInfo()",
"consumer = rule(",
" implementation = _consumer_impl,",
" attrs = {\"dep\": attr.label(cfg = stamp_transition)},",
")",
testingRuleDefinition);
scratch.file(
"test/BUILD",
"load(':rules.bzl', 'consumer', 'stamped_dep')",
"stamped_dep(name = 'dep')",
"consumer(name = 'top', dep = ':dep')");

ConfiguredTarget top = getConfiguredTarget("//test:top");
ConfiguredTarget dep = Iterables.getOnlyElement(getPrerequisites(top, "dep"));

assertThat(getConfiguration(top).getOptions().get(CoreOptions.class).stampBinaries)
.isFalse();
assertThat(getConfiguration(dep).getOptions().get(CoreOptions.class).stampBinaries)
.isTrue();
assertThat(getFilesToBuild(dep)).isNotEmpty();
}
36 changes: 36 additions & 0 deletions src/test/shell/bazel/bazel_workspace_status_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,40 @@ EOF

}

function test_prevent_status_files_without_stamp() {
create_new_workspace

cat > rules.bzl <<'EOF'
def _impl(ctx):
ctx.actions.write(ctx.outputs.out, ctx.info_file.path)
return DefaultInfo(files = depset([ctx.outputs.out]))

uses_status = rule(
implementation = _impl,
attrs = {"stamp": attr.int(default = -1)},
outputs = {"out": "%{name}.txt"},
)
EOF

cat > BUILD <<'EOF'
load(":rules.bzl", "uses_status")

uses_status(name = "unstamped")
uses_status(name = "stamped", stamp = 1)
EOF

# By default, status files are available without stamping.
bazel build //:unstamped &> $TEST_log || fail "expected build to succeed by default"

# With the incompatible flag enabled, unstamped targets fail and point at the rule implementation.
bazel build --incompatible_prevent_status_files_without_stamp //:unstamped &> $TEST_log \
&& fail "expected build to fail" || true
expect_log "ctx.info_file cannot be accessed without stamping"
expect_log "rules.bzl"

# Stamped targets succeed with the incompatible flag enabled.
bazel build --incompatible_prevent_status_files_without_stamp --stamp //:stamped \
&> $TEST_log || fail "expected stamped build to succeed"
}

run_suite "workspace status tests"
Loading