diff --git a/Android.bp b/Android.bp index 23cbad830..4ba695930 100644 --- a/Android.bp +++ b/Android.bp @@ -145,6 +145,7 @@ bootstrap_go_package { ], testSrcs: [ "cc/cc_test.go", + "cc/test_data_test.go", ], pluginFor: ["soong_build"], } diff --git a/android/module.go b/android/module.go index 430563d3c..b5de1adf0 100644 --- a/android/module.go +++ b/android/module.go @@ -77,6 +77,7 @@ type ModuleContext interface { ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams) ExpandSources(srcFiles, excludes []string) Paths + ExpandSourcesSubDir(srcFiles, excludes []string, subDir string) Paths Glob(globPattern string, excludes []string) Paths InstallFile(installPath OutputPath, srcPath Path, deps ...Path) OutputPath @@ -742,9 +743,14 @@ type SourceFileProducer interface { } // Returns a list of paths expanded from globs and modules referenced using ":module" syntax. -// ExpandSourceDeps must have already been called during the dependency resolution phase. +// ExtractSourcesDeps must have already been called during the dependency resolution phase. func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) Paths { + return ctx.ExpandSourcesSubDir(srcFiles, excludes, "") +} + +func (ctx *androidModuleContext) ExpandSourcesSubDir(srcFiles, excludes []string, subDir string) Paths { prefix := PathForModuleSrc(ctx).String() + for i, e := range excludes { j := findStringInSlice(e, srcFiles) if j != -1 { @@ -754,23 +760,28 @@ func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) Path excludes[i] = filepath.Join(prefix, e) } - globbedSrcFiles := make(Paths, 0, len(srcFiles)) + expandedSrcFiles := make(Paths, 0, len(srcFiles)) for _, s := range srcFiles { if m := SrcIsModule(s); m != "" { module := ctx.GetDirectDepWithTag(m, SourceDepTag) if srcProducer, ok := module.(SourceFileProducer); ok { - globbedSrcFiles = append(globbedSrcFiles, srcProducer.Srcs()...) + expandedSrcFiles = append(expandedSrcFiles, srcProducer.Srcs()...) } else { ctx.ModuleErrorf("srcs dependency %q is not a source file producing module", m) } } else if pathtools.IsGlob(s) { - globbedSrcFiles = append(globbedSrcFiles, ctx.Glob(filepath.Join(prefix, s), excludes)...) + globbedSrcFiles := ctx.Glob(filepath.Join(prefix, s), excludes) + expandedSrcFiles = append(expandedSrcFiles, globbedSrcFiles...) + for i, s := range expandedSrcFiles { + expandedSrcFiles[i] = s.(ModuleSrcPath).WithSubDir(ctx, subDir) + } } else { - globbedSrcFiles = append(globbedSrcFiles, PathForModuleSrc(ctx, s)) + s := PathForModuleSrc(ctx, s).WithSubDir(ctx, subDir) + expandedSrcFiles = append(expandedSrcFiles, s) } } - return globbedSrcFiles + return expandedSrcFiles } func (ctx *androidModuleContext) Glob(globPattern string, excludes []string) Paths { diff --git a/android/paths.go b/android/paths.go index ac7d81e62..037c98dce 100644 --- a/android/paths.go +++ b/android/paths.go @@ -86,6 +86,11 @@ type Path interface { // Base returns the last element of the path Base() string + + // Rel returns the portion of the path relative to the directory it was created from. For + // example, Rel on a PathsForModuleSrc would return the path relative to the module source + // directory. + Rel() string } // WritablePath is a type of path that can be used as an output for build rules. @@ -283,6 +288,7 @@ func (p WritablePaths) Strings() []string { type basePath struct { path string config Config + rel string } func (p basePath) Ext() string { @@ -293,6 +299,13 @@ func (p basePath) Base() string { return filepath.Base(p.path) } +func (p basePath) Rel() string { + if p.rel != "" { + return p.rel + } + return p.path +} + // SourcePath is a Path representing a file path rooted from SrcDir type SourcePath struct { basePath @@ -304,7 +317,7 @@ var _ Path = SourcePath{} // code that is embedding ninja variables in paths func safePathForSource(ctx PathContext, path string) SourcePath { p := validateSafePath(ctx, path) - ret := SourcePath{basePath{p, pathConfig(ctx)}} + ret := SourcePath{basePath{p, pathConfig(ctx), ""}} abs, err := filepath.Abs(ret.String()) if err != nil { @@ -330,7 +343,7 @@ func safePathForSource(ctx PathContext, path string) SourcePath { // will return a usable, but invalid SourcePath, and report a ModuleError. func PathForSource(ctx PathContext, paths ...string) SourcePath { p := validatePath(ctx, paths...) - ret := SourcePath{basePath{p, pathConfig(ctx)}} + ret := SourcePath{basePath{p, pathConfig(ctx), ""}} abs, err := filepath.Abs(ret.String()) if err != nil { @@ -365,7 +378,7 @@ func OptionalPathForSource(ctx PathContext, intermediates string, paths ...strin } p := validatePath(ctx, paths...) - path := SourcePath{basePath{p, pathConfig(ctx)}} + path := SourcePath{basePath{p, pathConfig(ctx), ""}} abs, err := filepath.Abs(path.String()) if err != nil { @@ -476,7 +489,7 @@ var _ Path = OutputPath{} // OutputPath, and report a ModuleError. func PathForOutput(ctx PathContext, paths ...string) OutputPath { path := validatePath(ctx, paths...) - return OutputPath{basePath{path, pathConfig(ctx)}} + return OutputPath{basePath{path, pathConfig(ctx), ""}} } func (p OutputPath) writablePath() {} @@ -516,8 +529,10 @@ var _ resPathProvider = ModuleSrcPath{} // PathForModuleSrc returns a ModuleSrcPath representing the paths... under the // module's local source directory. func PathForModuleSrc(ctx ModuleContext, paths ...string) ModuleSrcPath { - path := validatePath(ctx, paths...) - return ModuleSrcPath{PathForSource(ctx, ctx.ModuleDir(), path)} + p := validatePath(ctx, paths...) + path := ModuleSrcPath{PathForSource(ctx, ctx.ModuleDir(), p)} + path.basePath.rel = p + return path } // OptionalPathForModuleSrc returns an OptionalPath. The OptionalPath contains a @@ -542,6 +557,18 @@ func (p ModuleSrcPath) resPathWithName(ctx ModuleContext, name string) ModuleRes return PathForModuleRes(ctx, p.path, name) } +func (p ModuleSrcPath) WithSubDir(ctx ModuleContext, subdir string) ModuleSrcPath { + subdir = PathForModuleSrc(ctx, subdir).String() + var err error + rel, err := filepath.Rel(subdir, p.path) + if err != nil { + ctx.ModuleErrorf("source file %q is not under path %q", p.path, subdir) + return p + } + p.rel = rel + return p +} + // ModuleOutPath is a Path representing a module's output directory. type ModuleOutPath struct { OutputPath diff --git a/cc/androidmk.go b/cc/androidmk.go index c0be11148..182938c6e 100644 --- a/cc/androidmk.go +++ b/cc/androidmk.go @@ -159,6 +159,23 @@ func (test *testBinary) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkDa if Bool(test.Properties.Test_per_src) { ret.SubName = "_" + test.binaryDecorator.Properties.Stem } + + var testFiles []string + for _, d := range test.data { + rel := d.Rel() + path := d.String() + if !strings.HasSuffix(path, rel) { + panic(fmt.Errorf("path %q does not end with %q", path, rel)) + } + path = strings.TrimSuffix(path, rel) + testFiles = append(testFiles, path+":"+rel) + } + if len(testFiles) > 0 { + ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) error { + fmt.Fprintln(w, "LOCAL_TEST_DATA := "+strings.Join(testFiles, " ")) + return nil + }) + } } func (test *testLibrary) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) { diff --git a/cc/test.go b/cc/test.go index f60996c73..d3556bf97 100644 --- a/cc/test.go +++ b/cc/test.go @@ -19,9 +19,8 @@ import ( "runtime" "strings" - "github.com/google/blueprint" - "android/soong/android" + "github.com/google/blueprint" ) type TestProperties struct { @@ -38,6 +37,10 @@ type TestBinaryProperties struct { // relative_install_path. Useful if several tests need to be in the same // directory, but test_per_src doesn't work. No_named_install_directory *bool + + // list of files or filegroup modules that provide data that should be installed alongside + // the test + Data []string } func init() { @@ -191,6 +194,7 @@ type testBinary struct { *binaryDecorator *baseCompiler Properties TestBinaryProperties + data android.Paths } func (test *testBinary) linkerProps() []interface{} { @@ -205,6 +209,8 @@ func (test *testBinary) linkerInit(ctx BaseModuleContext) { } func (test *testBinary) linkerDeps(ctx DepsContext, deps Deps) Deps { + android.ExtractSourcesDeps(ctx, test.Properties.Data) + deps = test.testDecorator.linkerDeps(ctx, deps) deps = test.binaryDecorator.linkerDeps(ctx, deps) return deps @@ -217,6 +223,8 @@ func (test *testBinary) linkerFlags(ctx ModuleContext, flags Flags) Flags { } func (test *testBinary) install(ctx ModuleContext, file android.Path) { + test.data = ctx.ExpandSources(test.Properties.Data, nil) + test.binaryDecorator.baseInstaller.dir = "nativetest" test.binaryDecorator.baseInstaller.dir64 = "nativetest64" diff --git a/cc/test_data_test.go b/cc/test_data_test.go new file mode 100644 index 000000000..e3b1214f2 --- /dev/null +++ b/cc/test_data_test.go @@ -0,0 +1,204 @@ +// Copyright 2017 Google Inc. All rights reserved. +// +// 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 cc + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "android/soong/android" + "github.com/google/blueprint" +) + +type dataFile struct { + path string + file string +} + +var testDataTests = []struct { + name string + modules string + data []dataFile +}{ + { + name: "data files", + modules: ` + test { + name: "foo", + data: [ + "baz", + "bar/baz", + ], + }`, + data: []dataFile{ + {"dir", "baz"}, + {"dir", "bar/baz"}, + }, + }, + { + name: "filegroup", + modules: ` + filegroup { + name: "fg", + srcs: [ + "baz", + "bar/baz", + ], + } + + test { + name: "foo", + data: [":fg"], + }`, + data: []dataFile{ + {"dir", "baz"}, + {"dir", "bar/baz"}, + }, + }, + { + name: "relative filegroup", + modules: ` + filegroup { + name: "fg", + srcs: [ + "bar/baz", + ], + path: "bar", + } + + test { + name: "foo", + data: [":fg"], + }`, + data: []dataFile{ + {"dir/bar", "baz"}, + }, + }, + { + name: "relative filegroup trailing slash", + modules: ` + filegroup { + name: "fg", + srcs: [ + "bar/baz", + ], + path: "bar/", + } + + test { + name: "foo", + data: [":fg"], + }`, + data: []dataFile{ + {"dir/bar", "baz"}, + }, + }, +} + +func TestDataTests(t *testing.T) { + buildDir, err := ioutil.TempDir("", "soong_test_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(buildDir) + + config := android.TestConfig(buildDir) + + for _, test := range testDataTests { + t.Run(test.name, func(t *testing.T) { + ctx := android.NewContext() + ctx.MockFileSystem(map[string][]byte{ + "Blueprints": []byte(`subdirs = ["dir"]`), + "dir/Blueprints": []byte(test.modules), + "dir/baz": nil, + "dir/bar/baz": nil, + }) + ctx.RegisterModuleType("test", newTest) + + _, errs := ctx.ParseBlueprintsFiles("Blueprints") + fail(t, errs) + _, errs = ctx.PrepareBuildActions(config) + fail(t, errs) + + foo := findModule(ctx, "foo") + if foo == nil { + t.Fatalf("failed to find module foo") + } + + got := foo.(*testDataTest).data + if len(got) != len(test.data) { + t.Errorf("expected %d data files, got %d", + len(test.data), len(got)) + } + + for i := range got { + if i >= len(test.data) { + break + } + + path := filepath.Join(test.data[i].path, test.data[i].file) + if test.data[i].file != got[i].Rel() || + path != got[i].String() { + fmt.Errorf("expected %s:%s got %s:%s", + path, test.data[i].file, + got[i].String(), got[i].Rel()) + } + } + }) + } +} + +type testDataTest struct { + android.ModuleBase + data android.Paths + Properties struct { + Data []string + } +} + +func newTest() (blueprint.Module, []interface{}) { + m := &testDataTest{} + return android.InitAndroidModule(m, &m.Properties) +} + +func (test *testDataTest) DepsMutator(ctx android.BottomUpMutatorContext) { + android.ExtractSourcesDeps(ctx, test.Properties.Data) +} + +func (test *testDataTest) GenerateAndroidBuildActions(ctx android.ModuleContext) { + test.data = ctx.ExpandSources(test.Properties.Data, nil) +} + +func findModule(ctx *blueprint.Context, name string) blueprint.Module { + var ret blueprint.Module + ctx.VisitAllModules(func(m blueprint.Module) { + if ctx.ModuleName(m) == name { + ret = m + } + }) + return ret +} + +func fail(t *testing.T, errs []error) { + if len(errs) > 0 { + for _, err := range errs { + t.Error(err) + } + t.FailNow() + } +} diff --git a/genrule/filegroup.go b/genrule/filegroup.go index 9b53c9f7b..c1d08a823 100644 --- a/genrule/filegroup.go +++ b/genrule/filegroup.go @@ -29,6 +29,12 @@ type fileGroupProperties struct { Srcs []string Exclude_srcs []string + + // The base path to the files. May be used by other modules to determine which portion + // of the path to use. For example, when a filegroup is used as data in a cc_test rule, + // the base path is stripped off the path and the remaining path is used as the + // installation directory. + Path string } type fileGroup struct { @@ -53,7 +59,7 @@ func (fg *fileGroup) DepsMutator(ctx android.BottomUpMutatorContext) { } func (fg *fileGroup) GenerateAndroidBuildActions(ctx android.ModuleContext) { - fg.srcs = ctx.ExpandSources(fg.properties.Srcs, fg.properties.Exclude_srcs) + fg.srcs = ctx.ExpandSourcesSubDir(fg.properties.Srcs, fg.properties.Exclude_srcs, fg.properties.Path) } func (fg *fileGroup) Srcs() android.Paths {