// Copyright 2018 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.includescanning;

import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.includescanning.IncludeParser.Hints;
import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
import com.google.devtools.build.lib.skyframe.ContainingPackageLookupValue;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyKey;
import java.io.IOException;
import javax.annotation.Nullable;

/**
 * Creates a {@link IncludeParser.HintsRules} object. Done in Skyframe to track dependence on
 * INCLUDE_HINTS file.
 */
public class IncludeHintsFunction implements SkyFunction {
  // TODO(b/111722810): We don't re-run compiles if INCLUDE_HINTS file changes, because it is not
  // present in the action graph. This is an incremental compilation bug.
  @AutoCodec
  public static final SkyKey INCLUDE_HINTS_KEY =
      (SkyKey) () -> IncludeScanningSkyFunctions.INCLUDE_HINTS;

  private final PathFragment hintsFile;

  public IncludeHintsFunction(PathFragment hintsFile) {
    this.hintsFile = hintsFile;
  }

  @Nullable
  @Override
  public IncludeParser.HintsRules compute(SkyKey skyKey, Environment env)
      throws IncludeHintsFunctionException, InterruptedException {
    Root hintsPackageRoot;
    try {
      ContainingPackageLookupValue hintsLookupValue =
          (ContainingPackageLookupValue) env.getValueOrThrow(ContainingPackageLookupValue.key(
              PackageIdentifier.createInMainRepo(hintsFile.getParentDirectory())),
              IOException.class, BuildFileNotFoundException.class);
      if (env.valuesMissing()) {
        return null;
      }
      Preconditions.checkState(hintsLookupValue.hasContainingPackage(), "%s %s",
          hintsFile, hintsLookupValue);
      hintsPackageRoot = hintsLookupValue.getContainingPackageRoot();
      env.getValueOrThrow(FileValue.key(RootedPath.toRootedPath(hintsPackageRoot, hintsFile)),
          IOException.class);
    } catch (IOException | BuildFileNotFoundException e) {
      throw new IncludeHintsFunctionException(new EnvironmentalExecException(
          "could not read INCLUDE_HINTS file", e));
    }
    if (env.valuesMissing()) {
      return null;
    }
    try {
      return Hints.getRules(hintsPackageRoot.getRelative(hintsFile));
    } catch (IOException e) {
      throw new IncludeHintsFunctionException(new EnvironmentalExecException(
          "could not read INCLUDE_HINTS file", e));
    }
  }

  @Nullable
  @Override
  public String extractTag(SkyKey skyKey) {
    return null;
  }

  /**
   * Used to declare the exception type that can be wrapped in the exception thrown by
   * {@link IncludeHintsFunction#compute}.
   */
  private static final class IncludeHintsFunctionException extends SkyFunctionException {
    IncludeHintsFunctionException(EnvironmentalExecException e) {
      super(e, Transience.PERSISTENT);
    }
  }
}

@@ -668,53 +668,8 @@
 g;%0A%0A
-import com.google.common.base.Preconditions;%0A
 impo
@@ -729,32 +729,32 @@
 lExecException;%0A
+
 import com.googl
@@ -1761,154 +1761,173 @@
 0): 
-We don't re-run compiles if INCLUDE_HINTS file changes, because it is not%0A  // present in the action graph. This is an incremental compilation bug
+the action cache is not sensitive to changes in the INCLUDE_HINTS file, so%0A  //  even though Skyframe handles changes, we may still not re-execute an affected action
 .%0A  
@@ -2759,33 +2759,102 @@
     
-Preconditions.checkState(
+if (!hintsLookupValue.hasContainingPackage()) %7B%0A        String reasonForNoContainingPackage = 
 hint
@@ -2858,35 +2858,46 @@
 intsLookupValue.
-has
+getReasonForNo
 ContainingPackag
@@ -2903,19 +2903,145 @@
 ge()
-, %22%25s %25s%22,%0A
+;%0A        throw new IncludeHintsFunctionException(%0A            new EnvironmentalExecException(%0A                %22INCLUDE_HINTS file %22%0A    
     
@@ -3041,24 +3041,32 @@
             
+       +
  hintsFile, 
@@ -3067,28 +3067,217 @@
 File
-, hintsLookupValue);
+%0A                    + %22 was not in a package%22%0A                    + (reasonForNoContainingPackage != null%0A                        ? %22: %22 + reasonForNoContainingPackage%0A                        : %22%22)));%0A      %7D
 %0A   

Fix include scanning crash when no containing package is found.