// Copyright 2019 The Android Open Source Project
//
// 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 rust

import (
	"android/soong/cc"
	"errors"
	"fmt"
	"path/filepath"
	"strings"

	"github.com/google/blueprint/proptools"

	"android/soong/android"
	"android/soong/rust/config"
)

type RustLinkage int

const (
	DefaultLinkage RustLinkage = iota
	RlibLinkage
	DylibLinkage
)

type compiler interface {
	initialize(ctx ModuleContext)
	compilerFlags(ctx ModuleContext, flags Flags) Flags
	cfgFlags(ctx ModuleContext, flags Flags) Flags
	featureFlags(ctx ModuleContext, module *Module, flags Flags) Flags
	compilerProps() []interface{}
	compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput
	compilerDeps(ctx DepsContext, deps Deps) Deps
	crateName() string
	edition() string
	features(ctx android.ConfigurableEvaluatorContext, module *Module) []string
	rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath
	Thinlto() bool

	// Output directory in which source-generated code from dependencies is
	// copied. This is equivalent to Cargo's OUT_DIR variable.
	cargoOutDir() android.OptionalPath

	// cargoPkgVersion returns the value of the Cargo_pkg_version property.
	cargoPkgVersion() string

	// cargoEnvCompat returns whether Cargo environment variables should be used.
	cargoEnvCompat() bool

	inData() bool
	install(ctx ModuleContext)
	relativeInstallPath() string
	everInstallable() bool

	nativeCoverage() bool

	Disabled() bool
	SetDisabled()

	stdLinkage(ctx *depsContext) RustLinkage
	noStdlibs() bool

	unstrippedOutputFilePath() android.Path
	strippedOutputFilePath() android.OptionalPath

	checkedCrateRootPath() (android.Path, error)

	Aliases() map[string]string
}

func (compiler *baseCompiler) edition() string {
	return proptools.StringDefault(compiler.Properties.Edition, config.DefaultEdition)
}

func (compiler *baseCompiler) setNoStdlibs() {
	compiler.Properties.No_stdlibs = proptools.BoolPtr(true)
}

func (compiler *baseCompiler) disableLints() {
	compiler.Properties.Lints = proptools.StringPtr("none")
}

func NewBaseCompiler(dir, dir64 string, location installLocation) *baseCompiler {
	return &baseCompiler{
		Properties: BaseCompilerProperties{},
		dir:        dir,
		dir64:      dir64,
		location:   location,
	}
}

type installLocation int

const (
	InstallInSystem installLocation = 0
	InstallInData                   = iota

	incorrectSourcesError = "srcs can only contain one path for a rust file and source providers prefixed by \":\""
	genSubDir             = "out/"
)

type BaseCompilerProperties struct {
	// path to the source file that is the main entry point of the program (e.g. main.rs or lib.rs).
	// Only a single source file can be defined. Modules which generate source can be included by prefixing
	// the module name with ":", for example ":libfoo_bindgen"
	//
	// If no source file is defined, a single generated source module can be defined to be used as the main source.
	Srcs []string `android:"path,arch_variant"`

	// Entry point that is passed to rustc to begin the compilation. E.g. main.rs or lib.rs.
	// When this property is set,
	//    * sandboxing is enabled for this module, and
	//    * the srcs attribute is interpreted as a list of all source files potentially
	//          used in compilation, including the entrypoint, and
	//    * compile_data can be used to add additional files used in compilation that
	//          not directly used as source files.
	Crate_root *string `android:"path,arch_variant"`

	// name of the lint set that should be used to validate this module.
	//
	// Possible values are "default" (for using a sensible set of lints
	// depending on the module's location), "android" (for the strictest
	// lint set that applies to all Android platform code), "vendor" (for
	// a relaxed set) and "none" (for ignoring all lint warnings and
	// errors). The default value is "default".
	Lints *string

	// flags to pass to rustc. To enable configuration options or features, use the "cfgs" or "features" properties.
	Flags []string `android:"arch_variant"`

	// flags to pass to the linker
	Ld_flags []string `android:"arch_variant"`

	// Rust crate dependencies to rename. Each entry should be a string of the form "dependencyname:alias".
	//
	// "dependencyname" here should be the name of the crate, not the Android module. This is
	// equivalent to writing `alias = { package = "dependencyname" }` in a `Cargo.toml`.
	Aliases []string

	// list of rust rlib crate dependencies
	Rlibs []string `android:"arch_variant"`

	// list of rust automatic crate dependencies.
	// Rustlibs linkage is rlib for host targets and dylib for device targets.
	Rustlibs proptools.Configurable[[]string] `android:"arch_variant"`

	// list of rust proc_macro crate dependencies
	Proc_macros []string `android:"arch_variant"`

	// list of C shared library dependencies
	Shared_libs []string `android:"arch_variant"`

	// list of C static library dependencies. These dependencies do not normally propagate to dependents
	// and may need to be redeclared. See whole_static_libs for bundling static dependencies into a library.
	Static_libs []string `android:"arch_variant"`

	// Similar to static_libs, but will bundle the static library dependency into a library. This is helpful
	// to avoid having to redeclare the dependency for dependents of this library, but in some cases may also
	// result in bloat if multiple dependencies all include the same static library whole.
	//
	// The common use case for this is when the static library is unlikely to be a dependency of other modules to avoid
	// having to redeclare the static library dependency for every dependent module.
	// If you are not sure what to, for rust_library modules most static dependencies should go in static_libraries,
	// and for rust_ffi modules most static dependencies should go into whole_static_libraries.
	//
	// For rust_ffi static variants, these libraries will be included in the resulting static library archive.
	//
	// For rust_library rlib variants, these libraries will be bundled into the resulting rlib library. This will
	// include all of the static libraries symbols in any dylibs or binaries which use this rlib as well.
	Whole_static_libs []string `android:"arch_variant"`

	// list of Rust system library dependencies.
	//
	// This is usually only needed when `no_stdlibs` is true, in which case it can be used to depend on system crates
	// like `core` and `alloc`.
	Stdlibs []string `android:"arch_variant"`

	// crate name, required for modules which produce Rust libraries: rust_library, rust_ffi and SourceProvider
	// modules which create library variants (rust_bindgen). This must be the expected extern crate name used in
	// source, and is required to conform to an enforced format matching library output files (if the output file is
	// lib<someName><suffix>, the crate_name property must be <someName>).
	Crate_name string `android:"arch_variant"`

	// list of features to enable for this crate
	Features proptools.Configurable[[]string] `android:"arch_variant"`

	// list of configuration options to enable for this crate. To enable features, use the "features" property.
	Cfgs proptools.Configurable[[]string] `android:"arch_variant"`

	// specific rust edition that should be used if the default version is not desired
	Edition *string `android:"arch_variant"`

	// sets name of the output
	Stem *string `android:"arch_variant"`

	// append to name of output
	Suffix *string `android:"arch_variant"`

	// install to a subdirectory of the default install path for the module
	Relative_install_path *string `android:"arch_variant"`

	// whether to suppress inclusion of standard crates - defaults to false
	No_stdlibs *bool `android:"arch_variant"`

	// Change the rustlibs linkage to select rlib linkage by default for device targets.
	// Also link libstd as an rlib as well on device targets.
	// Note: This is the default behavior for host targets.
	//
	// This is primarily meant for rust_binary and rust_ffi modules where the default
	// linkage of libstd might need to be overridden in some use cases. This should
	// generally be avoided with other module types since it may cause collisions at
	// linkage if all dependencies of the root binary module do not link against libstd
	// the same way.
	Prefer_rlib *bool `android:"arch_variant"`

	// Enables emitting certain Cargo environment variables. Only intended to be used for compatibility purposes.
	// Will set CARGO_CRATE_NAME to the crate_name property's value.
	// Will set CARGO_BIN_NAME to the output filename value without the extension.
	Cargo_env_compat *bool

	// If cargo_env_compat is true, sets the CARGO_PKG_VERSION env var to this value.
	Cargo_pkg_version *string

	// Control whether LTO is used for the final (Rust) linkage. This does not impact
	// cross-language LTO.
	Lto struct {
		// Whether thin LTO should be enabled. By default this is true.
		// LTO provides such a large code size benefit for Rust, this should always
		// be enabled for production builds unless there's a clear need to disable it.
		Thin *bool `android:"arch_variant"`
	} `android:"arch_variant"`
}

type baseCompiler struct {
	Properties BaseCompilerProperties

	// Install related
	dir      string
	dir64    string
	subDir   string
	relative string
	path     android.InstallPath
	location installLocation
	sanitize *sanitize

	distFile android.OptionalPath

	installDeps android.InstallPaths

	// unstripped output file.
	unstrippedOutputFile android.Path

	// stripped output file.
	strippedOutputFile android.OptionalPath

	// If a crate has a source-generated dependency, a copy of the source file
	// will be available in cargoOutDir (equivalent to Cargo OUT_DIR).
	// This is stored internally because it may not be available during
	// 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 {
	return false
}

// Thin LTO is enabled by default.
func (compiler *baseCompiler) Thinlto() bool {
	return BoolDefault(compiler.Properties.Lto.Thin, true)
}

func (compiler *baseCompiler) SetDisabled() {
	panic("baseCompiler does not implement SetDisabled()")
}

func (compiler *baseCompiler) noStdlibs() bool {
	return Bool(compiler.Properties.No_stdlibs)
}

func (compiler *baseCompiler) coverageOutputZipPath() android.OptionalPath {
	panic("baseCompiler does not implement coverageOutputZipPath()")
}

func (compiler *baseCompiler) preferRlib() bool {
	return Bool(compiler.Properties.Prefer_rlib)
}

func (compiler *baseCompiler) Aliases() map[string]string {
	aliases := map[string]string{}
	for _, entry := range compiler.Properties.Aliases {
		dep, alias, found := strings.Cut(entry, ":")
		if !found {
			panic(fmt.Errorf("invalid aliases entry %q missing ':'", entry))
		}
		aliases[dep] = alias
	}
	return aliases
}

func (compiler *baseCompiler) stdLinkage(ctx *depsContext) RustLinkage {
	// For devices, we always link stdlibs in as dylibs by default.
	if compiler.preferRlib() {
		return RlibLinkage
	} else if ctx.Device() {
		return DylibLinkage
	} else {
		return RlibLinkage
	}
}

var _ compiler = (*baseCompiler)(nil)

func (compiler *baseCompiler) inData() bool {
	return compiler.location == InstallInData
}

func (compiler *baseCompiler) compilerProps() []interface{} {
	return []interface{}{&compiler.Properties}
}

func cfgsToFlags(cfgs []string) []string {
	flags := make([]string, 0, len(cfgs))
	for _, cfg := range cfgs {
		flags = append(flags, "--cfg '"+cfg+"'")
	}

	return flags
}

func (compiler *baseCompiler) features(ctx android.ConfigurableEvaluatorContext, module *Module) []string {
	eval := module.ConfigurableEvaluator(ctx)
	return compiler.Properties.Features.GetOrDefault(eval, nil)
}

func (compiler *baseCompiler) featuresToFlags(ctx android.ConfigurableEvaluatorContext, module *Module) []string {
	flags := []string{}
	for _, feature := range compiler.features(ctx, module) {
		flags = append(flags, "--cfg 'feature=\""+feature+"\"'")
	}

	return flags
}

func (compiler *baseCompiler) featureFlags(ctx ModuleContext, module *Module, flags Flags) Flags {
	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags(ctx, module)...)
	flags.RustdocFlags = append(flags.RustdocFlags, compiler.featuresToFlags(ctx, module)...)

	return flags
}

func CommonDefaultCfgFlags(flags Flags, vendor bool, product bool) Flags {
	var cfgs []string
	if vendor || product {
		cfgs = append(cfgs, "android_vndk")
		if vendor {
			cfgs = append(cfgs, "android_vendor")
		} else if product {
			cfgs = append(cfgs, "android_product")
		}
	}

	flags.RustFlags = append(flags.RustFlags, cfgsToFlags(cfgs)...)
	flags.RustdocFlags = append(flags.RustdocFlags, cfgsToFlags(cfgs)...)
	return flags
}

func (compiler *baseCompiler) cfgFlags(ctx ModuleContext, flags Flags) Flags {
	flags = CommonDefaultCfgFlags(flags, ctx.RustModule().InVendor(), ctx.RustModule().InProduct())

	cfgFlags := cfgsToFlags(compiler.Properties.Cfgs.GetOrDefault(ctx, nil))
	flags.RustFlags = append(flags.RustFlags, cfgFlags...)
	flags.RustdocFlags = append(flags.RustdocFlags, cfgFlags...)

	return flags
}

func CommonDefaultFlags(ctx android.ModuleContext, toolchain config.Toolchain, flags Flags) Flags {
	flags.GlobalRustFlags = append(flags.GlobalRustFlags, config.GlobalRustFlags...)
	flags.GlobalRustFlags = append(flags.GlobalRustFlags, toolchain.ToolchainRustFlags())
	flags.GlobalLinkFlags = append(flags.GlobalLinkFlags, toolchain.ToolchainLinkFlags())
	flags.EmitXrefs = ctx.Config().EmitXrefRules()

	if ctx.Host() && !ctx.Windows() {
		flags.LinkFlags = append(flags.LinkFlags, cc.RpathFlags(ctx)...)
	}

	if ctx.Os() == android.Linux {
		// Add -lc, -lrt, -ldl, -lpthread, -lm and -lgcc_s to glibc builds to match
		// the default behavior of device builds.
		flags.LinkFlags = append(flags.LinkFlags, config.LinuxHostGlobalLinkFlags...)
	} else if ctx.Os() == android.Darwin {
		// Add -lc, -ldl, -lpthread and -lm to glibc darwin builds to match the default
		// behavior of device builds.
		flags.LinkFlags = append(flags.LinkFlags,
			"-lc",
			"-ldl",
			"-lpthread",
			"-lm",
		)
	}
	return flags
}

func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags) Flags {

	flags = CommonDefaultFlags(ctx, ctx.toolchain(), flags)
	lintFlags, err := config.RustcLintsForDir(ctx.ModuleDir(), compiler.Properties.Lints)
	if err != nil {
		ctx.PropertyErrorf("lints", err.Error())
	}

	// linkage-related flags are disallowed.
	for _, s := range compiler.Properties.Ld_flags {
		if strings.HasPrefix(s, "-Wl,-l") || strings.HasPrefix(s, "-Wl,-L") {
			ctx.PropertyErrorf("ld_flags", "'-Wl,-l' and '-Wl,-L' flags cannot be manually specified")
		}
	}
	for _, s := range compiler.Properties.Flags {
		if strings.HasPrefix(s, "-l") || strings.HasPrefix(s, "-L") {
			ctx.PropertyErrorf("flags", "'-l' and '-L' flags cannot be manually specified")
		}
		if strings.HasPrefix(s, "--extern") {
			ctx.PropertyErrorf("flags", "'--extern' flag cannot be manually specified")
		}
		if strings.HasPrefix(s, "-Clink-args=") || strings.HasPrefix(s, "-C link-args=") {
			ctx.PropertyErrorf("flags", "'-C link-args' flag cannot be manually specified")
		}
	}

	flags.RustFlags = append(flags.RustFlags, lintFlags)
	flags.RustFlags = append(flags.RustFlags, compiler.Properties.Flags...)
	flags.RustFlags = append(flags.RustFlags, "--edition="+compiler.edition())
	flags.RustdocFlags = append(flags.RustdocFlags, "--edition="+compiler.edition())
	flags.LinkFlags = append(flags.LinkFlags, compiler.Properties.Ld_flags...)

	return flags
}

func (compiler *baseCompiler) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
	panic(fmt.Errorf("baseCrater doesn't know how to crate things!"))
}

func (compiler *baseCompiler) rustdoc(ctx ModuleContext, flags Flags,
	deps PathDeps) android.OptionalPath {

	return android.OptionalPath{}
}

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 {
	return android.OptionalPathForPath(compiler.cachedCargoOutDir)
}

func (compiler *baseCompiler) cargoEnvCompat() bool {
	return Bool(compiler.Properties.Cargo_env_compat)
}

func (compiler *baseCompiler) cargoPkgVersion() string {
	return String(compiler.Properties.Cargo_pkg_version)
}

func (compiler *baseCompiler) unstrippedOutputFilePath() android.Path {
	return compiler.unstrippedOutputFile
}

func (compiler *baseCompiler) strippedOutputFilePath() android.OptionalPath {
	return compiler.strippedOutputFile
}

func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps {
	deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs...)
	deps.Rustlibs = append(deps.Rustlibs, compiler.Properties.Rustlibs.GetOrDefault(ctx, nil)...)
	deps.ProcMacros = append(deps.ProcMacros, compiler.Properties.Proc_macros...)
	deps.StaticLibs = append(deps.StaticLibs, compiler.Properties.Static_libs...)
	deps.WholeStaticLibs = append(deps.WholeStaticLibs, compiler.Properties.Whole_static_libs...)
	deps.SharedLibs = append(deps.SharedLibs, compiler.Properties.Shared_libs...)
	deps.Stdlibs = append(deps.Stdlibs, compiler.Properties.Stdlibs...)

	if !Bool(compiler.Properties.No_stdlibs) {
		for _, stdlib := range config.Stdlibs {
			// If we're building for the build host, use the prebuilt stdlibs, unless the host
			// is linux_bionic which doesn't have prebuilts.
			if ctx.Host() && !ctx.Target().HostCross && ctx.Target().Os != android.LinuxBionic {
				stdlib = "prebuilt_" + stdlib
			}
			deps.Stdlibs = append(deps.Stdlibs, stdlib)
		}
	}
	return deps
}

func bionicDeps(ctx DepsContext, deps Deps, static bool) Deps {
	bionicLibs := []string{}
	bionicLibs = append(bionicLibs, "liblog")
	bionicLibs = append(bionicLibs, "libc")
	bionicLibs = append(bionicLibs, "libm")
	bionicLibs = append(bionicLibs, "libdl")

	if static {
		deps.StaticLibs = append(deps.StaticLibs, bionicLibs...)
	} else {
		deps.SharedLibs = append(deps.SharedLibs, bionicLibs...)
	}
	if ctx.RustModule().StaticExecutable() {
		deps.StaticLibs = append(deps.StaticLibs, "libunwind")
	}
	if libRuntimeBuiltins := config.BuiltinsRuntimeLibrary(ctx.toolchain()); libRuntimeBuiltins != "" {
		deps.StaticLibs = append(deps.StaticLibs, libRuntimeBuiltins)
	}
	return deps
}

func muslDeps(ctx DepsContext, deps Deps, static bool) Deps {
	muslLibs := []string{"libc_musl"}
	if static {
		deps.StaticLibs = append(deps.StaticLibs, muslLibs...)
	} else {
		deps.SharedLibs = append(deps.SharedLibs, muslLibs...)
	}
	if libRuntimeBuiltins := config.BuiltinsRuntimeLibrary(ctx.toolchain()); libRuntimeBuiltins != "" {
		deps.StaticLibs = append(deps.StaticLibs, libRuntimeBuiltins)
	}

	return deps
}

func (compiler *baseCompiler) crateName() string {
	return compiler.Properties.Crate_name
}

func (compiler *baseCompiler) everInstallable() bool {
	// Most modules are installable, so return true by default.
	return true
}

func (compiler *baseCompiler) installDir(ctx ModuleContext) android.InstallPath {
	dir := compiler.dir
	if ctx.toolchain().Is64Bit() && compiler.dir64 != "" {
		dir = compiler.dir64
	}
	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
		dir = filepath.Join(dir, ctx.Target().NativeBridgeRelativePath)
	}
	if !ctx.Host() && ctx.Config().HasMultilibConflict(ctx.Arch().ArchType) {
		dir = filepath.Join(dir, ctx.Arch().ArchType.String())
	}

	if compiler.location == InstallInData && ctx.RustModule().InVendorOrProduct() {
		if ctx.RustModule().InProduct() {
			dir = filepath.Join(dir, "product")
		} else if ctx.RustModule().InVendor() {
			dir = filepath.Join(dir, "vendor")
		} else {
			ctx.ModuleErrorf("Unknown data+VNDK installation kind")
		}
	}

	return android.PathForModuleInstall(ctx, dir, compiler.subDir,
		compiler.relativeInstallPath(), compiler.relative)
}

func (compiler *baseCompiler) nativeCoverage() bool {
	return false
}

func (compiler *baseCompiler) install(ctx ModuleContext) {
	path := ctx.RustModule().OutputFile()
	compiler.path = ctx.InstallFile(compiler.installDir(ctx), path.Path().Base(), path.Path(), compiler.installDeps...)
}

func (compiler *baseCompiler) installTestData(ctx ModuleContext, data []android.DataPath) {
	installedData := ctx.InstallTestData(compiler.installDir(ctx), data)
	compiler.installDeps = append(compiler.installDeps, installedData...)
}

func (compiler *baseCompiler) getStem(ctx android.ModuleContext) string {
	return compiler.getStemWithoutSuffix(ctx) + String(compiler.Properties.Suffix)
}

func (compiler *baseCompiler) getStemWithoutSuffix(ctx android.BaseModuleContext) string {
	stem := ctx.ModuleName()
	if String(compiler.Properties.Stem) != "" {
		stem = String(compiler.Properties.Stem)
	}

	return stem
}

func (compiler *baseCompiler) relativeInstallPath() string {
	return String(compiler.Properties.Relative_install_path)
}

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, 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.
	numSrcs := 0
	srcIndex := 0
	for i, s := range srcs {
		if android.SrcIsModule(s) == "" {
			numSrcs++
			srcIndex = i
		}
	}
	if numSrcs > 1 {
		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 {
		return nil, errors.New("main source file must be the first in srcs")
	} else if numSrcs > 1 {
		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)
	if len(paths) == 0 {
		return nil, errors.New("srcs must not be empty")
	}
	return paths[srcIndex], nil
}
