/**
   * Converts the PathFragment of the Skylark file to a Label using the BUILD file closest to the
   * Skylark file in its directory hierarchy - finds the package to which the Skylark file belongs.
   * Throws an exception if no such BUILD file exists.
   */
  private Label pathFragmentToLabel(RepositoryName repo, PathFragment file, Environment env)
      throws SkylarkImportLookupFunctionException {
    ContainingPackageLookupValue containingPackageLookupValue = null;
    try {
      PackageIdentifier newPkgId = PackageIdentifier.create(repo, file.getParentDirectory());
      containingPackageLookupValue =
          (ContainingPackageLookupValue)
              env.getValueOrThrow(
                  ContainingPackageLookupValue.key(newPkgId),
                  BuildFileNotFoundException.class,
                  InconsistentFilesystemException.class);
    } catch (BuildFileNotFoundException e) {
      // Thrown when there are IO errors looking for BUILD files.
      throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
    } catch (InconsistentFilesystemException e) {
      throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
    }

    if (containingPackageLookupValue == null) {
      return null;
    }

    if (!containingPackageLookupValue.hasContainingPackage()) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.noBuildFile(file));
    }

    PathFragment pkgName =
        containingPackageLookupValue.getContainingPackageName().getPackageFragment();
    PathFragment fileInPkg = file.relativeTo(pkgName);

    try {
      // This code relies on PackageIdentifier.RepositoryName.toString()
      return Label.parseAbsolute(repo + "//" + pkgName.getPathString() + ":" + fileInPkg);
    } catch (LabelSyntaxException e) {
      throw new IllegalStateException(e);
    }
  }
  /** Creates the Extension to be imported. */
  private Extension createExtension(
      BuildFileAST ast, PathFragment file, Map<PathFragment, Extension> importMap, Environment env)
      throws InterruptedException, SkylarkImportLookupFunctionException {
    StoredEventHandler eventHandler = new StoredEventHandler();
    // TODO(bazel-team): this method overestimates the changes which can affect the
    // Skylark RuleClass. For example changes to comments or unused functions can modify the hash.
    // A more accurate - however much more complicated - way would be to calculate a hash based on
    // the transitive closure of the accessible AST nodes.
    try (Mutability mutability = Mutability.create("importing %s", file)) {
      com.google.devtools.build.lib.syntax.Environment extensionEnv =
          ruleClassProvider
              .createSkylarkRuleClassEnvironment(
                  mutability, eventHandler, ast.getContentHashCode(), importMap)
              .setupOverride("native", packageFactory.getNativeModule());
      ast.exec(extensionEnv, eventHandler);
      SkylarkRuleClassFunctions.exportRuleFunctions(extensionEnv, file);

      Event.replayEventsOn(env.getListener(), eventHandler.getEvents());
      if (eventHandler.hasErrors()) {
        throw new SkylarkImportLookupFunctionException(SkylarkImportFailedException.errors(file));
      }
      return new Extension(extensionEnv);
    }
  }
  SkyValue computeInternal(
      SkyKey skyKey, Environment env, @Nullable Set<SkyKey> visitedKeysForCycle)
      throws SkyFunctionException, InterruptedException {
    PackageIdentifier arg = (PackageIdentifier) skyKey.argument();
    PathFragment file = arg.getPackageFragment();
    ASTFileLookupValue astLookupValue = null;
    try {
      SkyKey astLookupKey = ASTFileLookupValue.key(arg);
      astLookupValue =
          (ASTFileLookupValue)
              env.getValueOrThrow(
                  astLookupKey,
                  ErrorReadingSkylarkExtensionException.class,
                  InconsistentFilesystemException.class);
    } catch (ErrorReadingSkylarkExtensionException e) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.errorReadingFile(file, e.getMessage()));
    } catch (InconsistentFilesystemException e) {
      throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
    }
    if (astLookupValue == null) {
      return null;
    }
    if (astLookupValue.getAST() == null) {
      // Skylark import files have to exist.
      throw new SkylarkImportLookupFunctionException(SkylarkImportFailedException.noFile(file));
    }

    BuildFileAST ast = astLookupValue.getAST();
    if (ast.containsErrors()) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.skylarkErrors(file));
    }

    Label label = pathFragmentToLabel(arg.getRepository(), file, env);
    if (label == null) {
      Preconditions.checkState(env.valuesMissing(), "null label with no missing %s", file);
      return null;
    }

    Map<Location, PathFragment> astImports = ast.getImports();
    Map<PathFragment, Extension> importMap = Maps.newHashMapWithExpectedSize(astImports.size());
    ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
    Map<SkyKey, PathFragment> skylarkImports = Maps.newHashMapWithExpectedSize(astImports.size());
    for (Map.Entry<Location, PathFragment> entry : ast.getImports().entrySet()) {
      try {
        skylarkImports.put(
            PackageFunction.getImportKey(entry, ruleClassProvider.getPreludePath(), file, arg),
            entry.getValue());
      } catch (ASTLookupInputException e) {
        throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
      }
    }

    Map<SkyKey, SkyValue> skylarkImportMap;
    boolean valuesMissing = false;
    if (visitedKeysForCycle == null) {
      // Not inlining.
      skylarkImportMap = env.getValues(skylarkImports.keySet());
      valuesMissing = env.valuesMissing();
    } else {
      // inlining calls to SkylarkImportLookupFunction.
      if (!visitedKeysForCycle.add(skyKey)) {
        ImmutableList<SkyKey> cycle =
            CycleUtils.splitIntoPathAndChain(Predicates.equalTo(skyKey), visitedKeysForCycle)
                .second;
        if (env.getValue(SkylarkImportUniqueCycleValue.key(cycle)) == null) {
          return null;
        }
        throw new SkylarkImportLookupFunctionException(
            new SkylarkImportFailedException("Skylark import cycle"));
      }
      skylarkImportMap = Maps.newHashMapWithExpectedSize(astImports.size());
      for (SkyKey skylarkImport : skylarkImports.keySet()) {
        SkyValue skyValue = this.computeWithInlineCalls(skylarkImport, env, visitedKeysForCycle);
        if (skyValue == null) {
          Preconditions.checkState(
              env.valuesMissing(), "no skylark import value for %s", skylarkImport);
          // Don't give up on computing. This is against the Skyframe contract, but we don't want to
          // pay the price of serializing all these calls, since they are fundamentally independent.
          valuesMissing = true;
        } else {
          skylarkImportMap.put(skylarkImport, skyValue);
        }
      }
      // All imports traversed, this key can no longer be part of a cycle.
      visitedKeysForCycle.remove(skyKey);
    }

    if (valuesMissing) {
      // This means some imports are unavailable.
      return null;
    }

    for (Map.Entry<SkyKey, SkyValue> entry : skylarkImportMap.entrySet()) {
      SkylarkImportLookupValue importLookupValue = (SkylarkImportLookupValue) entry.getValue();
      importMap.put(
          skylarkImports.get(entry.getKey()), importLookupValue.getEnvironmentExtension());
      fileDependencies.add(importLookupValue.getDependency());
    }

    // Skylark UserDefinedFunction-s in that file will share this function definition Environment,
    // which will be frozen by the time it is returned by createExtension.
    Extension extension = createExtension(ast, file, importMap, env);

    return new SkylarkImportLookupValue(
        extension, new SkylarkFileDependency(label, fileDependencies.build()));
  }
  @Override
  public SkyValue compute(SkyKey skyKey, Environment env)
      throws SkyFunctionException, InterruptedException {
    PackageIdentifier arg = (PackageIdentifier) skyKey.argument();
    PathFragment file = arg.getPackageFragment();
    ASTFileLookupValue astLookupValue = null;
    try {
      SkyKey astLookupKey = ASTFileLookupValue.key(arg);
      astLookupValue =
          (ASTFileLookupValue)
              env.getValueOrThrow(
                  astLookupKey,
                  ErrorReadingSkylarkExtensionException.class,
                  InconsistentFilesystemException.class);
    } catch (ErrorReadingSkylarkExtensionException e) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.errorReadingFile(file, e.getMessage()));
    } catch (InconsistentFilesystemException e) {
      throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
    }
    if (astLookupValue == null) {
      return null;
    }
    if (astLookupValue.getAST() == null) {
      // Skylark import files have to exist.
      throw new SkylarkImportLookupFunctionException(SkylarkImportFailedException.noFile(file));
    }

    BuildFileAST ast = astLookupValue.getAST();
    if (ast.containsErrors()) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.skylarkErrors(file));
    }

    Map<Location, PathFragment> astImports = ast.getImports();
    Map<PathFragment, Extension> importMap = Maps.newHashMapWithExpectedSize(astImports.size());
    ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
    Map<SkyKey, PathFragment> skylarkImports = Maps.newHashMapWithExpectedSize(astImports.size());
    for (Map.Entry<Location, PathFragment> entry : ast.getImports().entrySet()) {
      try {
        skylarkImports.put(
            PackageFunction.getImportKey(entry, ruleClassProvider.getPreludePath(), file, arg),
            entry.getValue());
      } catch (ASTLookupInputException e) {
        throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
      }
    }
    Map<SkyKey, SkyValue> skylarkImportMap = env.getValues(skylarkImports.keySet());

    if (env.valuesMissing()) {
      // This means some imports are unavailable.
      return null;
    }
    for (Map.Entry<SkyKey, SkyValue> entry : skylarkImportMap.entrySet()) {
      SkylarkImportLookupValue importLookupValue = (SkylarkImportLookupValue) entry.getValue();
      importMap.put(
          skylarkImports.get(entry.getKey()), importLookupValue.getEnvironmentExtension());
      fileDependencies.add(importLookupValue.getDependency());
    }

    Label label = pathFragmentToLabel(arg.getRepository(), file, env);

    if (label == null) {
      Preconditions.checkState(env.valuesMissing(), "label null but no missing for %s", file);
      return null;
    }

    // Skylark UserDefinedFunction-s in that file will share this function definition Environment,
    // which will be frozen by the time it is returned by createExtension.
    Extension extension = createExtension(ast, file, importMap, env);

    return new SkylarkImportLookupValue(
        extension, new SkylarkFileDependency(label, fileDependencies.build()));
  }