rust: Cache crateRootPath to avoid ctx

This makes it possible to call crateRootPath in situations where a
ModuleContext is unavailable.

Test: m nothing
Bug: 309943184
Change-Id: Iee20b0606954a18ca516cdac40917d0016f94a05
diff --git a/rust/binary.go b/rust/binary.go
index 146c683..5e7e922 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -137,7 +137,7 @@
 	fileName := binary.getStem(ctx) + ctx.toolchain().ExecutableSuffix()
 	outputFile := android.PathForModuleOut(ctx, fileName)
 	ret := buildOutput{outputFile: outputFile}
-	crateRootPath := binary.crateRootPath(ctx)
+	crateRootPath := crateRootPath(ctx, binary)
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
diff --git a/rust/compiler.go b/rust/compiler.go
index 899502a..e2415a4 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/cc"
+	"errors"
 	"fmt"
 	"path/filepath"
 	"strings"
@@ -71,7 +72,7 @@
 	unstrippedOutputFilePath() android.Path
 	strippedOutputFilePath() android.OptionalPath
 
-	crateRootPath(ctx ModuleContext) android.Path
+	checkedCrateRootPath() (android.Path, error)
 }
 
 func (compiler *baseCompiler) edition() string {
@@ -248,6 +249,12 @@
 	// singleton-generation passes like rustdoc/rust_project.json, but should
 	// be stashed during initial generation.
 	cachedCargoOutDir android.ModuleOutPath
+	// Calculated crate root cached internally because ModuleContext is not
+	// available to singleton targets like rustdoc/rust_project.json
+	cachedCrateRootPath android.Path
+	// If cachedCrateRootPath is nil after initialization, this will contain
+	// an explanation of why
+	cachedCrateRootError error
 }
 
 func (compiler *baseCompiler) Disabled() bool {
@@ -399,6 +406,12 @@
 
 func (compiler *baseCompiler) initialize(ctx ModuleContext) {
 	compiler.cachedCargoOutDir = android.PathForModuleOut(ctx, genSubDir)
+	if compiler.Properties.Crate_root == nil {
+		compiler.cachedCrateRootPath, compiler.cachedCrateRootError = srcPathFromModuleSrcs(ctx, compiler.Properties.Srcs)
+	} else {
+		compiler.cachedCrateRootPath = android.PathForModuleSrc(ctx, *compiler.Properties.Crate_root)
+		compiler.cachedCrateRootError = nil
+	}
 }
 
 func (compiler *baseCompiler) cargoOutDir() android.OptionalPath {
@@ -539,21 +552,20 @@
 	return String(compiler.Properties.Relative_install_path)
 }
 
-func (compiler *baseCompiler) crateRootPath(ctx ModuleContext) android.Path {
-	if compiler.Properties.Crate_root == nil {
-		path := srcPathFromModuleSrcs(ctx, compiler.Properties.Srcs)
-		return path
-	} else {
-		return android.PathForModuleSrc(ctx, *compiler.Properties.Crate_root)
+func (compiler *baseCompiler) checkedCrateRootPath() (android.Path, error) {
+	return compiler.cachedCrateRootPath, compiler.cachedCrateRootError
+}
+
+func crateRootPath(ctx ModuleContext, compiler compiler) android.Path {
+	root, err := compiler.checkedCrateRootPath()
+	if err != nil {
+		ctx.PropertyErrorf("srcs", err.Error())
 	}
+	return root
 }
 
 // Returns the Path for the main source file along with Paths for generated source files from modules listed in srcs.
-func srcPathFromModuleSrcs(ctx ModuleContext, srcs []string) android.Path {
-	if len(srcs) == 0 {
-		ctx.PropertyErrorf("srcs", "srcs must not be empty")
-	}
-
+func srcPathFromModuleSrcs(ctx ModuleContext, srcs []string) (android.Path, error) {
 	// The srcs can contain strings with prefix ":".
 	// They are dependent modules of this module, with android.SourceDepTag.
 	// They are not the main source file compiled by rustc.
@@ -566,19 +578,22 @@
 		}
 	}
 	if numSrcs > 1 {
-		ctx.PropertyErrorf("srcs", incorrectSourcesError)
+		return nil, errors.New(incorrectSourcesError)
 	}
 
 	// If a main source file is not provided we expect only a single SourceProvider module to be defined
 	// within srcs, with the expectation that the first source it provides is the entry point.
 	if srcIndex != 0 {
-		ctx.PropertyErrorf("srcs", "main source file must be the first in srcs")
+		return nil, errors.New("main source file must be the first in srcs")
 	} else if numSrcs > 1 {
-		ctx.PropertyErrorf("srcs", "only a single generated source module can be defined without a main source file.")
+		return nil, errors.New("only a single generated source module can be defined without a main source file.")
 	}
 
 	// TODO: b/297264540 - once all modules are sandboxed, we need to select the proper
 	// entry point file from Srcs rather than taking the first one
 	paths := android.PathsForModuleSrc(ctx, srcs)
-	return paths[srcIndex]
+	if len(paths) == 0 {
+		return nil, errors.New("srcs must not be empty")
+	}
+	return paths[srcIndex], nil
 }
diff --git a/rust/library.go b/rust/library.go
index 613e9b7..c0ff741 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -15,6 +15,7 @@
 package rust
 
 import (
+	"errors"
 	"fmt"
 	"regexp"
 	"strings"
@@ -489,7 +490,7 @@
 	var outputFile android.ModuleOutPath
 	var ret buildOutput
 	var fileName string
-	crateRootPath := library.crateRootPath(ctx)
+	crateRootPath := crateRootPath(ctx, library)
 
 	if library.sourceProvider != nil {
 		deps.srcProviderFiles = append(deps.srcProviderFiles, library.sourceProvider.Srcs()...)
@@ -584,12 +585,16 @@
 	return ret
 }
 
-func (library *libraryDecorator) crateRootPath(ctx ModuleContext) android.Path {
+func (library *libraryDecorator) checkedCrateRootPath() (android.Path, error) {
 	if library.sourceProvider != nil {
+		srcs := library.sourceProvider.Srcs()
+		if len(srcs) == 0 {
+			return nil, errors.New("Source provider generated 0 sources")
+		}
 		// Assume the first source from the source provider is the library entry point.
-		return library.sourceProvider.Srcs()[0]
+		return srcs[0], nil
 	} else {
-		return library.baseCompiler.crateRootPath(ctx)
+		return library.baseCompiler.checkedCrateRootPath()
 	}
 }
 
@@ -604,7 +609,7 @@
 		return android.OptionalPath{}
 	}
 
-	return android.OptionalPathForPath(Rustdoc(ctx, library.crateRootPath(ctx),
+	return android.OptionalPathForPath(Rustdoc(ctx, crateRootPath(ctx, library),
 		deps, flags))
 }
 
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index e3a48bf..8a0543d 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -79,7 +79,7 @@
 	fileName := procMacro.getStem(ctx) + ctx.toolchain().ProcMacroSuffix()
 	outputFile := android.PathForModuleOut(ctx, fileName)
 
-	srcPath := procMacro.crateRootPath(ctx)
+	srcPath := crateRootPath(ctx, procMacro)
 	ret := TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile)
 	procMacro.baseCompiler.unstrippedOutputFile = outputFile
 	return ret