diff --git a/rust/Android.bp b/rust/Android.bp index b06ea8e75..d56de870f 100644 --- a/rust/Android.bp +++ b/rust/Android.bp @@ -9,24 +9,26 @@ bootstrap_go_package { ], srcs: [ "androidmk.go", - "compiler.go", - "coverage.go", "binary.go", "builder.go", + "clippy.go", + "compiler.go", + "coverage.go", "library.go", "prebuilt.go", "proc_macro.go", - "project_json.go", + "project_json.go", "rust.go", "test.go", "testing.go", ], testSrcs: [ "binary_test.go", + "clippy_test.go", "compiler_test.go", "coverage_test.go", "library_test.go", - "project_json_test.go", + "project_json_test.go", "rust_test.go", "test_test.go", ], diff --git a/rust/builder.go b/rust/builder.go index 7dbb59d3e..b1913235e 100644 --- a/rust/builder.go +++ b/rust/builder.go @@ -39,6 +39,18 @@ var ( }, "rustcFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd") + _ = pctx.SourcePathVariable("clippyCmd", "${config.RustBin}/clippy-driver") + clippyDriver = pctx.AndroidStaticRule("clippy", + blueprint.RuleParams{ + Command: "$clippyCmd " + + // Because clippy-driver uses rustc as backend, we need to have some output even during the linting. + // Use the metadata output as it has the smallest footprint. + "--emit metadata -o $out $in ${libFlags} " + + "$clippyFlags $rustcFlags", + CommandDeps: []string{"$clippyCmd"}, + }, + "rustcFlags", "libFlags", "clippyFlags") + zip = pctx.AndroidStaticRule("zip", blueprint.RuleParams{ Command: "cat $out.rsp | tr ' ' '\\n' | tr -d \\' | sort -u > ${out}.tmp && ${SoongZipCmd} -o ${out} -C $$OUT_DIR -l ${out}.tmp", @@ -125,10 +137,14 @@ func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps Path rustcFlags = append(rustcFlags, "--target="+targetTriple) linkFlags = append(linkFlags, "-target "+targetTriple) } - // TODO once we have static libraries in the host prebuilt .bp, this - // should be unconditionally added. - if !(ctx.Host() && ctx.TargetPrimary()) { - // If we're not targeting the host primary arch, do not use an implicit sysroot + // TODO(b/159718669): Once we have defined static libraries in the host + // prebuilts Blueprint file, sysroot should be unconditionally sourced + // from /dev/null. Explicitly set sysroot to avoid clippy-driver to + // internally call rustc. + if ctx.Host() && ctx.TargetPrimary() { + rustcFlags = append(rustcFlags, "--sysroot=${config.RustPath}") + } else { + // If we're not targeting the host primary arch, do not use a sysroot. rustcFlags = append(rustcFlags, "--sysroot=/dev/null") } // Collect linker flags @@ -179,6 +195,25 @@ func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps Path output.coverageFile = gcnoFile } + if flags.Clippy { + clippyFile := android.PathForModuleOut(ctx, outputFile.Base()+".clippy") + ctx.Build(pctx, android.BuildParams{ + Rule: clippyDriver, + Description: "clippy " + main.Rel(), + Output: clippyFile, + ImplicitOutputs: nil, + Inputs: inputs, + Implicits: implicits, + Args: map[string]string{ + "rustcFlags": strings.Join(rustcFlags, " "), + "libFlags": strings.Join(libFlags, " "), + "clippyFlags": strings.Join(flags.ClippyFlags, " "), + }, + }) + // Declare the clippy build as an implicit dependency of the original crate. + implicits = append(implicits, clippyFile) + } + ctx.Build(pctx, android.BuildParams{ Rule: rustc, Description: "rustc " + main.Rel(), diff --git a/rust/clippy.go b/rust/clippy.go new file mode 100644 index 000000000..e1f567d99 --- /dev/null +++ b/rust/clippy.go @@ -0,0 +1,42 @@ +// Copyright 2020 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/rust/config" +) + +type ClippyProperties struct { + // whether to run clippy. + Clippy *bool +} + +type clippy struct { + Properties ClippyProperties +} + +func (c *clippy) props() []interface{} { + return []interface{}{&c.Properties} +} + +func (c *clippy) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) { + if c.Properties.Clippy != nil && !*c.Properties.Clippy { + return flags, deps + } + enabled, lints := config.ClippyLintsForDir(ctx.ModuleDir()) + flags.Clippy = enabled + flags.ClippyFlags = append(flags.ClippyFlags, lints) + return flags, deps +} diff --git a/rust/clippy_test.go b/rust/clippy_test.go new file mode 100644 index 000000000..af5cd1737 --- /dev/null +++ b/rust/clippy_test.go @@ -0,0 +1,46 @@ +// Copyright 2020 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 ( + "testing" +) + +func TestClippy(t *testing.T) { + ctx := testRust(t, ` + rust_library { + name: "libfoo", + srcs: ["foo.rs"], + crate_name: "foo", + } + rust_library { + name: "libfoobar", + srcs: ["foo.rs"], + crate_name: "foobar", + clippy: false, + }`) + + ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Output("libfoo.so") + fooClippy := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").MaybeRule("clippy") + if fooClippy.Rule.String() != "android/soong/rust.clippy" { + t.Errorf("Clippy output (default) for libfoo was not generated: %+v", fooClippy) + } + + ctx.ModuleForTests("libfoobar", "android_arm64_armv8-a_shared").Output("libfoobar.so") + foobarClippy := ctx.ModuleForTests("libfoobar", "android_arm64_armv8-a_shared").MaybeRule("clippy") + if foobarClippy.Rule != nil { + t.Errorf("Clippy output for libfoobar is not empty") + } +} diff --git a/rust/config/Android.bp b/rust/config/Android.bp index 5026da39e..1d30f826f 100644 --- a/rust/config/Android.bp +++ b/rust/config/Android.bp @@ -9,6 +9,7 @@ bootstrap_go_package { "arm_device.go", "arm64_device.go", "global.go", + "clippy.go", "toolchain.go", "allowed_list.go", "x86_darwin_host.go", diff --git a/rust/config/clippy.go b/rust/config/clippy.go new file mode 100644 index 000000000..c199ff269 --- /dev/null +++ b/rust/config/clippy.go @@ -0,0 +1,80 @@ +// Copyright 2020 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 ( + defaultLints = []string{ + "-D missing-docs", + "-D clippy::missing-safety-doc", + } + defaultVendorLints = []string{ + "", + } +) + +func init() { + // Default Rust lints. These apply to all Google-authored modules. + pctx.VariableFunc("ClippyDefaultLints", func(ctx android.PackageVarContext) string { + if override := ctx.Config().Getenv("CLIPPY_DEFAULT_LINTS"); override != "" { + return override + } + return strings.Join(defaultLints, " ") + }) + + // Rust lints that only applies to external code. + pctx.VariableFunc("ClippyVendorLints", func(ctx android.PackageVarContext) string { + if override := ctx.Config().Getenv("CLIPPY_VENDOR_LINTS"); override != "" { + return override + } + return strings.Join(defaultVendorLints, " ") + }) +} + +type PathBasedClippyConfig struct { + PathPrefix string + Enabled bool + ClippyConfig string +} + +const clippyNone = "" +const clippyDefault = "${config.ClippyDefaultLints}" +const clippyVendor = "${config.ClippyVendorLints}" + +// This is a map of local path prefixes to a boolean indicating if the lint +// rule should be generated and if so, the set of lints to use. The first entry +// matching will be used. If no entry is matching, clippyDefault will be used. +var DefaultLocalTidyChecks = []PathBasedClippyConfig{ + {"external/", false, clippyNone}, + {"hardware/", true, clippyVendor}, + {"prebuilts/", false, clippyNone}, + {"vendor/google", true, clippyDefault}, + {"vendor/", true, clippyVendor}, +} + +// ClippyLintsForDir returns the Clippy lints to be used for a repository. +func ClippyLintsForDir(dir string) (bool, string) { + for _, pathCheck := range DefaultLocalTidyChecks { + if strings.HasPrefix(dir, pathCheck.PathPrefix) { + return pathCheck.Enabled, pathCheck.ClippyConfig + } + } + return true, clippyDefault +} diff --git a/rust/rust.go b/rust/rust.go index 7b82b1f69..7f8287394 100644 --- a/rust/rust.go +++ b/rust/rust.go @@ -49,8 +49,10 @@ type Flags struct { GlobalLinkFlags []string // Flags that apply globally to linker RustFlags []string // Flags that apply to rust LinkFlags []string // Flags that apply to linker + ClippyFlags []string // Flags that apply to clippy-driver, during the linting Toolchain config.Toolchain Coverage bool + Clippy bool } type BaseProperties struct { @@ -75,6 +77,7 @@ type Module struct { compiler compiler coverage *coverage + clippy *clippy cachedToolchain config.Toolchain subAndroidMkOnce map[subAndroidMkProvider]bool outputFile android.OptionalPath @@ -306,6 +309,7 @@ func DefaultsFactory(props ...interface{}) android.Module { &PrebuiltProperties{}, &TestProperties{}, &cc.CoverageProperties{}, + &ClippyProperties{}, ) android.InitDefaultsModule(module) @@ -456,6 +460,9 @@ func (mod *Module) Init() android.Module { if mod.coverage != nil { mod.AddProperties(mod.coverage.props()...) } + if mod.clippy != nil { + mod.AddProperties(mod.clippy.props()...) + } android.InitAndroidArchModule(mod, mod.hod, mod.multilib) @@ -487,6 +494,7 @@ func newBaseModule(hod android.HostOrDeviceSupported, multilib android.Multilib) func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module { module := newBaseModule(hod, multilib) module.coverage = &coverage{} + module.clippy = &clippy{} return module } @@ -576,6 +584,9 @@ func (mod *Module) GenerateAndroidBuildActions(actx android.ModuleContext) { if mod.coverage != nil { flags, deps = mod.coverage.flags(ctx, flags, deps) } + if mod.clippy != nil { + flags, deps = mod.clippy.flags(ctx, flags, deps) + } if mod.compiler != nil { outputFile := mod.compiler.compile(ctx, flags, deps)