// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.skyframe; import static com.google.devtools.build.lib.skyframe.SkyFunctions.DIRECTORY_LISTING_STATE; import static com.google.devtools.build.lib.skyframe.SkyFunctions.FILE_STATE; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.FileType; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.RootedPath; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.io.IOException; import java.util.EnumSet; import java.util.Set; import javax.annotation.Nullable; /** Utilities for checking dirtiness of keys (mainly filesystem keys) in the graph. */ public class DirtinessCheckerUtils { private DirtinessCheckerUtils() {} static class FileDirtinessChecker extends SkyValueDirtinessChecker { @Override public boolean applies(SkyKey skyKey) { return skyKey.functionName().equals(FILE_STATE); } @Override @Nullable public SkyValue createNewValue(SkyKey key, @Nullable TimestampGranularityMonitor tsgm) { RootedPath rootedPath = (RootedPath) key.argument(); try { return FileStateValue.create(rootedPath, tsgm); } catch (InconsistentFilesystemException | IOException e) { // TODO(bazel-team): An IOException indicates a failure to get a file digest or a symlink // target, not a missing file. Such a failure really shouldn't happen, so failing early // may be better here. return null; } } } static class DirectoryDirtinessChecker extends SkyValueDirtinessChecker { @Override public boolean applies(SkyKey skyKey) { return skyKey.functionName().equals(DIRECTORY_LISTING_STATE); } @Override @Nullable public SkyValue createNewValue(SkyKey key, @Nullable TimestampGranularityMonitor tsgm) { RootedPath rootedPath = (RootedPath) key.argument(); try { return DirectoryListingStateValue.create(rootedPath); } catch (IOException e) { return null; } } } static class BasicFilesystemDirtinessChecker extends SkyValueDirtinessChecker { private final FileDirtinessChecker fdc = new FileDirtinessChecker(); private final DirectoryDirtinessChecker ddc = new DirectoryDirtinessChecker(); private final UnionDirtinessChecker checker = new UnionDirtinessChecker(ImmutableList.of(fdc, ddc)); @Override public boolean applies(SkyKey skyKey) { return fdc.applies(skyKey) || ddc.applies(skyKey); } @Override @Nullable public SkyValue createNewValue(SkyKey key, @Nullable TimestampGranularityMonitor tsgm) { return checker.createNewValue(key, tsgm); } } static final class MissingDiffDirtinessChecker extends BasicFilesystemDirtinessChecker { private final Set<Path> missingDiffPackageRoots; MissingDiffDirtinessChecker(final Set<Path> missingDiffPackageRoots) { this.missingDiffPackageRoots = missingDiffPackageRoots; } @Override public boolean applies(SkyKey key) { return super.applies(key) && missingDiffPackageRoots.contains(((RootedPath) key.argument()).getRoot()); } } /** Checks files outside of the package roots for changes. */ static final class ExternalDirtinessChecker extends BasicFilesystemDirtinessChecker { private final ExternalFilesHelper externalFilesHelper; private final EnumSet<FileType> fileTypesToCheck; ExternalDirtinessChecker(ExternalFilesHelper externalFilesHelper, EnumSet<FileType> fileTypesToCheck) { this.externalFilesHelper = externalFilesHelper; this.fileTypesToCheck = fileTypesToCheck; } @Override public boolean applies(SkyKey key) { if (!super.applies(key)) { return false; } FileType fileType = externalFilesHelper.getAndNoteFileType((RootedPath) key.argument()); return fileTypesToCheck.contains(fileType); } @Nullable @Override public SkyValue createNewValue(SkyKey key, @Nullable TimestampGranularityMonitor tsgm) { throw new UnsupportedOperationException(); } @Override public SkyValueDirtinessChecker.DirtyResult check( SkyKey skyKey, SkyValue oldValue, @Nullable TimestampGranularityMonitor tsgm) { SkyValue newValue = super.createNewValue(skyKey, tsgm); if (Objects.equal(newValue, oldValue)) { return SkyValueDirtinessChecker.DirtyResult.notDirty(oldValue); } FileType fileType = externalFilesHelper.getAndNoteFileType((RootedPath) skyKey.argument()); if (fileType == FileType.EXTERNAL_REPO) { // Files under output_base/external have a dependency on the WORKSPACE file, so we don't add // a new SkyValue to the graph yet because it might change once the WORKSPACE file has been // parsed. return SkyValueDirtinessChecker.DirtyResult.dirty(oldValue); } return SkyValueDirtinessChecker.DirtyResult.dirtyWithNewValue(oldValue, newValue); } } /** {@link SkyValueDirtinessChecker} that encompasses a union of other dirtiness checkers. */ static final class UnionDirtinessChecker extends SkyValueDirtinessChecker { private final Iterable<SkyValueDirtinessChecker> dirtinessCheckers; UnionDirtinessChecker(Iterable<SkyValueDirtinessChecker> dirtinessCheckers) { this.dirtinessCheckers = dirtinessCheckers; } @Nullable private SkyValueDirtinessChecker getChecker(SkyKey key) { for (SkyValueDirtinessChecker dirtinessChecker : dirtinessCheckers) { if (dirtinessChecker.applies(key)) { return dirtinessChecker; } } return null; } @Override public boolean applies(SkyKey key) { return getChecker(key) != null; } @Override @Nullable public SkyValue createNewValue(SkyKey key, @Nullable TimestampGranularityMonitor tsgm) { return Preconditions.checkNotNull(getChecker(key), key).createNewValue(key, tsgm); } @Override public DirtyResult check(SkyKey key, @Nullable SkyValue oldValue, @Nullable TimestampGranularityMonitor tsgm) { return Preconditions.checkNotNull(getChecker(key), key).check(key, oldValue, tsgm); } } }