Skip to content

Commit de14ade

Browse files
ulfjackmeteorcloudy
authored andcommitted
Make --watchfs a common command option.
Adding an options parameter to DiffAwareness#getCurrentView seems like the simplest way to achieve that. Alternatives considered: 1. Making the diff awareness modules stateful. However, I did not want to do so as I've also been working on improving the module API to reduce state, or at least to have a proper lifecycle management for any necessary state. 2. Making the watchFs flag a constructor parameter. However, that would also invalidate any implementations that don't use the flag (of which we have several). 3. Only passing in a single boolean flag instead of an options class provider; however, this is a more principled, futureproof API, which allows other modules / awareness implementations to use their own options. RELNOTES: --watchfs is now a command option; the startup option of the same name is deprecated. I.e., use bazel build --watchfs, not blaze --watchfs build. -- MOS_MIGRATED_REVID=136154395
1 parent 8d0be89 commit de14ade

19 files changed

+283
-118
lines changed

src/main/java/com/google/devtools/build/lib/bazel/BazelDiffAwarenessModule.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@
1919
import com.google.devtools.build.lib.runtime.WorkspaceBuilder;
2020
import com.google.devtools.build.lib.skyframe.DiffAwareness;
2121
import com.google.devtools.build.lib.skyframe.LocalDiffAwareness;
22+
import com.google.devtools.common.options.OptionsBase;
2223

2324
/**
2425
* Provides the {@link DiffAwareness} implementation that uses the Java watch service.
2526
*/
2627
public class BazelDiffAwarenessModule extends BlazeModule {
2728
@Override
2829
public void workspaceInit(BlazeDirectories directories, WorkspaceBuilder builder) {
29-
if (builder.enableWatchFs()) {
30-
builder.addDiffAwarenessFactory(new LocalDiffAwareness.Factory(ImmutableList.<String>of()));
31-
}
30+
// Order here is important - LocalDiffAwareness creation always succeeds, so it must be last.
31+
builder.addDiffAwarenessFactory(new LocalDiffAwareness.Factory(ImmutableList.<String>of()));
32+
}
33+
34+
@Override
35+
public Iterable<Class<? extends OptionsBase>> getCommonCommandOptions() {
36+
return ImmutableList.<Class<? extends OptionsBase>>of(LocalDiffAwareness.Options.class);
3237
}
3338
}

src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@ public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
7171
}
7272

7373
try {
74-
env.setupPackageCache(
75-
options.getOptions(PackageCacheOptions.class),
76-
runtime.getDefaultsPackageContent());
74+
env.setupPackageCache(options, runtime.getDefaultsPackageContent());
7775
} catch (InterruptedException e) {
7876
env.getReporter().handle(Event.error("fetch interrupted"));
7977
return ExitCode.INTERRUPTED;

src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,7 @@ public void buildTargets(BuildRequest request, BuildResult result, TargetValidat
134134
validateOptions(request);
135135
BuildOptions buildOptions = runtime.createBuildOptions(request);
136136
// Sync the package manager before sending the BuildStartingEvent in runLoadingPhase()
137-
env.setupPackageCache(request.getPackageCacheOptions(),
138-
DefaultsPackage.getDefaultsPackageContent(buildOptions));
137+
env.setupPackageCache(request, DefaultsPackage.getDefaultsPackageContent(buildOptions));
139138

140139
ExecutionTool executionTool = null;
141140
LoadingResult loadingResult = null;

src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,7 @@ private BlazeRuntime(
189189
public void initWorkspace(BlazeDirectories directories, BinTools binTools)
190190
throws AbruptExitException {
191191
Preconditions.checkState(this.workspace == null);
192-
boolean watchFS = startupOptionsProvider != null
193-
&& startupOptionsProvider.getOptions(BlazeServerStartupOptions.class).watchFS;
194-
WorkspaceBuilder builder = new WorkspaceBuilder(directories, binTools, watchFS);
192+
WorkspaceBuilder builder = new WorkspaceBuilder(directories, binTools);
195193
for (BlazeModule module : blazeModules) {
196194
module.workspaceInit(directories, builder);
197195
}

src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import com.google.devtools.build.lib.vfs.Path;
5252
import com.google.devtools.build.lib.vfs.PathFragment;
5353
import com.google.devtools.common.options.OptionPriority;
54+
import com.google.devtools.common.options.OptionsClassProvider;
5455
import com.google.devtools.common.options.OptionsParser;
5556
import com.google.devtools.common.options.OptionsParsingException;
5657
import com.google.devtools.common.options.OptionsProvider;
@@ -470,23 +471,24 @@ public void throwPendingException() throws AbruptExitException {
470471
*
471472
* @see DefaultsPackage
472473
*/
473-
public void setupPackageCache(PackageCacheOptions packageCacheOptions,
474+
public void setupPackageCache(OptionsClassProvider options,
474475
String defaultsPackageContents) throws InterruptedException, AbruptExitException {
475476
SkyframeExecutor skyframeExecutor = getSkyframeExecutor();
476477
if (!skyframeExecutor.hasIncrementalState()) {
477478
skyframeExecutor.resetEvaluator();
478479
}
479480
skyframeExecutor.sync(
480481
reporter,
481-
packageCacheOptions,
482+
options.getOptions(PackageCacheOptions.class),
482483
getOutputBase(),
483484
getWorkingDirectory(),
484485
defaultsPackageContents,
485486
getCommandId(),
486487
// TODO(bazel-team): this optimization disallows rule-specified additional dependencies
487488
// on the client environment!
488489
getWhitelistedClientEnv(),
489-
timestampGranularityMonitor);
490+
timestampGranularityMonitor,
491+
options);
490492
}
491493

492494
public void recordLastExecutionTime() {
@@ -516,6 +518,15 @@ void beforeCommand(Command command, OptionsParser optionsParser,
516518
CommonCommandOptions options, long execStartTimeNanos, long waitTimeInMs)
517519
throws AbruptExitException {
518520
commandStartTime -= options.startupTime;
521+
if (runtime.getStartupOptionsProvider().getOptions(BlazeServerStartupOptions.class).watchFS) {
522+
try {
523+
// TODO(ulfjack): Get rid of the startup option and drop this code.
524+
optionsParser.parse("--watchfs");
525+
} catch (OptionsParsingException e) {
526+
// This should never happen.
527+
throw new IllegalStateException(e);
528+
}
529+
}
519530

520531
eventBus.post(new GotOptionsEvent(runtime.getStartupOptionsProvider(), optionsParser));
521532
throwPendingException();

src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java

+4-9
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import com.google.devtools.build.lib.vfs.PathFragment;
3737
import com.google.devtools.build.skyframe.SkyFunction;
3838
import com.google.devtools.build.skyframe.SkyFunctionName;
39-
4039
import java.util.Map;
4140

4241
/**
@@ -46,7 +45,6 @@
4645
public final class WorkspaceBuilder {
4746
private final BlazeDirectories directories;
4847
private final BinTools binTools;
49-
private final boolean watchFs;
5048

5149
private SkyframeExecutorFactory skyframeExecutorFactory;
5250
private WorkspaceStatusAction.Factory workspaceStatusActionFactory;
@@ -63,10 +61,9 @@ public final class WorkspaceBuilder {
6361
private final ImmutableList.Builder<SkyValueDirtinessChecker> customDirtinessCheckers =
6462
ImmutableList.builder();
6563

66-
WorkspaceBuilder(BlazeDirectories directories, BinTools binTools, boolean watchFs) {
64+
WorkspaceBuilder(BlazeDirectories directories, BinTools binTools) {
6765
this.directories = directories;
6866
this.binTools = binTools;
69-
this.watchFs = watchFs;
7067
}
7168

7269
BlazeWorkspace build(
@@ -104,10 +101,6 @@ BlazeWorkspace build(
104101
workspaceStatusActionFactory, binTools);
105102
}
106103

107-
public boolean enableWatchFs() {
108-
return watchFs;
109-
}
110-
111104
/**
112105
* Sets a factory for creating {@link SkyframeExecutor} objects. Note that only one factory per
113106
* workspace is allowed.
@@ -136,7 +129,9 @@ public WorkspaceBuilder setWorkspaceStatusActionFactory(
136129

137130
/**
138131
* Add a {@link DiffAwareness} factory. These will be used to determine which files, if any,
139-
* changed between Blaze commands.
132+
* changed between Blaze commands. Note that these factories are attempted in the order in which
133+
* they are added to this class, so order matters - in order to guarantee a specific order, only
134+
* a single module should add such factories.
140135
*/
141136
public WorkspaceBuilder addDiffAwarenessFactory(DiffAwareness.Factory factory) {
142137
this.diffAwarenessFactories.add(Preconditions.checkNotNull(factory));

src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
2121
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
2222
import com.google.devtools.build.lib.events.Event;
23-
import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
2423
import com.google.devtools.build.lib.runtime.BlazeCommand;
2524
import com.google.devtools.build.lib.runtime.BlazeRuntime;
2625
import com.google.devtools.build.lib.runtime.Command;
@@ -114,8 +113,7 @@ public BuildConfiguration get() {
114113
// package path. Since info inherits all the build options, all the necessary information
115114
// is available here.
116115
env.setupPackageCache(
117-
optionsProvider.getOptions(PackageCacheOptions.class),
118-
runtime.getDefaultsPackageContent(optionsProvider));
116+
optionsProvider, runtime.getDefaultsPackageContent(optionsProvider));
119117
// TODO(bazel-team): What if there are multiple configurations? [multi-config]
120118
configuration = env
121119
.getConfigurations(optionsProvider)

src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,7 @@ public ExitCode exec(CommandEnvironment env, OptionsProvider options) {
8383
QueryOptions queryOptions = options.getOptions(QueryOptions.class);
8484

8585
try {
86-
env.setupPackageCache(
87-
options.getOptions(PackageCacheOptions.class),
88-
runtime.getDefaultsPackageContent());
86+
env.setupPackageCache(options, runtime.getDefaultsPackageContent());
8987
} catch (InterruptedException e) {
9088
env.getReporter().handle(Event.error("query interrupted"));
9189
return ExitCode.INTERRUPTED;

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515

1616
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
1717
import com.google.devtools.build.lib.vfs.Path;
18-
18+
import com.google.devtools.common.options.OptionsClassProvider;
1919
import java.io.Closeable;
20-
2120
import javax.annotation.Nullable;
2221

2322
/**
@@ -55,7 +54,7 @@ interface View {
5554
* {@link DiffAwareness} instance. The {@link DiffAwareness} is expected to close itself in
5655
* this case.
5756
*/
58-
View getCurrentView() throws BrokenDiffAwarenessException;
57+
View getCurrentView(OptionsClassProvider options) throws BrokenDiffAwarenessException;
5958

6059
/**
6160
* Returns the set of files of interest that have been modified between the given two views.

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

+9-7
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,16 @@
1313
// limitations under the License.
1414
package com.google.devtools.build.lib.skyframe;
1515

16-
import com.google.common.collect.ImmutableSet;
16+
import com.google.common.collect.ImmutableList;
1717
import com.google.common.collect.Maps;
1818
import com.google.devtools.build.lib.events.Event;
1919
import com.google.devtools.build.lib.events.EventHandler;
2020
import com.google.devtools.build.lib.skyframe.DiffAwareness.View;
2121
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
2222
import com.google.devtools.build.lib.vfs.Path;
23-
23+
import com.google.devtools.common.options.OptionsClassProvider;
2424
import java.util.Map;
2525
import java.util.logging.Logger;
26-
2726
import javax.annotation.Nullable;
2827

2928
/**
@@ -34,11 +33,13 @@ public final class DiffAwarenessManager {
3433

3534
private static final Logger LOG = Logger.getLogger(DiffAwarenessManager.class.getName());
3635

37-
private final ImmutableSet<? extends DiffAwareness.Factory> diffAwarenessFactories;
36+
// The manager attempts to instantiate these in the order in which they are passed to the
37+
// constructor; this is critical in the case where a factory always succeeds.
38+
private final ImmutableList<? extends DiffAwareness.Factory> diffAwarenessFactories;
3839
private Map<Path, DiffAwarenessState> currentDiffAwarenessStates = Maps.newHashMap();
3940

4041
public DiffAwarenessManager(Iterable<? extends DiffAwareness.Factory> diffAwarenessFactories) {
41-
this.diffAwarenessFactories = ImmutableSet.copyOf(diffAwarenessFactories);
42+
this.diffAwarenessFactories = ImmutableList.copyOf(diffAwarenessFactories);
4243
}
4344

4445
private static class DiffAwarenessState {
@@ -79,15 +80,16 @@ public interface ProcessableModifiedFileSet {
7980
* Gets the set of changed files since the last call with this path entry, or
8081
* {@code ModifiedFileSet.EVERYTHING_MODIFIED} if this is the first such call.
8182
*/
82-
public ProcessableModifiedFileSet getDiff(EventHandler eventHandler, Path pathEntry) {
83+
public ProcessableModifiedFileSet getDiff(
84+
EventHandler eventHandler, Path pathEntry, OptionsClassProvider options) {
8385
DiffAwarenessState diffAwarenessState = maybeGetDiffAwarenessState(pathEntry);
8486
if (diffAwarenessState == null) {
8587
return BrokenProcessableModifiedFileSet.INSTANCE;
8688
}
8789
DiffAwareness diffAwareness = diffAwarenessState.diffAwareness;
8890
View newView;
8991
try {
90-
newView = diffAwareness.getCurrentView();
92+
newView = diffAwareness.getCurrentView(options);
9193
} catch (BrokenDiffAwarenessException e) {
9294
handleBrokenDiffAwareness(eventHandler, pathEntry, e);
9395
return BrokenProcessableModifiedFileSet.INSTANCE;

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

+39-18
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,20 @@
1414

1515
package com.google.devtools.build.lib.skyframe;
1616

17+
import com.google.common.annotations.VisibleForTesting;
1718
import com.google.common.base.Function;
1819
import com.google.common.collect.ImmutableList;
20+
import com.google.common.collect.ImmutableSet;
1921
import com.google.common.collect.Iterables;
2022
import com.google.devtools.build.lib.util.OS;
2123
import com.google.devtools.build.lib.util.Preconditions;
2224
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
2325
import com.google.devtools.build.lib.vfs.PathFragment;
24-
26+
import com.google.devtools.common.options.Option;
27+
import com.google.devtools.common.options.OptionsBase;
2528
import java.io.IOException;
2629
import java.nio.file.FileSystems;
2730
import java.nio.file.Path;
28-
import java.nio.file.WatchService;
2931
import java.util.Set;
3032

3133
/**
@@ -38,6 +40,19 @@
3840
* {@link WatchServiceDiffAwareness}.
3941
*/
4042
public abstract class LocalDiffAwareness implements DiffAwareness {
43+
/**
44+
* Option to enable / disable local diff awareness.
45+
*/
46+
public static final class Options extends OptionsBase {
47+
@Option(
48+
name = "watchfs",
49+
defaultValue = "false",
50+
category = "server startup",
51+
help = "If true, %{product} tries to use the operating system's file watch service for "
52+
+ "local changes instead of scanning every file for a change."
53+
)
54+
public boolean watchFS;
55+
}
4156

4257
/** Factory for creating {@link LocalDiffAwareness} instances. */
4358
public static class Factory implements DiffAwareness.Factory {
@@ -73,16 +88,26 @@ public DiffAwareness maybeCreate(com.google.devtools.build.lib.vfs.Path pathEntr
7388
return new MacOSXFsEventsDiffAwareness(resolvedPathEntryFragment.toString());
7489
}
7590

76-
WatchService watchService;
77-
try {
78-
watchService = FileSystems.getDefault().newWatchService();
79-
} catch (IOException e) {
80-
return null;
81-
}
82-
return new WatchServiceDiffAwareness(resolvedPathEntryFragment.toString(), watchService);
91+
return new WatchServiceDiffAwareness(resolvedPathEntryFragment.toString());
8392
}
8493
}
8594

95+
/**
96+
* A view that results in any subsequent getDiff calls returning
97+
* {@link ModifiedFileSet#EVERYTHING_MODIFIED}. Use this if --watchFs is disabled.
98+
*
99+
* <p>The position is set to -2 in order for {@link #areInSequence} below to always return false
100+
* if this view is passed to it. Any negative number would work; we don't use -1 as the other
101+
* view may have a position of 0.
102+
*/
103+
protected static final View EVERYTHING_MODIFIED =
104+
new SequentialView(/*owner=*/null, /*position=*/-2, ImmutableSet.<Path>of());
105+
106+
public static boolean areInSequence(SequentialView oldView, SequentialView newView) {
107+
// Keep this in sync with the EVERYTHING_MODIFIED View above.
108+
return oldView.owner == newView.owner && (oldView.position + 1) == newView.position;
109+
}
110+
86111
private int numGetCurrentViewCalls = 0;
87112

88113
/** Root directory to watch. This is an absolute path. */
@@ -96,7 +121,8 @@ protected LocalDiffAwareness(String watchRoot) {
96121
* The WatchService is inherently sequential and side-effectful, so we enforce this by only
97122
* supporting {@link #getDiff} calls that happen to be sequential.
98123
*/
99-
private static class SequentialView implements DiffAwareness.View {
124+
@VisibleForTesting
125+
static class SequentialView implements DiffAwareness.View {
100126
private final LocalDiffAwareness owner;
101127
private final int position;
102128
private final Set<Path> modifiedAbsolutePaths;
@@ -107,10 +133,6 @@ public SequentialView(LocalDiffAwareness owner, int position, Set<Path> modified
107133
this.modifiedAbsolutePaths = modifiedAbsolutePaths;
108134
}
109135

110-
public static boolean areInSequence(SequentialView oldView, SequentialView newView) {
111-
return oldView.owner == newView.owner && (oldView.position + 1) == newView.position;
112-
}
113-
114136
@Override
115137
public String toString() {
116138
return String.format("SequentialView[owner=%s, position=%d, modifiedAbsolutePaths=%s]", owner,
@@ -119,7 +141,7 @@ public String toString() {
119141
}
120142

121143
/**
122-
* Returns true on any call before first call to {@link #newView(Set<Path>)}.
144+
* Returns true on any call before first call to {@link #newView}.
123145
*/
124146
protected boolean isFirstCall() {
125147
return numGetCurrentViewCalls == 0;
@@ -129,8 +151,7 @@ protected boolean isFirstCall() {
129151
* Create a new views using a list of modified absolute paths. This will increase the view
130152
* counter.
131153
*/
132-
protected SequentialView newView(Set<Path> modifiedAbsolutePaths)
133-
throws BrokenDiffAwarenessException {
154+
protected SequentialView newView(Set<Path> modifiedAbsolutePaths) {
134155
numGetCurrentViewCalls++;
135156
return new SequentialView(this, numGetCurrentViewCalls, modifiedAbsolutePaths);
136157
}
@@ -146,7 +167,7 @@ public ModifiedFileSet getDiff(View oldView, View newView)
146167
} catch (ClassCastException e) {
147168
throw new IncompatibleViewException("Given views are not from LocalDiffAwareness");
148169
}
149-
if (!SequentialView.areInSequence(oldSequentialView, newSequentialView)) {
170+
if (!areInSequence(oldSequentialView, newSequentialView)) {
150171
return ModifiedFileSet.EVERYTHING_MODIFIED;
151172
}
152173
return ModifiedFileSet.builder()

0 commit comments

Comments
 (0)