Skip to content

Commit 88c426e

Browse files
c-parsonscopybara-github
authored andcommitted
Flag for writable outputs (experimental)
This feature is tied to an experimental flag `--experimental_writable_outputs`. When enabled, Bazel will set the permissions of all output files to 0755 instead of 0555. RELNOTES: None. PiperOrigin-RevId: 500786227 Change-Id: I59e15f3fec09c40a052a60b00da209547f10d7fc
1 parent 5dab9d8 commit 88c426e

21 files changed

+622
-113
lines changed

src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java

+15-6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.google.devtools.build.lib.events.EventKind;
4242
import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
4343
import com.google.devtools.build.lib.skyframe.TreeArtifactValue.ArchivedRepresentation;
44+
import com.google.devtools.build.lib.vfs.OutputPermissions;
4445
import com.google.devtools.build.lib.vfs.Path;
4546
import com.google.devtools.build.lib.vfs.PathFragment;
4647
import java.io.FileNotFoundException;
@@ -299,9 +300,10 @@ private static Map<String, String> computeUsedEnv(
299300
Map<String, String> usedClientEnv = computeUsedClientEnv(action, clientEnv);
300301
Map<String, String> usedExecProperties =
301302
computeUsedExecProperties(action, remoteDefaultPlatformProperties);
302-
// Combining the Client environment with the Remote Default Execution Properties, because
303-
// the Miss Reason is not used currently by Bazel, therefore there is no need to distinguish
304-
// between these two cases. This also saves memory used for the Action Cache.
303+
// Combining the Client environment with the Remote Default Execution Properties and Output
304+
// Permissions, because the Miss Reason is not used currently by Bazel, therefore there is no
305+
// need to distinguish between these property types. This also saves memory used for the Action
306+
// Cache.
305307
Map<String, String> usedEnvironment = new HashMap<>();
306308
usedEnvironment.putAll(usedClientEnv);
307309
usedEnvironment.putAll(usedExecProperties);
@@ -427,6 +429,7 @@ public Token getTokenIfNeedToExecute(
427429
Action action,
428430
List<Artifact> resolvedCacheArtifacts,
429431
Map<String, String> clientEnv,
432+
OutputPermissions outputPermissions,
430433
EventHandler handler,
431434
MetadataHandler metadataHandler,
432435
ArtifactExpander artifactExpander,
@@ -489,6 +492,7 @@ public Token getTokenIfNeedToExecute(
489492
artifactExpander,
490493
actionInputs,
491494
clientEnv,
495+
outputPermissions,
492496
remoteDefaultPlatformProperties,
493497
cachedOutputMetadata)) {
494498
if (entry != null) {
@@ -518,6 +522,7 @@ private boolean mustExecute(
518522
ArtifactExpander artifactExpander,
519523
NestedSet<Artifact> actionInputs,
520524
Map<String, String> clientEnv,
525+
OutputPermissions outputPermissions,
521526
Map<String, String> remoteDefaultPlatformProperties,
522527
@Nullable CachedOutputMetadata cachedOutputMetadata)
523528
throws InterruptedException {
@@ -550,7 +555,7 @@ private boolean mustExecute(
550555
}
551556
Map<String, String> usedEnvironment =
552557
computeUsedEnv(action, clientEnv, remoteDefaultPlatformProperties);
553-
if (!entry.usedSameClientEnv(usedEnvironment)) {
558+
if (!entry.sameActionProperties(usedEnvironment, outputPermissions)) {
554559
reportClientEnv(handler, action, usedEnvironment);
555560
actionCache.accountMiss(MissReason.DIFFERENT_ENVIRONMENT);
556561
return true;
@@ -588,6 +593,7 @@ public void updateActionCache(
588593
MetadataHandler metadataHandler,
589594
ArtifactExpander artifactExpander,
590595
Map<String, String> clientEnv,
596+
OutputPermissions outputPermissions,
591597
Map<String, String> remoteDefaultPlatformProperties)
592598
throws IOException, InterruptedException {
593599
checkState(cacheConfig.enabled(), "cache unexpectedly disabled, action: %s", action);
@@ -603,7 +609,8 @@ public void updateActionCache(
603609
new ActionCache.Entry(
604610
action.getKey(actionKeyContext, artifactExpander),
605611
usedEnvironment,
606-
action.discoversInputs());
612+
action.discoversInputs(),
613+
outputPermissions);
607614
for (Artifact output : action.getOutputs()) {
608615
// Remove old records from the cache if they used different key.
609616
String execPath = output.getExecPathString();
@@ -754,7 +761,7 @@ private void checkMiddlemanAction(
754761
// Compute the aggregated middleman digest.
755762
// Since we never validate action key for middlemen, we should not store
756763
// it in the cache entry and just use empty string instead.
757-
entry = new ActionCache.Entry("", ImmutableMap.of(), false);
764+
entry = new ActionCache.Entry("", ImmutableMap.of(), false, OutputPermissions.READONLY);
758765
for (Artifact input : action.getInputs().toList()) {
759766
entry.addInputFile(
760767
input.getExecPath(), getMetadataMaybe(metadataHandler, input), /*saveExecPath=*/ true);
@@ -776,6 +783,7 @@ public Token getTokenUnconditionallyAfterFailureToRecordActionCacheHit(
776783
Action action,
777784
List<Artifact> resolvedCacheArtifacts,
778785
Map<String, String> clientEnv,
786+
OutputPermissions outputPermissions,
779787
EventHandler handler,
780788
MetadataHandler metadataHandler,
781789
ArtifactExpander artifactExpander,
@@ -789,6 +797,7 @@ public Token getTokenUnconditionallyAfterFailureToRecordActionCacheHit(
789797
action,
790798
resolvedCacheArtifacts,
791799
clientEnv,
800+
outputPermissions,
792801
handler,
793802
metadataHandler,
794803
artifactExpander,

src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java

+33-17
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
3434
import com.google.devtools.build.lib.util.Fingerprint;
3535
import com.google.devtools.build.lib.vfs.DigestUtils;
36+
import com.google.devtools.build.lib.vfs.OutputPermissions;
3637
import com.google.devtools.build.lib.vfs.PathFragment;
3738
import java.io.IOException;
3839
import java.io.PrintStream;
@@ -84,9 +85,11 @@ public interface ActionCache {
8485
* will continue to return same result regardless of internal data transformations).
8586
*/
8687
final class Entry {
88+
private static final byte[] EMPTY_CLIENT_ENV_DIGEST = new byte[0];
89+
8790
/** Unique instance to represent a corrupted cache entry. */
8891
public static final ActionCache.Entry CORRUPTED =
89-
new ActionCache.Entry(null, ImmutableMap.of(), false);
92+
new ActionCache.Entry(null, ImmutableMap.of(), false, OutputPermissions.READONLY);
9093

9194
private final String actionKey;
9295
@Nullable
@@ -95,7 +98,7 @@ final class Entry {
9598
// If null, digest is non-null and the entry is immutable.
9699
private Map<String, FileArtifactValue> mdMap;
97100
private byte[] digest;
98-
private final byte[] usedClientEnvDigest;
101+
private final byte[] actionPropertiesDigest;
99102
private final Map<String, RemoteFileArtifactValue> outputFileMetadata;
100103
private final Map<String, SerializableTreeArtifactValue> outputTreeMetadata;
101104

@@ -160,9 +163,13 @@ public static Optional<SerializableTreeArtifactValue> createSerializable(
160163
public abstract Optional<PathFragment> materializationExecPath();
161164
}
162165

163-
public Entry(String key, Map<String, String> usedClientEnv, boolean discoversInputs) {
166+
public Entry(
167+
String key,
168+
Map<String, String> usedClientEnv,
169+
boolean discoversInputs,
170+
OutputPermissions outputPermissions) {
164171
actionKey = key;
165-
this.usedClientEnvDigest = digestClientEnv(usedClientEnv);
172+
this.actionPropertiesDigest = digestActionProperties(usedClientEnv, outputPermissions);
166173
files = discoversInputs ? new ArrayList<>() : null;
167174
mdMap = new HashMap<>();
168175
outputFileMetadata = new HashMap<>();
@@ -171,39 +178,46 @@ public Entry(String key, Map<String, String> usedClientEnv, boolean discoversInp
171178

172179
public Entry(
173180
String key,
174-
byte[] usedClientEnvDigest,
181+
byte[] actionPropertiesDigest,
175182
@Nullable List<String> files,
176183
byte[] digest,
177184
Map<String, RemoteFileArtifactValue> outputFileMetadata,
178185
Map<String, SerializableTreeArtifactValue> outputTreeMetadata) {
179186
actionKey = key;
180-
this.usedClientEnvDigest = usedClientEnvDigest;
187+
this.actionPropertiesDigest = actionPropertiesDigest;
181188
this.files = files;
182189
this.digest = digest;
183190
mdMap = null;
184191
this.outputFileMetadata = outputFileMetadata;
185192
this.outputTreeMetadata = outputTreeMetadata;
186193
}
187194

188-
private static final byte[] EMPTY_CLIENT_ENV_DIGEST = new byte[0];
189-
190195
/**
191-
* Computes an order-independent digest of a map of environment variables.
196+
* Computes an order-independent digest of action properties. This includes a map of client
197+
* environment variables and the non-default permissions for output artifacts of the action.
192198
*
193199
* <p>Note that as discussed in https://github.com/bazelbuild/bazel/issues/15660, using {@link
194200
* DigestUtils#xor} to achieve order-independence is questionable in case it is possible that
195201
* multiple string keys map to the same bytes when passed through {@link Fingerprint#addString}
196202
* (due to lossy conversion from UTF-16 to UTF-8). We could instead use a sorted map, however
197203
* changing the digest function would cause action cache misses across bazel versions.
198204
*/
199-
private static byte[] digestClientEnv(Map<String, String> clientEnv) {
205+
private static byte[] digestActionProperties(
206+
Map<String, String> clientEnv, OutputPermissions outputPermissions) {
200207
byte[] result = EMPTY_CLIENT_ENV_DIGEST;
201208
Fingerprint fp = new Fingerprint();
202209
for (Map.Entry<String, String> entry : clientEnv.entrySet()) {
203210
fp.addString(entry.getKey());
204211
fp.addString(entry.getValue());
205212
result = DigestUtils.xor(result, fp.digestAndReset());
206213
}
214+
// Add the permissions mode to the digest if it differs from the default.
215+
// This is a bit of a hack to save memory on entries which have the default permissions mode
216+
// and no client env.
217+
if (outputPermissions != OutputPermissions.READONLY) {
218+
fp.addInt(outputPermissions.getPermissionsMode());
219+
result = DigestUtils.xor(result, fp.digestAndReset());
220+
}
207221
return result;
208222
}
209223

@@ -288,14 +302,16 @@ public String getActionKey() {
288302
return actionKey;
289303
}
290304

291-
/** @return the effectively used client environment */
292-
public byte[] getUsedClientEnvDigest() {
293-
return usedClientEnvDigest;
305+
/** Returns the effectively used client environment. */
306+
public byte[] getActionPropertiesDigest() {
307+
return actionPropertiesDigest;
294308
}
295309

296-
/** Determines whether this entry used the same client environment as the one given. */
297-
public boolean usedSameClientEnv(Map<String, String> clientEnv) {
298-
return Arrays.equals(digestClientEnv(clientEnv), usedClientEnvDigest);
310+
/** Determines whether this entry has the same action properties as the one given. */
311+
public boolean sameActionProperties(
312+
Map<String, String> clientEnv, OutputPermissions outputPermissions) {
313+
return Arrays.equals(
314+
digestActionProperties(clientEnv, outputPermissions), actionPropertiesDigest);
299315
}
300316

301317
/**
@@ -343,7 +359,7 @@ public String toString() {
343359
builder.append(" actionKey = ").append(actionKey).append("\n");
344360
builder
345361
.append(" usedClientEnvKey = ")
346-
.append(formatDigest(usedClientEnvDigest))
362+
.append(formatDigest(actionPropertiesDigest))
347363
.append("\n");
348364
builder.append(" digestKey = ");
349365
if (digest == null) {

src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ private static byte[] encode(StringIndexer indexer, ActionCache.Entry entry) thr
571571
VarInt.putVarInt(indexer.getOrCreateIndex(file), sink);
572572
}
573573

574-
MetadataDigestUtils.write(entry.getUsedClientEnvDigest(), sink);
574+
MetadataDigestUtils.write(entry.getActionPropertiesDigest(), sink);
575575

576576
VarInt.putVarInt(entry.getOutputFiles().size(), sink);
577577
for (Map.Entry<String, RemoteFileArtifactValue> file : entry.getOutputFiles().entrySet()) {

src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java

+12
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,17 @@ public class CoreOptions extends FragmentOptions implements Cloneable {
170170
+ "disabled.")
171171
public boolean strictFilesets;
172172

173+
// This option is only used during execution. However, it is a required input to the analysis
174+
// phase, as otherwise flipping this flag would not invalidate already-executed actions.
175+
@Option(
176+
name = "experimental_writable_outputs",
177+
defaultValue = "false",
178+
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
179+
effectTags = {OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION},
180+
metadataTags = {OptionMetadataTag.EXPERIMENTAL},
181+
help = "If true, the file permissions of action outputs are set to 0755 instead of 0555")
182+
public boolean experimentalWritableOutputs;
183+
173184
@Option(
174185
name = "experimental_strict_fileset_output",
175186
defaultValue = "false",
@@ -949,6 +960,7 @@ public FragmentOptions getExec() {
949960
exec.debugSelectsAlwaysSucceed = debugSelectsAlwaysSucceed;
950961
exec.checkTestonlyForOutputFiles = checkTestonlyForOutputFiles;
951962
exec.useAutoExecGroups = useAutoExecGroups;
963+
exec.experimentalWritableOutputs = experimentalWritableOutputs;
952964

953965
// === Runfiles ===
954966
exec.buildRunfilesManifests = buildRunfilesManifests;

src/main/java/com/google/devtools/build/lib/remote/AbstractActionInputPrefetcher.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.google.devtools.build.lib.remote.util.RxUtils.TransferResult;
4343
import com.google.devtools.build.lib.remote.util.TempPathGenerator;
4444
import com.google.devtools.build.lib.vfs.FileSystemUtils;
45+
import com.google.devtools.build.lib.vfs.OutputPermissions;
4546
import com.google.devtools.build.lib.vfs.Path;
4647
import com.google.devtools.build.lib.vfs.PathFragment;
4748
import io.reactivex.rxjava3.core.Completable;
@@ -66,6 +67,7 @@ public abstract class AbstractActionInputPrefetcher implements ActionInputPrefet
6667

6768
private final AsyncTaskCache.NoResult<Path> downloadCache = AsyncTaskCache.NoResult.create();
6869
private final TempPathGenerator tempPathGenerator;
70+
private final OutputPermissions outputPermissions;
6971
protected final Set<Artifact> outputsAreInputs = Sets.newConcurrentHashSet();
7072

7173
protected final Path execRoot;
@@ -112,10 +114,12 @@ protected enum Priority {
112114
protected AbstractActionInputPrefetcher(
113115
Path execRoot,
114116
TempPathGenerator tempPathGenerator,
115-
ImmutableList<Pattern> patternsToDownload) {
117+
ImmutableList<Pattern> patternsToDownload,
118+
OutputPermissions outputPermissions) {
116119
this.execRoot = execRoot;
117120
this.tempPathGenerator = tempPathGenerator;
118121
this.patternsToDownload = patternsToDownload;
122+
this.outputPermissions = outputPermissions;
119123
}
120124

121125
private boolean shouldDownloadFile(Path path, FileArtifactValue metadata) {
@@ -338,12 +342,12 @@ private Completable prefetchInputTree(
338342
}
339343

340344
for (Path dir : dirs) {
341-
// Change permission of all directories of a tree artifact to 0555 (files are
345+
// Change permission of all directories of a tree artifact (files are
342346
// changed inside {@code finalizeDownload}) in order to match the behaviour when
343347
// the tree artifact is generated locally. In that case, permission of all files
344-
// and directories inside a tree artifact is changed to 0555 within {@code
348+
// and directories inside a tree artifact is changed within {@code
345349
// checkOutputs()}.
346-
dir.chmod(0555);
350+
dir.chmod(outputPermissions.getPermissionsMode());
347351
}
348352

349353
completed.set(true);
@@ -490,9 +494,9 @@ private void finalizeDownload(Context context, Path tmpPath, Path path) throws I
490494
parentDir.setWritable(true);
491495
}
492496

493-
// The permission of output file is changed to 0555 after action execution. We manually change
497+
// The permission of output file is changed after action execution. We manually change
494498
// the permission here for the downloaded file to keep this behaviour consistent.
495-
tmpPath.chmod(0555);
499+
tmpPath.chmod(outputPermissions.getPermissionsMode());
496500
FileSystemUtils.moveFile(tmpPath, path);
497501
}
498502

src/main/java/com/google/devtools/build/lib/remote/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ java_library(
5959
"//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
6060
"//src/main/java/com/google/devtools/build/lib/analysis:blaze_directories",
6161
"//src/main/java/com/google/devtools/build/lib/analysis:config/build_options",
62+
"//src/main/java/com/google/devtools/build/lib/analysis:config/core_options",
6263
"//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
6364
"//src/main/java/com/google/devtools/build/lib/analysis/platform:platform_utils",
6465
"//src/main/java/com/google/devtools/build/lib/authandtls",

src/main/java/com/google/devtools/build/lib/remote/RemoteActionInputFetcher.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.google.devtools.build.lib.remote.util.TempPathGenerator;
3131
import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
3232
import com.google.devtools.build.lib.sandbox.SandboxHelpers;
33+
import com.google.devtools.build.lib.vfs.OutputPermissions;
3334
import com.google.devtools.build.lib.vfs.Path;
3435
import com.google.devtools.build.lib.vfs.PathFragment;
3536
import io.reactivex.rxjava3.core.Completable;
@@ -54,8 +55,9 @@ class RemoteActionInputFetcher extends AbstractActionInputPrefetcher {
5455
RemoteCache remoteCache,
5556
Path execRoot,
5657
TempPathGenerator tempPathGenerator,
57-
ImmutableList<Pattern> patternsToDownload) {
58-
super(execRoot, tempPathGenerator, patternsToDownload);
58+
ImmutableList<Pattern> patternsToDownload,
59+
OutputPermissions outputPermissions) {
60+
super(execRoot, tempPathGenerator, patternsToDownload, outputPermissions);
5961
this.buildRequestId = Preconditions.checkNotNull(buildRequestId);
6062
this.commandId = Preconditions.checkNotNull(commandId);
6163
this.remoteCache = Preconditions.checkNotNull(remoteCache);

src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.google.devtools.build.lib.analysis.BlazeDirectories;
4040
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
4141
import com.google.devtools.build.lib.analysis.config.BuildOptions;
42+
import com.google.devtools.build.lib.analysis.config.CoreOptions;
4243
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
4344
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
4445
import com.google.devtools.build.lib.authandtls.CallCredentialsProvider;
@@ -91,6 +92,7 @@
9192
import com.google.devtools.build.lib.util.io.AsynchronousFileOutputStream;
9293
import com.google.devtools.build.lib.vfs.DigestHashFunction;
9394
import com.google.devtools.build.lib.vfs.FileSystem;
95+
import com.google.devtools.build.lib.vfs.OutputPermissions;
9496
import com.google.devtools.build.lib.vfs.OutputService;
9597
import com.google.devtools.build.lib.vfs.Path;
9698
import com.google.devtools.common.options.OptionsBase;
@@ -933,6 +935,11 @@ public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorB
933935
RemoteOptions remoteOptions =
934936
Preconditions.checkNotNull(
935937
env.getOptions().getOptions(RemoteOptions.class), "RemoteOptions");
938+
CoreOptions coreOptions = env.getOptions().getOptions(CoreOptions.class);
939+
OutputPermissions outputPermissions =
940+
coreOptions.experimentalWritableOutputs
941+
? OutputPermissions.WRITABLE
942+
: OutputPermissions.READONLY;
936943
RemoteOutputsMode remoteOutputsMode = remoteOptions.remoteOutputsMode;
937944

938945
if (!remoteOutputsMode.downloadAllOutputs() && actionContextProvider.getRemoteCache() != null) {
@@ -944,7 +951,8 @@ public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorB
944951
actionContextProvider.getRemoteCache(),
945952
env.getExecRoot(),
946953
tempPathGenerator,
947-
patternsToDownload);
954+
patternsToDownload,
955+
outputPermissions);
948956
env.getEventBus().register(actionInputFetcher);
949957
builder.setActionInputPrefetcher(actionInputFetcher);
950958
remoteOutputService.setActionInputFetcher(actionInputFetcher);

src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java

+1
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,7 @@ private ActionExecutionValue checkCacheAndExecuteIfNeeded(
742742
state.inputArtifactData,
743743
action.discoversInputs(),
744744
skyframeActionExecutor.useArchivedTreeArtifacts(action),
745+
skyframeActionExecutor.getOutputPermissions(),
745746
action.getOutputs(),
746747
skyframeActionExecutor.getXattrProvider(),
747748
tsgm.get(),

0 commit comments

Comments
 (0)