diff --git a/Android.bp b/Android.bp index 5c76f5a19..d63921a63 100644 --- a/Android.bp +++ b/Android.bp @@ -325,6 +325,50 @@ bootstrap_go_package { ], } +bootstrap_go_package { + name: "soong-rust-config", + pkgPath: "android/soong/rust/config", + deps: [ + "soong-android", + "soong-cc-config", + ], + srcs: [ + "rust/config/global.go", + "rust/config/toolchain.go", + "rust/config/x86_linux_host.go", + "rust/config/x86_64_device.go", + ], +} + +bootstrap_go_package { + name: "soong-rust", + pkgPath: "android/soong/rust", + deps: [ + "soong", + "soong-android", + "soong-cc", + "soong-rust-config", + ], + srcs: [ + "rust/androidmk.go", + "rust/compiler.go", + "rust/binary.go", + "rust/builder.go", + "rust/library.go", + "rust/prebuilt.go", + "rust/proc_macro.go", + "rust/rust.go", + "rust/testing.go", + ], + testSrcs: [ + "rust/binary_test.go", + "rust/compiler_test.go", + "rust/library_test.go", + "rust/rust_test.go", + ], + pluginFor: ["soong_build"], +} + bootstrap_go_package { name: "soong-python", pkgPath: "android/soong/python", diff --git a/cc/cc.go b/cc/cc.go index 4edbee6a6..2ff343413 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -392,6 +392,14 @@ func IsTestPerSrcDepTag(depTag blueprint.DependencyTag) bool { return ok && ccDepTag == testPerSrcDepTag } +func SharedDepTag() dependencyTag { + return sharedDepTag +} + +func StaticDepTag() dependencyTag { + return staticDepTag +} + // Module contains the properties and members used by all C/C++ module types, and implements // the blueprint.Module interface. It delegates to compiler, linker, and installer interfaces // to construct the output file. Behavior can be customized with a Customizer interface diff --git a/rust/androidmk.go b/rust/androidmk.go new file mode 100644 index 000000000..c9056e1b4 --- /dev/null +++ b/rust/androidmk.go @@ -0,0 +1,155 @@ +// 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 ( + "fmt" + "io" + "path/filepath" + "regexp" + "strings" + + "android/soong/android" +) + +type AndroidMkContext interface { + Name() string + Target() android.Target + subAndroidMk(*android.AndroidMkData, interface{}) +} + +type subAndroidMkProvider interface { + AndroidMk(AndroidMkContext, *android.AndroidMkData) +} + +func (mod *Module) subAndroidMk(data *android.AndroidMkData, obj interface{}) { + if mod.subAndroidMkOnce == nil { + mod.subAndroidMkOnce = make(map[subAndroidMkProvider]bool) + } + if androidmk, ok := obj.(subAndroidMkProvider); ok { + if !mod.subAndroidMkOnce[androidmk] { + mod.subAndroidMkOnce[androidmk] = true + androidmk.AndroidMk(mod, data) + } + } +} + +func (mod *Module) AndroidMk() android.AndroidMkData { + ret := android.AndroidMkData{ + OutputFile: mod.outputFile, + Include: "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk", + Extra: []android.AndroidMkExtraFunc{ + func(w io.Writer, outputFile android.Path) { + if len(mod.Properties.AndroidMkRlibs) > 0 { + fmt.Fprintln(w, "LOCAL_RLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkRlibs, " ")) + } + if len(mod.Properties.AndroidMkDylibs) > 0 { + fmt.Fprintln(w, "LOCAL_DYLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkDylibs, " ")) + } + if len(mod.Properties.AndroidMkProcMacroLibs) > 0 { + fmt.Fprintln(w, "LOCAL_PROC_MACRO_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkProcMacroLibs, " ")) + } + if len(mod.Properties.AndroidMkSharedLibs) > 0 { + fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkSharedLibs, " ")) + } + if len(mod.Properties.AndroidMkStaticLibs) > 0 { + fmt.Fprintln(w, "LOCAL_STATIC_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkStaticLibs, " ")) + } + }, + }, + } + + mod.subAndroidMk(&ret, mod.compiler) + + return ret +} + +func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) { + ctx.subAndroidMk(ret, binary.baseCompiler) + + ret.Class = "EXECUTABLES" + ret.DistFile = binary.distFile + ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) { + fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", binary.unstrippedOutputFile.String()) + }) +} + +func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) { + ctx.subAndroidMk(ret, library.baseCompiler) + + if library.rlib() { + ret.Class = "RLIB_LIBRARIES" + } else if library.dylib() { + ret.Class = "DYLIB_LIBRARIES" + } + ret.DistFile = library.distFile + ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) { + if !library.rlib() { + fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", library.unstrippedOutputFile.String()) + } + }) +} + +func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) { + ctx.subAndroidMk(ret, procMacro.baseCompiler) + + ret.Class = "PROC_MACRO_LIBRARIES" + ret.DistFile = procMacro.distFile + +} + +func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) { + // Soong installation is only supported for host modules. Have Make + // installation trigger Soong installation. + if ctx.Target().Os.Class == android.Host { + ret.OutputFile = android.OptionalPathForPath(compiler.path) + } + ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) { + path := compiler.path.RelPathString() + dir, file := filepath.Split(path) + stem, suffix, _ := splitFileExt(file) + fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix) + fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(OUT_DIR)/"+filepath.Clean(dir)) + fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem) + }) +} + +//TODO: splitFileExt copied from cc/util.go; move this to android/util.go and refactor usages. + +// splitFileExt splits a file name into root, suffix and ext. root stands for the file name without +// the file extension and the version number (e.g. "libexample"). suffix stands for the +// concatenation of the file extension and the version number (e.g. ".so.1.0"). ext stands for the +// file extension after the version numbers are trimmed (e.g. ".so"). +var shlibVersionPattern = regexp.MustCompile("(?:\\.\\d+(?:svn)?)+") + +func splitFileExt(name string) (string, string, string) { + // Extract and trim the shared lib version number if the file name ends with dot digits. + suffix := "" + matches := shlibVersionPattern.FindAllStringIndex(name, -1) + if len(matches) > 0 { + lastMatch := matches[len(matches)-1] + if lastMatch[1] == len(name) { + suffix = name[lastMatch[0]:lastMatch[1]] + name = name[0:lastMatch[0]] + } + } + + // Extract the file name root and the file extension. + ext := filepath.Ext(name) + root := strings.TrimSuffix(name, ext) + suffix = ext + suffix + + return root, suffix, ext +} diff --git a/rust/binary.go b/rust/binary.go new file mode 100644 index 000000000..279c6f50f --- /dev/null +++ b/rust/binary.go @@ -0,0 +1,110 @@ +// 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/android" + "android/soong/rust/config" +) + +func init() { + android.RegisterModuleType("rust_binary", RustBinaryFactory) + android.RegisterModuleType("rust_binary_host", RustBinaryHostFactory) +} + +type BinaryCompilerProperties struct { + // path to the main source file that contains the program entry point (e.g. src/main.rs) + Srcs []string `android:"path,arch_variant"` + + // passes -C prefer-dynamic to rustc, which tells it to dynamically link the stdlib (assuming it has no dylib dependencies already) + Prefer_dynamic *bool +} + +type binaryDecorator struct { + *baseCompiler + + Properties BinaryCompilerProperties + distFile android.OptionalPath + unstrippedOutputFile android.Path +} + +var _ compiler = (*binaryDecorator)(nil) + +// rust_binary produces a binary that is runnable on a device. +func RustBinaryFactory() android.Module { + module, _ := NewRustBinary(android.HostAndDeviceSupported) + return module.Init() +} + +func RustBinaryHostFactory() android.Module { + module, _ := NewRustBinary(android.HostSupported) + return module.Init() +} + +func NewRustBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) { + module := newModule(hod, android.MultilibFirst) + + binary := &binaryDecorator{ + baseCompiler: NewBaseCompiler("bin", ""), + } + + module.compiler = binary + + return module, binary +} + +func (binary *binaryDecorator) preferDynamic() bool { + return Bool(binary.Properties.Prefer_dynamic) +} + +func (binary *binaryDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags { + flags = binary.baseCompiler.compilerFlags(ctx, flags) + if binary.preferDynamic() { + flags.RustFlags = append(flags.RustFlags, "-C prefer-dynamic") + } + return flags +} + +func (binary *binaryDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps { + deps = binary.baseCompiler.compilerDeps(ctx, deps) + + if binary.preferDynamic() || len(deps.Dylibs) > 0 { + for _, stdlib := range config.Stdlibs { + deps.Dylibs = append(deps.Dylibs, stdlib+"_"+ctx.toolchain().RustTriple()) + } + } + + return deps +} + +func (binary *binaryDecorator) compilerProps() []interface{} { + return append(binary.baseCompiler.compilerProps(), + &binary.Properties) +} + +func (binary *binaryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path { + fileName := binary.getStem(ctx) + ctx.toolchain().ExecutableSuffix() + + srcPath := srcPathFromModuleSrcs(ctx, binary.Properties.Srcs) + + outputFile := android.PathForModuleOut(ctx, fileName) + binary.unstrippedOutputFile = outputFile + + flags.RustFlags = append(flags.RustFlags, deps.depFlags...) + + TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + + return outputFile +} diff --git a/rust/binary_test.go b/rust/binary_test.go new file mode 100644 index 000000000..cd41fcfae --- /dev/null +++ b/rust/binary_test.go @@ -0,0 +1,46 @@ +// 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 ( + "strings" + "testing" +) + +// Test that the prefer_dynamic property is handled correctly. +func TestPreferDynamicBinary(t *testing.T) { + ctx := testRust(t, ` + rust_binary_host { + name: "fizz-buzz-dynamic", + srcs: ["foo.rs"], + prefer_dynamic: true, + } + + rust_binary_host { + name: "fizz-buzz", + srcs: ["foo.rs"], + }`) + + fizzBuzz := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Output("fizz-buzz") + fizzBuzzDynamic := ctx.ModuleForTests("fizz-buzz-dynamic", "linux_glibc_x86_64").Output("fizz-buzz-dynamic") + + if !strings.Contains(fizzBuzzDynamic.Args["rustcFlags"], "prefer-dynamic") { + t.Errorf("missing prefer-dynamic flag, rustcFlags: %#v", fizzBuzzDynamic.Args["rustcFlags"]) + } + + if strings.Contains(fizzBuzz.Args["rustcFlags"], "prefer-dynamic") { + t.Errorf("unexpected prefer-dynamic flag, rustcFlags: %#v", fizzBuzz.Args["rustcFlags"]) + } +} diff --git a/rust/builder.go b/rust/builder.go new file mode 100644 index 000000000..64e387b9f --- /dev/null +++ b/rust/builder.go @@ -0,0 +1,132 @@ +// 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 ( + "strings" + + "github.com/google/blueprint" + + "android/soong/android" +) + +var ( + _ = pctx.SourcePathVariable("rustcCmd", "${config.RustBin}/rustc") + rustc = pctx.AndroidStaticRule("rustc", + blueprint.RuleParams{ + Command: "$rustcCmd " + + "-C linker=${config.RustLinker} " + + "-C link-args=\"${config.RustLinkerArgs} ${linkFlags}\" " + + "-o $out $in ${libFlags} $rustcFlags " + + "&& $rustcCmd --emit=dep-info -o $out.d $in ${libFlags} $rustcFlags", + CommandDeps: []string{"$rustcCmd"}, + Depfile: "$out.d", + Deps: blueprint.DepsGCC, // Rustc deps-info writes out make compatible dep files: https://github.com/rust-lang/rust/issues/7633 + }, + "rustcFlags", "linkFlags", "libFlags") +) + +func init() { + +} + +func TransformSrcToBinary(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, outputFile android.WritablePath, includeDirs []string) { + targetTriple := ctx.(ModuleContext).toolchain().RustTriple() + + transformSrctoCrate(ctx, mainSrc, deps.RLibs, deps.DyLibs, deps.ProcMacros, deps.StaticLibs, deps.SharedLibs, flags, outputFile, "bin", includeDirs, targetTriple) +} + +func TransformSrctoRlib(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, outputFile android.WritablePath, includeDirs []string) { + targetTriple := ctx.(ModuleContext).toolchain().RustTriple() + + transformSrctoCrate(ctx, mainSrc, deps.RLibs, deps.DyLibs, deps.ProcMacros, deps.StaticLibs, deps.SharedLibs, flags, outputFile, "rlib", includeDirs, targetTriple) +} + +func TransformSrctoDylib(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, outputFile android.WritablePath, includeDirs []string) { + targetTriple := ctx.(ModuleContext).toolchain().RustTriple() + + transformSrctoCrate(ctx, mainSrc, deps.RLibs, deps.DyLibs, deps.ProcMacros, deps.StaticLibs, deps.SharedLibs, flags, outputFile, "dylib", includeDirs, targetTriple) +} + +func TransformSrctoProcMacro(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, outputFile android.WritablePath, includeDirs []string) { + // Proc macros are compiler plugins, and thus should target the host compiler + targetTriple := "" + + transformSrctoCrate(ctx, mainSrc, deps.RLibs, deps.DyLibs, deps.ProcMacros, deps.StaticLibs, deps.SharedLibs, flags, outputFile, "proc-macro", includeDirs, targetTriple) +} + +func rustLibsToPaths(libs RustLibraries) android.Paths { + var paths android.Paths + for _, lib := range libs { + paths = append(paths, lib.Path) + } + return paths +} + +func transformSrctoCrate(ctx android.ModuleContext, main android.Path, + rlibs, dylibs, proc_macros RustLibraries, static_libs, shared_libs android.Paths, flags Flags, outputFile android.WritablePath, crate_type string, includeDirs []string, targetTriple string) { + + var inputs android.Paths + var deps android.Paths + var libFlags, rustcFlags []string + crate_name := ctx.(ModuleContext).CrateName() + + inputs = append(inputs, main) + + // Collect rustc flags + rustcFlags = append(rustcFlags, flags.GlobalFlags...) + rustcFlags = append(rustcFlags, flags.RustFlags...) + rustcFlags = append(rustcFlags, "--crate-type="+crate_type) + rustcFlags = append(rustcFlags, "--crate-name="+crate_name) + if targetTriple != "" { + rustcFlags = append(rustcFlags, "--target="+targetTriple) + } + + // Collect library/crate flags + for _, lib := range rlibs { + libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String()) + } + for _, lib := range dylibs { + libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String()) + } + for _, proc_macro := range proc_macros { + libFlags = append(libFlags, "--extern "+proc_macro.CrateName+"="+proc_macro.Path.String()) + } + + for _, path := range includeDirs { + libFlags = append(libFlags, "-L "+path) + } + + // Collect dependencies + deps = append(deps, rustLibsToPaths(rlibs)...) + deps = append(deps, rustLibsToPaths(dylibs)...) + deps = append(deps, rustLibsToPaths(proc_macros)...) + deps = append(deps, static_libs...) + deps = append(deps, shared_libs...) + + ctx.Build(pctx, android.BuildParams{ + Rule: rustc, + Description: "rustc " + main.Rel(), + Output: outputFile, + Inputs: inputs, + Implicits: deps, + Args: map[string]string{ + "rustcFlags": strings.Join(rustcFlags, " "), + "linkFlags": strings.Join(flags.LinkFlags, " "), + "libFlags": strings.Join(libFlags, " "), + }, + }) + +} diff --git a/rust/compiler.go b/rust/compiler.go new file mode 100644 index 000000000..87cf08b40 --- /dev/null +++ b/rust/compiler.go @@ -0,0 +1,193 @@ +// 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 ( + "fmt" + "path/filepath" + + "android/soong/android" + "android/soong/rust/config" +) + +func NewBaseCompiler(dir, dir64 string) *baseCompiler { + return &baseCompiler{ + Properties: BaseCompilerProperties{ + Edition: &config.DefaultEdition, + }, + dir: dir, + dir64: dir64, + } +} + +type BaseCompilerProperties struct { + // flags to pass to rustc + Flags []string `android:"path,arch_variant"` + + // flags to pass to the linker + Ld_flags []string `android:"path,arch_variant"` + + // list of rust rlib crate dependencies + Rlibs []string `android:"arch_variant"` + + // list of rust dylib crate dependencies + Dylibs []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 + Static_libs []string `android:"arch_variant"` + + // crate name (defaults to module name); if library, this must be the expected extern crate name + Crate_name string `android:"arch_variant"` + + // list of features to enable for this crate + Features []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"` +} + +type baseCompiler struct { + Properties BaseCompilerProperties + pathDeps android.Paths + rustFlagsDeps android.Paths + linkFlagsDeps android.Paths + flags string + linkFlags string + depFlags []string + linkDirs []string + edition string + src android.Path //rustc takes a single src file + + // Install related + dir string + dir64 string + subDir string + relative string + path android.OutputPath +} + +var _ compiler = (*baseCompiler)(nil) + +func (compiler *baseCompiler) compilerProps() []interface{} { + return []interface{}{&compiler.Properties} +} + +func (compiler *baseCompiler) featuresToFlags(features []string) []string { + flags := []string{} + for _, feature := range features { + flags = append(flags, "--cfg 'feature=\""+feature+"\"'") + } + return flags +} + +func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags) Flags { + + flags.RustFlags = append(flags.RustFlags, compiler.Properties.Flags...) + flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags(compiler.Properties.Features)...) + flags.RustFlags = append(flags.RustFlags, "--edition="+*compiler.Properties.Edition) + flags.LinkFlags = append(flags.LinkFlags, compiler.Properties.Ld_flags...) + flags.GlobalFlags = append(flags.GlobalFlags, ctx.toolchain().ToolchainRustFlags()) + + if ctx.Host() && !ctx.Windows() { + rpath_prefix := `\$$ORIGIN/` + if ctx.Darwin() { + rpath_prefix = "@loader_path/" + } + + var rpath string + if ctx.toolchain().Is64Bit() { + rpath = "lib64" + } else { + rpath = "lib" + } + flags.LinkFlags = append(flags.LinkFlags, "-Wl,-rpath,"+rpath_prefix+rpath) + flags.LinkFlags = append(flags.LinkFlags, "-Wl,-rpath,"+rpath_prefix+"../"+rpath) + } + + return flags +} + +func (compiler *baseCompiler) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path { + panic(fmt.Errorf("baseCrater doesn't know how to crate things!")) +} + +func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps { + deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs...) + deps.Dylibs = append(deps.Dylibs, compiler.Properties.Dylibs...) + deps.ProcMacros = append(deps.ProcMacros, compiler.Properties.Proc_macros...) + deps.StaticLibs = append(deps.StaticLibs, compiler.Properties.Static_libs...) + deps.SharedLibs = append(deps.SharedLibs, compiler.Properties.Shared_libs...) + + return deps +} + +func (compiler *baseCompiler) crateName() string { + return compiler.Properties.Crate_name +} + +func (compiler *baseCompiler) installDir(ctx ModuleContext) android.OutputPath { + dir := compiler.dir + if ctx.toolchain().Is64Bit() && compiler.dir64 != "" { + dir = compiler.dir64 + } + if (!ctx.Host() && !ctx.Arch().Native) || ctx.Target().NativeBridge == android.NativeBridgeEnabled { + dir = filepath.Join(dir, ctx.Arch().ArchType.String()) + } + return android.PathForModuleInstall(ctx, dir, compiler.subDir, + compiler.relativeInstallPath(), compiler.relative) +} + +func (compiler *baseCompiler) install(ctx ModuleContext, file android.Path) { + compiler.path = ctx.InstallFile(compiler.installDir(ctx), file.Base(), file) +} + +func (compiler *baseCompiler) getStem(ctx ModuleContext) string { + return compiler.getStemWithoutSuffix(ctx) + String(compiler.Properties.Suffix) +} + +func (compiler *baseCompiler) getStemWithoutSuffix(ctx BaseModuleContext) string { + stem := ctx.baseModuleName() + 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 srcPathFromModuleSrcs(ctx ModuleContext, srcs []string) android.Path { + srcPaths := android.PathsForModuleSrc(ctx, srcs) + if len(srcPaths) != 1 { + ctx.PropertyErrorf("srcs", "srcs can only contain one path for rust modules") + } + return srcPaths[0] +} diff --git a/rust/compiler_test.go b/rust/compiler_test.go new file mode 100644 index 000000000..536909641 --- /dev/null +++ b/rust/compiler_test.go @@ -0,0 +1,77 @@ +// 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 ( + "strings" + "testing" +) + +// Test that feature flags are being correctly generated. +func TestFeaturesToFlags(t *testing.T) { + ctx := testRust(t, ` + rust_library_host_dylib { + name: "libfoo", + srcs: ["foo.rs"], + crate_name: "foo", + features: [ + "fizz", + "buzz" + ], + }`) + + libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc") + + if !strings.Contains(libfooDylib.Args["rustcFlags"], "cfg 'feature=\"fizz\"'") || + !strings.Contains(libfooDylib.Args["rustcFlags"], "cfg 'feature=\"buzz\"'") { + t.Fatalf("missing fizz and buzz feature flags for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"]) + } +} + +// Test that we reject multiple source files. +func TestEnforceSingleSourceFile(t *testing.T) { + + singleSrcError := "srcs can only contain one path for rust modules" + + // Test libraries + testRustError(t, singleSrcError, ` + rust_library_host { + name: "foo-bar-library", + srcs: ["foo.rs", "src/bar.rs"], + }`) + + // Test binaries + testRustError(t, singleSrcError, ` + rust_binary_host { + name: "foo-bar-binary", + srcs: ["foo.rs", "src/bar.rs"], + }`) + + // Test proc_macros + testRustError(t, singleSrcError, ` + rust_proc_macro { + name: "foo-bar-proc-macro", + srcs: ["foo.rs", "src/bar.rs"], + host_supported: true, + }`) + + // Test prebuilts + testRustError(t, singleSrcError, ` + rust_prebuilt_dylib { + name: "foo-bar-prebuilt", + srcs: ["liby.so", "libz.so"], + host_supported: true, + }`) +} diff --git a/rust/config/global.go b/rust/config/global.go new file mode 100644 index 000000000..2e08a8cf3 --- /dev/null +++ b/rust/config/global.go @@ -0,0 +1,65 @@ +// 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 config + +import ( + "android/soong/android" + _ "android/soong/cc/config" +) + +var pctx = android.NewPackageContext("android/soong/rust/config") + +var ( + RustDefaultVersion = "1.35.0" + RustDefaultBase = "prebuilts/rust/" + DefaultEdition = "2018" + Stdlibs = []string{ + "libarena", + "libfmt_macros", + "libgraphviz", + "libserialize", + "libstd", + "libsyntax", + "libsyntax_ext", + "libsyntax_pos", + "libterm", + } +) + +func init() { + pctx.SourcePathVariable("RustDefaultBase", RustDefaultBase) + pctx.VariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS) + + pctx.VariableFunc("RustBase", func(ctx android.PackageVarContext) string { + if override := ctx.Config().Getenv("RUST_PREBUILTS_BASE"); override != "" { + return override + } + return "${RustDefaultBase}" + }) + + pctx.VariableFunc("RustVersion", func(ctx android.PackageVarContext) string { + if override := ctx.Config().Getenv("RUST_PREBUILTS_VERSION"); override != "" { + return override + } + return RustDefaultVersion + }) + + pctx.StaticVariable("RustPath", "${RustBase}/${HostPrebuiltTag}/${RustVersion}") + pctx.StaticVariable("RustBin", "${RustPath}/bin") + + pctx.ImportAs("ccConfig", "android/soong/cc/config") + pctx.StaticVariable("RustLinker", "${ccConfig.ClangBin}/clang++") + pctx.StaticVariable("RustLinkerArgs", "-B ${ccConfig.ClangBin} -fuse-ld=lld") +} diff --git a/rust/config/toolchain.go b/rust/config/toolchain.go new file mode 100644 index 000000000..a36d61bfd --- /dev/null +++ b/rust/config/toolchain.go @@ -0,0 +1,124 @@ +// 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 config + +import ( + "android/soong/android" +) + +type Toolchain interface { + RustTriple() string + ToolchainRustFlags() string + ToolchainLinkFlags() string + + SharedLibSuffix() string + StaticLibSuffix() string + RlibSuffix() string + DylibSuffix() string + ProcMacroSuffix() string + ExecutableSuffix() string + + Is64Bit() bool + Supported() bool +} + +type toolchainBase struct { +} + +func (toolchainBase) RustTriple() string { + panic("toolchainBase does not define a triple.") +} + +func (toolchainBase) ToolchainRustFlags() string { + panic("toolchainBase does not provide rust flags.") +} + +func (toolchainBase) ToolchainLinkFlags() string { + panic("toolchainBase does not provide link flags.") +} + +func (toolchainBase) Is64Bit() bool { + panic("toolchainBase cannot determine datapath width.") +} + +type toolchain64Bit struct { + toolchainBase +} + +func (toolchain64Bit) Is64Bit() bool { + return true +} + +type toolchain32Bit struct { + toolchainBase +} + +func (toolchain32Bit) Is64Bit() bool { + return false +} + +func (toolchain32Bit) Bionic() bool { + return true +} + +func (toolchainBase) ExecutableSuffix() string { + return "" +} + +func (toolchainBase) SharedLibSuffix() string { + return ".so" +} + +func (toolchainBase) StaticLibSuffix() string { + return ".a" +} + +func (toolchainBase) RlibSuffix() string { + return ".rlib" +} +func (toolchainBase) DylibSuffix() string { + return ".so" +} + +func (toolchainBase) ProcMacroSuffix() string { + return ".so" +} + +func (toolchainBase) Supported() bool { + return false +} + +func toolchainBaseFactory() Toolchain { + return &toolchainBase{} +} + +type toolchainFactory func(arch android.Arch) Toolchain + +var toolchainFactories = make(map[android.OsType]map[android.ArchType]toolchainFactory) + +func registerToolchainFactory(os android.OsType, arch android.ArchType, factory toolchainFactory) { + if toolchainFactories[os] == nil { + toolchainFactories[os] = make(map[android.ArchType]toolchainFactory) + } + toolchainFactories[os][arch] = factory +} + +func FindToolchain(os android.OsType, arch android.Arch) Toolchain { + factory := toolchainFactories[os][arch.ArchType] + if factory == nil { + return toolchainBaseFactory() + } + return factory(arch) +} diff --git a/rust/config/x86_64_device.go b/rust/config/x86_64_device.go new file mode 100644 index 000000000..2aca56a3d --- /dev/null +++ b/rust/config/x86_64_device.go @@ -0,0 +1,88 @@ +// 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 config + +import ( + "strings" + + "android/soong/android" +) + +var ( + x86_64RustFlags = []string{} + x86_64ArchFeatureRustFlags = map[string][]string{} + x86_64LinkFlags = []string{} + + x86_64ArchVariantRustFlags = map[string][]string{ + "": []string{}, + "broadwell": []string{"-C target-cpu=broadwell"}, + "haswell": []string{"-C target-cpu=haswell"}, + "ivybridge": []string{"-C target-cpu=ivybridge"}, + "sandybridge": []string{"-C target-cpu=sandybridge"}, + "silvermont": []string{"-C target-cpu=silvermont"}, + "skylake": []string{"-C target-cpu=skylake"}, + //TODO: Add target-cpu=stoneyridge when rustc supports it. + "stoneyridge": []string{""}, + } +) + +func init() { + registerToolchainFactory(android.Android, android.X86_64, x86_64ToolchainFactory) + + pctx.StaticVariable("x86_64ToolchainRustFlags", strings.Join(x86_64RustFlags, " ")) + pctx.StaticVariable("x86_64ToolchainLinkFlags", strings.Join(x86_64LinkFlags, " ")) + + for variant, rustFlags := range x86_64ArchVariantRustFlags { + pctx.StaticVariable("X86_64"+variant+"VariantRustFlags", + strings.Join(rustFlags, " ")) + } + +} + +type toolchainX86_64 struct { + toolchain64Bit + toolchainRustFlags string +} + +func (t *toolchainX86_64) RustTriple() string { + return "x86_64-unknown-linux-gnu" +} + +func (t *toolchainX86_64) ToolchainLinkFlags() string { + return "${config.x86_64ToolchainLinkFlags}" +} + +func (t *toolchainX86_64) ToolchainRustFlags() string { + return t.toolchainRustFlags +} + +func (t *toolchainX86_64) RustFlags() string { + return "${config.x86_64ToolchainRustFlags}" +} + +func x86_64ToolchainFactory(arch android.Arch) Toolchain { + toolchainRustFlags := []string{ + "${config.x86_64ToolchainRustFlags}", + "${config.X86_64" + arch.ArchVariant + "VariantRustFlags}", + } + + for _, feature := range arch.ArchFeatures { + toolchainRustFlags = append(toolchainRustFlags, x86_64ArchFeatureRustFlags[feature]...) + } + + return &toolchainX86_64{ + toolchainRustFlags: strings.Join(toolchainRustFlags, " "), + } +} diff --git a/rust/config/x86_linux_host.go b/rust/config/x86_linux_host.go new file mode 100644 index 000000000..cb6bf1a22 --- /dev/null +++ b/rust/config/x86_linux_host.go @@ -0,0 +1,109 @@ +// 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 config + +import ( + "strings" + + "android/soong/android" +) + +var ( + LinuxRustFlags = []string{} + LinuxRustLinkFlags = []string{} + linuxX86Rustflags = []string{} + linuxX86Linkflags = []string{} + linuxX8664Rustflags = []string{} + linuxX8664Linkflags = []string{} +) + +func init() { + registerToolchainFactory(android.Linux, android.X86_64, linuxX8664ToolchainFactory) + registerToolchainFactory(android.Linux, android.X86, linuxX86ToolchainFactory) + + pctx.StaticVariable("LinuxToolchainRustFlags", strings.Join(LinuxRustFlags, " ")) + pctx.StaticVariable("LinuxToolchainLinkFlags", strings.Join(LinuxRustLinkFlags, " ")) + pctx.StaticVariable("LinuxToolchainX86RustFlags", strings.Join(linuxX86Rustflags, " ")) + pctx.StaticVariable("LinuxToolchainX86LinkFlags", strings.Join(linuxX86Linkflags, " ")) + pctx.StaticVariable("LinuxToolchainX8664RustFlags", strings.Join(linuxX8664Rustflags, " ")) + pctx.StaticVariable("LinuxToolchainX8664LinkFlags", strings.Join(linuxX8664Linkflags, " ")) + +} + +type toolchainLinux struct { + toolchainRustFlags string + toolchainLinkFlags string +} + +type toolchainLinuxX86 struct { + toolchain32Bit + toolchainLinux +} + +type toolchainLinuxX8664 struct { + toolchain64Bit + toolchainLinux +} + +func (toolchainLinuxX8664) Supported() bool { + return true +} + +func (t *toolchainLinuxX8664) Name() string { + return "x86_64" +} + +func (t *toolchainLinuxX8664) RustTriple() string { + return "x86_64-unknown-linux-gnu" +} + +func (t *toolchainLinuxX8664) ToolchainLinkFlags() string { + return "${config.LinuxToolchainLinkFlags} ${config.LinuxToolchainX8664LinkFlags}" +} + +func (t *toolchainLinuxX8664) ToolchainRustFlags() string { + return "${config.LinuxToolchainRustFlags} ${config.LinuxToolchainX8664RustFlags}" +} + +func linuxX8664ToolchainFactory(arch android.Arch) Toolchain { + return toolchainLinuxX8664Singleton +} + +func (toolchainLinuxX86) Supported() bool { + return true +} + +func (t *toolchainLinuxX86) Name() string { + return "x86" +} + +func (t *toolchainLinuxX86) RustTriple() string { + return "i686-unknown-linux-gnu" +} + +func (t *toolchainLinuxX86) ToolchainLinkFlags() string { + return "${config.LinuxToolchainLinkFlags} ${config.LinuxToolchainX86LinkFlags}" +} + +func (t *toolchainLinuxX86) ToolchainRustFlags() string { + return "${config.LinuxToolchainRustFlags} ${config.LinuxToolchainX86RustFlags}" +} + +func linuxX86ToolchainFactory(arch android.Arch) Toolchain { + return toolchainLinuxX86Singleton +} + +var toolchainLinuxX8664Singleton Toolchain = &toolchainLinuxX8664{} +var toolchainLinuxX86Singleton Toolchain = &toolchainLinuxX86{} diff --git a/rust/library.go b/rust/library.go new file mode 100644 index 000000000..5cf8ac700 --- /dev/null +++ b/rust/library.go @@ -0,0 +1,245 @@ +// 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/android" +) + +func init() { + android.RegisterModuleType("rust_library", RustLibraryFactory) + android.RegisterModuleType("rust_library_dylib", RustLibraryDylibFactory) + android.RegisterModuleType("rust_library_rlib", RustLibraryRlibFactory) + android.RegisterModuleType("rust_library_host", RustLibraryHostFactory) + android.RegisterModuleType("rust_library_host_dylib", RustLibraryDylibHostFactory) + android.RegisterModuleType("rust_library_host_rlib", RustLibraryRlibHostFactory) + + //TODO: Add support for generating standard shared/static libraries. +} + +type VariantLibraryProperties struct { + Enabled *bool `android:"arch_variant"` +} + +type LibraryCompilerProperties struct { + Rlib VariantLibraryProperties `android:"arch_variant"` + Dylib VariantLibraryProperties `android:"arch_variant"` + + // path to the source file that is the main entry point of the program (e.g. src/lib.rs) + Srcs []string `android:"path,arch_variant"` +} + +type LibraryMutatedProperties struct { + VariantName string `blueprint:"mutated"` + + // Build a dylib variant + BuildDylib bool `blueprint:"mutated"` + // Build an rlib variant + BuildRlib bool `blueprint:"mutated"` + + // This variant is a dylib + VariantIsDylib bool `blueprint:"mutated"` + // This variant is an rlib + VariantIsRlib bool `blueprint:"mutated"` +} + +type libraryDecorator struct { + *baseCompiler + + Properties LibraryCompilerProperties + MutatedProperties LibraryMutatedProperties + distFile android.OptionalPath + unstrippedOutputFile android.Path +} + +type libraryInterface interface { + rlib() bool + dylib() bool + + // Returns true if the build options for the module have selected a particular build type + buildRlib() bool + buildDylib() bool + + // Sets a particular variant type + setRlib() + setDylib() +} + +func (library *libraryDecorator) exportedDirs() []string { + return library.linkDirs +} + +func (library *libraryDecorator) exportedDepFlags() []string { + return library.depFlags +} + +func (library *libraryDecorator) reexportDirs(dirs ...string) { + library.linkDirs = android.FirstUniqueStrings(append(library.linkDirs, dirs...)) +} + +func (library *libraryDecorator) reexportDepFlags(flags ...string) { + library.depFlags = android.FirstUniqueStrings(append(library.depFlags, flags...)) +} + +func (library *libraryDecorator) rlib() bool { + return library.MutatedProperties.VariantIsRlib +} + +func (library *libraryDecorator) dylib() bool { + return library.MutatedProperties.VariantIsDylib +} + +func (library *libraryDecorator) buildRlib() bool { + return library.MutatedProperties.BuildRlib && BoolDefault(library.Properties.Rlib.Enabled, true) +} + +func (library *libraryDecorator) buildDylib() bool { + return library.MutatedProperties.BuildDylib && BoolDefault(library.Properties.Dylib.Enabled, true) +} + +func (library *libraryDecorator) setRlib() { + library.MutatedProperties.VariantIsRlib = true + library.MutatedProperties.VariantIsDylib = false +} + +func (library *libraryDecorator) setDylib() { + library.MutatedProperties.VariantIsRlib = false + library.MutatedProperties.VariantIsDylib = true +} + +var _ compiler = (*libraryDecorator)(nil) + +// rust_library produces all variants. +func RustLibraryFactory() android.Module { + module, _ := NewRustLibrary(android.HostAndDeviceSupported) + return module.Init() +} + +// rust_library_dylib produces a dylib. +func RustLibraryDylibFactory() android.Module { + module, library := NewRustLibrary(android.HostAndDeviceSupported) + library.BuildOnlyDylib() + return module.Init() +} + +// rust_library_rlib produces an rlib. +func RustLibraryRlibFactory() android.Module { + module, library := NewRustLibrary(android.HostAndDeviceSupported) + library.BuildOnlyRlib() + return module.Init() +} + +// rust_library_host produces all variants. +func RustLibraryHostFactory() android.Module { + module, _ := NewRustLibrary(android.HostSupported) + return module.Init() +} + +// rust_library_dylib_host produces a dylib. +func RustLibraryDylibHostFactory() android.Module { + module, library := NewRustLibrary(android.HostSupported) + library.BuildOnlyDylib() + return module.Init() +} + +// rust_library_rlib_host produces an rlib. +func RustLibraryRlibHostFactory() android.Module { + module, library := NewRustLibrary(android.HostSupported) + library.BuildOnlyRlib() + return module.Init() +} + +func (library *libraryDecorator) BuildOnlyDylib() { + library.MutatedProperties.BuildRlib = false +} + +func (library *libraryDecorator) BuildOnlyRlib() { + library.MutatedProperties.BuildDylib = false +} + +func NewRustLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) { + module := newModule(hod, android.MultilibFirst) + + library := &libraryDecorator{ + MutatedProperties: LibraryMutatedProperties{ + BuildDylib: true, + BuildRlib: true, + }, + baseCompiler: NewBaseCompiler("lib", "lib64"), + } + + module.compiler = library + + return module, library +} + +func (library *libraryDecorator) compilerProps() []interface{} { + return append(library.baseCompiler.compilerProps(), + &library.Properties, + &library.MutatedProperties) +} + +func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path { + var outputFile android.WritablePath + + srcPath := srcPathFromModuleSrcs(ctx, library.Properties.Srcs) + + flags.RustFlags = append(flags.RustFlags, deps.depFlags...) + + if library.rlib() { + fileName := library.getStem(ctx) + ctx.toolchain().RlibSuffix() + outputFile = android.PathForModuleOut(ctx, fileName) + + TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + } else if library.dylib() { + fileName := library.getStem(ctx) + ctx.toolchain().DylibSuffix() + outputFile = android.PathForModuleOut(ctx, fileName) + + // We need prefer-dynamic for now to avoid linking in the static stdlib. See: + // https://github.com/rust-lang/rust/issues/19680 + // https://github.com/rust-lang/rust/issues/34909 + flags.RustFlags = append(flags.RustFlags, "-C prefer-dynamic") + + TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + } + + library.reexportDirs(deps.linkDirs...) + library.reexportDepFlags(deps.depFlags...) + library.unstrippedOutputFile = outputFile + + return outputFile +} + +func LibraryMutator(mctx android.BottomUpMutatorContext) { + if m, ok := mctx.Module().(*Module); ok && m.compiler != nil { + switch library := m.compiler.(type) { + case libraryInterface: + if library.buildRlib() && library.buildDylib() { + modules := mctx.CreateLocalVariations("rlib", "dylib") + rlib := modules[0].(*Module) + dylib := modules[1].(*Module) + + rlib.compiler.(libraryInterface).setRlib() + dylib.compiler.(libraryInterface).setDylib() + } else if library.buildRlib() { + modules := mctx.CreateLocalVariations("rlib") + modules[0].(*Module).compiler.(libraryInterface).setRlib() + } else if library.buildDylib() { + modules := mctx.CreateLocalVariations("dylib") + modules[0].(*Module).compiler.(libraryInterface).setDylib() + } + } + } +} diff --git a/rust/library_test.go b/rust/library_test.go new file mode 100644 index 000000000..bf8643efb --- /dev/null +++ b/rust/library_test.go @@ -0,0 +1,61 @@ +// 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 ( + "strings" + "testing" +) + +// Test that variants are being generated correctly, and that crate-types are correct. +func TestLibraryVariants(t *testing.T) { + + ctx := testRust(t, ` + rust_library_host { + name: "libfoo", + srcs: ["foo.rs"], + crate_name: "foo", + }`) + + // Test both variants are being built. + libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib").Output("libfoo.rlib") + libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Output("libfoo.so") + + // Test crate type for rlib is correct. + if !strings.Contains(libfooRlib.Args["rustcFlags"], "crate-type=rlib") { + t.Errorf("missing crate-type for libfoo rlib, rustcFlags: %#v", libfooRlib.Args["rustcFlags"]) + } + + // Test crate type for dylib is correct. + if !strings.Contains(libfooDylib.Args["rustcFlags"], "crate-type=dylib") { + t.Errorf("missing crate-type for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"]) + } +} + +// Test that dylibs are not statically linking the standard library. +func TestDylibPreferDynamic(t *testing.T) { + ctx := testRust(t, ` + rust_library_host_dylib { + name: "libfoo", + srcs: ["foo.rs"], + crate_name: "foo", + }`) + + libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Output("libfoo.so") + + if !strings.Contains(libfooDylib.Args["rustcFlags"], "prefer-dynamic") { + t.Errorf("missing prefer-dynamic flag for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"]) + } +} diff --git a/rust/prebuilt.go b/rust/prebuilt.go new file mode 100644 index 000000000..d4e631ba6 --- /dev/null +++ b/rust/prebuilt.go @@ -0,0 +1,65 @@ +// 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/android" +) + +func init() { + android.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory) +} + +type PrebuiltProperties struct { + // path to the prebuilt file + Srcs []string `android:"path,arch_variant"` +} + +type prebuiltLibraryDecorator struct { + *libraryDecorator + Properties PrebuiltProperties +} + +var _ compiler = (*prebuiltLibraryDecorator)(nil) + +func PrebuiltDylibFactory() android.Module { + module, _ := NewPrebuiltDylib(android.HostAndDeviceSupported) + return module.Init() +} + +func NewPrebuiltDylib(hod android.HostOrDeviceSupported) (*Module, *prebuiltLibraryDecorator) { + module, library := NewRustLibrary(hod) + library.BuildOnlyDylib() + library.setDylib() + prebuilt := &prebuiltLibraryDecorator{ + libraryDecorator: library, + } + module.compiler = prebuilt + module.AddProperties(&library.Properties) + return module, prebuilt +} + +func (prebuilt *prebuiltLibraryDecorator) compilerProps() []interface{} { + return append(prebuilt.baseCompiler.compilerProps(), + &prebuilt.Properties) +} + +func (prebuilt *prebuiltLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path { + srcPath := srcPathFromModuleSrcs(ctx, prebuilt.Properties.Srcs) + + prebuilt.unstrippedOutputFile = srcPath + + return srcPath +} diff --git a/rust/proc_macro.go b/rust/proc_macro.go new file mode 100644 index 000000000..4acb06fcd --- /dev/null +++ b/rust/proc_macro.go @@ -0,0 +1,79 @@ +// 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/android" +) + +func init() { + android.RegisterModuleType("rust_proc_macro", ProcMacroFactory) +} + +type ProcMacroCompilerProperties struct { + // path to the source file that is the main entry point of the program (e.g. src/lib.rs) + Srcs []string `android:"path,arch_variant"` + + // set name of the procMacro + Stem *string `android:"arch_variant"` + Suffix *string `android:"arch_variant"` +} + +type procMacroDecorator struct { + *baseCompiler + + Properties ProcMacroCompilerProperties + distFile android.OptionalPath + unstrippedOutputFile android.Path +} + +type procMacroInterface interface { +} + +var _ compiler = (*procMacroDecorator)(nil) + +func ProcMacroFactory() android.Module { + module, _ := NewProcMacro(android.HostAndDeviceSupported) + return module.Init() +} + +func NewProcMacro(hod android.HostOrDeviceSupported) (*Module, *procMacroDecorator) { + module := newModule(hod, android.MultilibFirst) + + procMacro := &procMacroDecorator{ + baseCompiler: NewBaseCompiler("lib", "lib64"), + } + + module.compiler = procMacro + + return module, procMacro +} + +func (procMacro *procMacroDecorator) compilerProps() []interface{} { + return append(procMacro.baseCompiler.compilerProps(), + &procMacro.Properties) +} + +func (procMacro *procMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path { + fileName := procMacro.getStem(ctx) + ctx.toolchain().ProcMacroSuffix() + outputFile := android.PathForModuleOut(ctx, fileName) + + srcPath := srcPathFromModuleSrcs(ctx, procMacro.Properties.Srcs) + + procMacro.unstrippedOutputFile = outputFile + + TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile, deps.linkDirs) + return outputFile +} diff --git a/rust/rust.go b/rust/rust.go new file mode 100644 index 000000000..5a2514e47 --- /dev/null +++ b/rust/rust.go @@ -0,0 +1,498 @@ +// 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 ( + "strings" + + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" + + "android/soong/android" + "android/soong/cc" + "android/soong/rust/config" +) + +var pctx = android.NewPackageContext("android/soong/rust") + +func init() { + // Only allow rust modules to be defined for certain projects + rustModuleTypes := []string{ + "rust_binary", + "rust_binary_host", + "rust_library", + "rust_library_dylib", + "rust_library_rlib", + "rust_library_host", + "rust_library_host_dylib", + "rust_library_host_rlib", + "rust_proc_macro", + } + + rustAllowedPaths := []string{ + "external/rust/crates", + "external/crosvm", + "external/adhd", + } + + android.AddNeverAllowRules( + android.NeverAllow(). + NotIn(rustAllowedPaths...). + ModuleType(rustModuleTypes...)) + + android.RegisterModuleType("rust_defaults", defaultsFactory) + android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("rust_libraries", LibraryMutator).Parallel() + }) + pctx.Import("android/soong/rust/config") +} + +type Flags struct { + GlobalFlags []string // Flags that apply globally + RustFlags []string // Flags that apply to rust + LinkFlags []string // Flags that apply to linker + RustFlagsDeps android.Paths // Files depended on by compiler flags + Toolchain config.Toolchain +} + +type BaseProperties struct { + AndroidMkRlibs []string + AndroidMkDylibs []string + AndroidMkProcMacroLibs []string + AndroidMkSharedLibs []string + AndroidMkStaticLibs []string +} + +type Module struct { + android.ModuleBase + android.DefaultableModuleBase + + Properties BaseProperties + + hod android.HostOrDeviceSupported + multilib android.Multilib + + compiler compiler + cachedToolchain config.Toolchain + subAndroidMkOnce map[subAndroidMkProvider]bool + outputFile android.OptionalPath +} + +type Deps struct { + Dylibs []string + Rlibs []string + ProcMacros []string + SharedLibs []string + StaticLibs []string + + CrtBegin, CrtEnd string +} + +type PathDeps struct { + DyLibs RustLibraries + RLibs RustLibraries + SharedLibs android.Paths + StaticLibs android.Paths + ProcMacros RustLibraries + linkDirs []string + depFlags []string + //ReexportedDeps android.Paths +} + +type RustLibraries []RustLibrary + +type RustLibrary struct { + Path android.Path + CrateName string +} + +type compiler interface { + compilerFlags(ctx ModuleContext, flags Flags) Flags + compilerProps() []interface{} + compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path + compilerDeps(ctx DepsContext, deps Deps) Deps + crateName() string + + install(ctx ModuleContext, path android.Path) + relativeInstallPath() string +} + +func defaultsFactory() android.Module { + return DefaultsFactory() +} + +type Defaults struct { + android.ModuleBase + android.DefaultsModuleBase +} + +func DefaultsFactory(props ...interface{}) android.Module { + module := &Defaults{} + + module.AddProperties(props...) + module.AddProperties( + &BaseProperties{}, + &BaseCompilerProperties{}, + &BinaryCompilerProperties{}, + &LibraryCompilerProperties{}, + &ProcMacroCompilerProperties{}, + &PrebuiltProperties{}, + ) + + android.InitDefaultsModule(module) + return module +} + +func (mod *Module) CrateName() string { + if mod.compiler != nil && mod.compiler.crateName() != "" { + return mod.compiler.crateName() + } + // Default crate names replace '-' in the name to '_' + return strings.Replace(mod.BaseModuleName(), "-", "_", -1) +} + +func (mod *Module) Init() android.Module { + mod.AddProperties(&mod.Properties) + + if mod.compiler != nil { + mod.AddProperties(mod.compiler.compilerProps()...) + } + android.InitAndroidArchModule(mod, mod.hod, mod.multilib) + + android.InitDefaultableModule(mod) + + return mod +} + +func newBaseModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module { + return &Module{ + hod: hod, + multilib: multilib, + } +} +func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module { + module := newBaseModule(hod, multilib) + return module +} + +type ModuleContext interface { + android.ModuleContext + ModuleContextIntf +} + +type BaseModuleContext interface { + android.BaseModuleContext + ModuleContextIntf +} + +type DepsContext interface { + android.BottomUpMutatorContext + ModuleContextIntf +} + +type ModuleContextIntf interface { + toolchain() config.Toolchain + baseModuleName() string + CrateName() string +} + +type depsContext struct { + android.BottomUpMutatorContext + moduleContextImpl +} + +type moduleContext struct { + android.ModuleContext + moduleContextImpl +} + +type moduleContextImpl struct { + mod *Module + ctx BaseModuleContext +} + +func (ctx *moduleContextImpl) toolchain() config.Toolchain { + return ctx.mod.toolchain(ctx.ctx) +} + +func (mod *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain { + if mod.cachedToolchain == nil { + mod.cachedToolchain = config.FindToolchain(ctx.Os(), ctx.Arch()) + } + return mod.cachedToolchain +} + +func (d *Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { +} + +func (mod *Module) GenerateAndroidBuildActions(actx android.ModuleContext) { + ctx := &moduleContext{ + ModuleContext: actx, + moduleContextImpl: moduleContextImpl{ + mod: mod, + }, + } + ctx.ctx = ctx + + toolchain := mod.toolchain(ctx) + + if !toolchain.Supported() { + // This toolchain's unsupported, there's nothing to do for this mod. + return + } + + deps := mod.depsToPaths(ctx) + flags := Flags{ + Toolchain: toolchain, + } + + if mod.compiler != nil { + flags = mod.compiler.compilerFlags(ctx, flags) + outputFile := mod.compiler.compile(ctx, flags, deps) + mod.outputFile = android.OptionalPathForPath(outputFile) + mod.compiler.install(ctx, mod.outputFile.Path()) + } +} + +func (mod *Module) deps(ctx DepsContext) Deps { + deps := Deps{} + + if mod.compiler != nil { + deps = mod.compiler.compilerDeps(ctx, deps) + } + + deps.Rlibs = android.LastUniqueStrings(deps.Rlibs) + deps.Dylibs = android.LastUniqueStrings(deps.Dylibs) + deps.ProcMacros = android.LastUniqueStrings(deps.ProcMacros) + deps.SharedLibs = android.LastUniqueStrings(deps.SharedLibs) + deps.StaticLibs = android.LastUniqueStrings(deps.StaticLibs) + + return deps + +} + +func (ctx *moduleContextImpl) baseModuleName() string { + return ctx.mod.ModuleBase.BaseModuleName() +} + +func (ctx *moduleContextImpl) CrateName() string { + return ctx.mod.CrateName() +} + +type dependencyTag struct { + blueprint.BaseDependencyTag + name string + library bool + proc_macro bool +} + +var ( + rlibDepTag = dependencyTag{name: "rlibTag", library: true} + dylibDepTag = dependencyTag{name: "dylib", library: true} + procMacroDepTag = dependencyTag{name: "procMacro", proc_macro: true} +) + +func (mod *Module) depsToPaths(ctx android.ModuleContext) PathDeps { + var depPaths PathDeps + + directRlibDeps := []*Module{} + directDylibDeps := []*Module{} + directProcMacroDeps := []*Module{} + directSharedLibDeps := []*(cc.Module){} + directStaticLibDeps := []*(cc.Module){} + + ctx.VisitDirectDeps(func(dep android.Module) { + depName := ctx.OtherModuleName(dep) + depTag := ctx.OtherModuleDependencyTag(dep) + if dep.Target().Os != ctx.Os() { + ctx.ModuleErrorf("OS mismatch between %q and %q", ctx.ModuleName(), depName) + return + } + if dep.Target().Arch.ArchType != ctx.Arch().ArchType { + ctx.ModuleErrorf("Arch mismatch between %q and %q", ctx.ModuleName(), depName) + return + } + + if rustDep, ok := dep.(*Module); ok { + //Handle Rust Modules + linkFile := rustDep.outputFile + if !linkFile.Valid() { + ctx.ModuleErrorf("Invalid output file when adding dep %q to %q", depName, ctx.ModuleName()) + } + + switch depTag { + case dylibDepTag: + dylib, ok := rustDep.compiler.(libraryInterface) + if !ok || !dylib.dylib() { + ctx.ModuleErrorf("mod %q not an dylib library", depName) + return + } + directDylibDeps = append(directDylibDeps, rustDep) + mod.Properties.AndroidMkDylibs = append(mod.Properties.AndroidMkDylibs, depName) + case rlibDepTag: + rlib, ok := rustDep.compiler.(libraryInterface) + if !ok || !rlib.rlib() { + ctx.ModuleErrorf("mod %q not an rlib library", depName) + return + } + directRlibDeps = append(directRlibDeps, rustDep) + mod.Properties.AndroidMkRlibs = append(mod.Properties.AndroidMkRlibs, depName) + case procMacroDepTag: + directProcMacroDeps = append(directProcMacroDeps, rustDep) + mod.Properties.AndroidMkProcMacroLibs = append(mod.Properties.AndroidMkProcMacroLibs, depName) + } + + //Append the dependencies exportedDirs + if lib, ok := rustDep.compiler.(*libraryDecorator); ok { + depPaths.linkDirs = append(depPaths.linkDirs, lib.exportedDirs()...) + depPaths.depFlags = append(depPaths.depFlags, lib.exportedDepFlags()...) + } else if procMacro, ok := rustDep.compiler.(*libraryDecorator); ok { + depPaths.linkDirs = append(depPaths.linkDirs, procMacro.exportedDirs()...) + depPaths.depFlags = append(depPaths.depFlags, procMacro.exportedDepFlags()...) + } + + // Append this dependencies output to this mod's linkDirs so they can be exported to dependencies + // This can be probably be refactored by defining a common exporter interface similar to cc's + if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag { + linkDir := linkPathFromFilePath(linkFile.Path()) + if lib, ok := mod.compiler.(*libraryDecorator); ok { + lib.linkDirs = append(lib.linkDirs, linkDir) + } else if procMacro, ok := mod.compiler.(*procMacroDecorator); ok { + procMacro.linkDirs = append(procMacro.linkDirs, linkDir) + } + } + + } else if ccDep, ok := dep.(*cc.Module); ok { + + //Handle C dependencies + linkFile := ccDep.OutputFile() + linkPath := linkPathFromFilePath(linkFile.Path()) + libName := libNameFromFilePath(linkFile.Path()) + if !linkFile.Valid() { + ctx.ModuleErrorf("Invalid output file when adding dep %q to %q", depName, ctx.ModuleName()) + } + + exportDep := false + + switch depTag { + case cc.StaticDepTag(): + depPaths.linkDirs = append(depPaths.linkDirs, linkPath) + depPaths.depFlags = append(depPaths.depFlags, "-l"+libName) + directStaticLibDeps = append(directStaticLibDeps, ccDep) + mod.Properties.AndroidMkStaticLibs = append(mod.Properties.AndroidMkStaticLibs, depName) + case cc.SharedDepTag(): + depPaths.linkDirs = append(depPaths.linkDirs, linkPath) + depPaths.depFlags = append(depPaths.depFlags, "-l"+libName) + directSharedLibDeps = append(directSharedLibDeps, ccDep) + mod.Properties.AndroidMkSharedLibs = append(mod.Properties.AndroidMkSharedLibs, depName) + exportDep = true + } + + // Make sure these dependencies are propagated + if lib, ok := mod.compiler.(*libraryDecorator); ok && (exportDep || lib.rlib()) { + lib.linkDirs = append(lib.linkDirs, linkPath) + lib.depFlags = append(lib.depFlags, "-l"+libName) + } else if procMacro, ok := mod.compiler.(*procMacroDecorator); ok && exportDep { + procMacro.linkDirs = append(procMacro.linkDirs, linkPath) + procMacro.depFlags = append(procMacro.depFlags, "-l"+libName) + } + + } + }) + + var rlibDepFiles RustLibraries + for _, dep := range directRlibDeps { + rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()}) + } + var dylibDepFiles RustLibraries + for _, dep := range directDylibDeps { + dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()}) + } + var procMacroDepFiles RustLibraries + for _, dep := range directProcMacroDeps { + procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()}) + } + + var staticLibDepFiles android.Paths + for _, dep := range directStaticLibDeps { + staticLibDepFiles = append(staticLibDepFiles, dep.OutputFile().Path()) + } + + var sharedLibDepFiles android.Paths + for _, dep := range directSharedLibDeps { + sharedLibDepFiles = append(sharedLibDepFiles, dep.OutputFile().Path()) + } + + depPaths.RLibs = append(depPaths.RLibs, rlibDepFiles...) + depPaths.DyLibs = append(depPaths.DyLibs, dylibDepFiles...) + depPaths.SharedLibs = append(depPaths.SharedLibs, sharedLibDepFiles...) + depPaths.StaticLibs = append(depPaths.StaticLibs, staticLibDepFiles...) + depPaths.ProcMacros = append(depPaths.ProcMacros, procMacroDepFiles...) + + // Dedup exported flags from dependencies + depPaths.linkDirs = android.FirstUniqueStrings(depPaths.linkDirs) + depPaths.depFlags = android.FirstUniqueStrings(depPaths.depFlags) + + return depPaths +} + +func linkPathFromFilePath(filepath android.Path) string { + return strings.Split(filepath.String(), filepath.Base())[0] +} +func libNameFromFilePath(filepath android.Path) string { + libName := strings.Split(filepath.Base(), filepath.Ext())[0] + if strings.Contains(libName, "lib") { + libName = strings.Split(libName, "lib")[1] + } + return libName +} +func (mod *Module) DepsMutator(actx android.BottomUpMutatorContext) { + ctx := &depsContext{ + BottomUpMutatorContext: actx, + moduleContextImpl: moduleContextImpl{ + mod: mod, + }, + } + ctx.ctx = ctx + + deps := mod.deps(ctx) + + actx.AddVariationDependencies([]blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}}, rlibDepTag, deps.Rlibs...) + actx.AddVariationDependencies([]blueprint.Variation{{Mutator: "rust_libraries", Variation: "dylib"}}, dylibDepTag, deps.Dylibs...) + + ccDepVariations := []blueprint.Variation{} + ccDepVariations = append(ccDepVariations, blueprint.Variation{Mutator: "version", Variation: ""}) + if !mod.Host() { + ccDepVariations = append(ccDepVariations, blueprint.Variation{Mutator: "image", Variation: "core"}) + } + actx.AddVariationDependencies(append(ccDepVariations, blueprint.Variation{Mutator: "link", Variation: "shared"}), cc.SharedDepTag(), deps.SharedLibs...) + actx.AddVariationDependencies(append(ccDepVariations, blueprint.Variation{Mutator: "link", Variation: "static"}), cc.StaticDepTag(), deps.StaticLibs...) + actx.AddDependency(mod, procMacroDepTag, deps.ProcMacros...) +} + +func (mod *Module) Name() string { + name := mod.ModuleBase.Name() + if p, ok := mod.compiler.(interface { + Name(string) string + }); ok { + name = p.Name(name) + } + return name +} + +var Bool = proptools.Bool +var BoolDefault = proptools.BoolDefault +var String = proptools.String +var StringPtr = proptools.StringPtr diff --git a/rust/rust_test.go b/rust/rust_test.go new file mode 100644 index 000000000..c68cfe7c9 --- /dev/null +++ b/rust/rust_test.go @@ -0,0 +1,167 @@ +// 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 ( + "io/ioutil" + "os" + "testing" + + "android/soong/android" +) + +var ( + buildDir string +) + +func setUp() { + var err error + buildDir, err = ioutil.TempDir("", "soong_rust_test") + if err != nil { + panic(err) + } +} + +func tearDown() { + os.RemoveAll(buildDir) +} + +func TestMain(m *testing.M) { + run := func() int { + setUp() + defer tearDown() + + return m.Run() + } + + os.Exit(run()) +} + +func testRust(t *testing.T, bp string) *android.TestContext { + t.Helper() + config := android.TestArchConfig(buildDir, nil) + + t.Helper() + ctx := CreateTestContext(bp) + ctx.Register() + + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + android.FailIfErrored(t, errs) + _, errs = ctx.PrepareBuildActions(config) + android.FailIfErrored(t, errs) + + return ctx +} + +func testRustError(t *testing.T, pattern string, bp string) { + t.Helper() + config := android.TestArchConfig(buildDir, nil) + + ctx := CreateTestContext(bp) + ctx.Register() + + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + if len(errs) > 0 { + android.FailIfNoMatchingErrors(t, pattern, errs) + return + } + + _, errs = ctx.PrepareBuildActions(config) + if len(errs) > 0 { + android.FailIfNoMatchingErrors(t, pattern, errs) + return + } + + t.Fatalf("missing expected error %q (0 errors are returned)", pattern) +} + +// Test that we can extract the lib name from a lib path. +func TestLibNameFromFilePath(t *testing.T) { + barPath := android.PathForTesting("out/soong/.intermediates/external/libbar/libbar/linux_glibc_x86_64_shared/libbar.so") + libName := libNameFromFilePath(barPath) + expectedResult := "bar" + + if libName != expectedResult { + t.Errorf("libNameFromFilePath returned the wrong name; expected '%#v', got '%#v'", expectedResult, libName) + } +} + +// Test that we can extract the link path from a lib path. +func TestLinkPathFromFilePath(t *testing.T) { + barPath := android.PathForTesting("out/soong/.intermediates/external/libbar/libbar/linux_glibc_x86_64_shared/libbar.so") + libName := linkPathFromFilePath(barPath) + expectedResult := "out/soong/.intermediates/external/libbar/libbar/linux_glibc_x86_64_shared/" + + if libName != expectedResult { + t.Errorf("libNameFromFilePath returned the wrong name; expected '%#v', got '%#v'", expectedResult, libName) + } +} + +// Test default crate names from module names are generated correctly. +func TestDefaultCrateName(t *testing.T) { + ctx := testRust(t, ` + rust_library_host_dylib { + name: "fizz-buzz", + srcs: ["foo.rs"], + }`) + module := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64_dylib").Module().(*Module) + crateName := module.CrateName() + expectedResult := "fizz_buzz" + + if crateName != expectedResult { + t.Errorf("CrateName() returned the wrong default crate name; expected '%#v', got '%#v'", expectedResult, crateName) + } +} + +// Test to make sure dependencies are being picked up correctly. +func TestDepsTracking(t *testing.T) { + ctx := testRust(t, ` + rust_library_host_dylib { + name: "libfoo", + srcs: ["foo.rs"], + } + rust_library_host_rlib { + name: "libbar", + srcs: ["foo.rs"], + } + rust_proc_macro { + name: "libpm", + srcs: ["foo.rs"], + host_supported: true, + } + rust_binary_host { + name: "fizz-buzz", + dylibs: ["libfoo"], + rlibs: ["libbar"], + proc_macros: ["libpm"], + srcs: ["foo.rs"], + } + `) + module := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module) + + // Since dependencies are added to AndroidMk* properties, we can check these to see if they've been picked up. + if !android.InList("libfoo", module.Properties.AndroidMkDylibs) { + t.Errorf("Dylib dependency not detected (dependency missing from AndroidMkDylibs)") + } + + if !android.InList("libbar", module.Properties.AndroidMkRlibs) { + t.Errorf("Rlib dependency not detected (dependency missing from AndroidMkRlibs)") + } + + if !android.InList("libpm", module.Properties.AndroidMkProcMacroLibs) { + t.Errorf("Proc_macro dependency not detected (dependency missing from AndroidMkProcMacroLibs)") + } + +} diff --git a/rust/testing.go b/rust/testing.go new file mode 100644 index 000000000..a38697f88 --- /dev/null +++ b/rust/testing.go @@ -0,0 +1,105 @@ +// Copyright (C) 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/android" +) + +func GatherRequiredDepsForTest() string { + bp := ` + rust_prebuilt_dylib { + name: "libarena_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libfmt_macros_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libgraphviz_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libserialize_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libstd_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libsyntax_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libsyntax_ext_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libsyntax_pos_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libterm_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + rust_prebuilt_dylib { + name: "libtest_x86_64-unknown-linux-gnu", + srcs: [""], + host_supported: true, + } + ` + return bp +} + +func CreateTestContext(bp string) *android.TestContext { + ctx := android.NewTestArchContext() + ctx.RegisterModuleType("rust_binary", android.ModuleFactoryAdaptor(RustBinaryFactory)) + ctx.RegisterModuleType("rust_binary_host", android.ModuleFactoryAdaptor(RustBinaryHostFactory)) + ctx.RegisterModuleType("rust_library", android.ModuleFactoryAdaptor(RustLibraryFactory)) + ctx.RegisterModuleType("rust_library_host", android.ModuleFactoryAdaptor(RustLibraryHostFactory)) + ctx.RegisterModuleType("rust_library_host_rlib", android.ModuleFactoryAdaptor(RustLibraryRlibHostFactory)) + ctx.RegisterModuleType("rust_library_host_dylib", android.ModuleFactoryAdaptor(RustLibraryDylibHostFactory)) + ctx.RegisterModuleType("rust_library_rlib", android.ModuleFactoryAdaptor(RustLibraryRlibFactory)) + ctx.RegisterModuleType("rust_library_dylib", android.ModuleFactoryAdaptor(RustLibraryDylibFactory)) + ctx.RegisterModuleType("rust_proc_macro", android.ModuleFactoryAdaptor(ProcMacroFactory)) + ctx.RegisterModuleType("rust_prebuilt_dylib", android.ModuleFactoryAdaptor(PrebuiltDylibFactory)) + ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("rust_libraries", LibraryMutator).Parallel() + }) + bp = bp + GatherRequiredDepsForTest() + + mockFS := map[string][]byte{ + "Android.bp": []byte(bp), + "foo.rs": nil, + "src/bar.rs": nil, + "liby.so": nil, + "libz.so": nil, + } + + ctx.MockFileSystem(mockFS) + + return ctx +}