diff --git a/Android.bp b/Android.bp index 91a49814b..959cbf1ce 100644 --- a/Android.bp +++ b/Android.bp @@ -50,6 +50,7 @@ bootstrap_go_package { "android/expand.go", "android/filegroup.go", "android/hooks.go", + "android/license.go", "android/makevars.go", "android/module.go", "android/mutator.go", @@ -58,6 +59,7 @@ bootstrap_go_package { "android/notices.go", "android/onceper.go", "android/override_module.go", + "android/package.go", "android/package_ctx.go", "android/path_properties.go", "android/paths.go", @@ -81,10 +83,12 @@ bootstrap_go_package { "android/arch_test.go", "android/config_test.go", "android/expand_test.go", + "android/license_test.go", "android/namespace_test.go", "android/neverallow_test.go", "android/onceper_test.go", "android/path_properties_test.go", + "android/package_test.go", "android/paths_test.go", "android/prebuilt_test.go", "android/prebuilt_etc_test.go", diff --git a/android/license.go b/android/license.go new file mode 100644 index 000000000..6a7c31338 --- /dev/null +++ b/android/license.go @@ -0,0 +1,69 @@ +// Copyright 2020 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 android + +import ( + "github.com/google/blueprint" +) + +func init() { + RegisterModuleType("license", LicenseFactory) +} + +type licenseProperties struct { + // Specifies the kinds of license that apply. + License_kinds []string + // Specifies a short copyright notice to use for the license. + Copyright_notice *string + // Specifies the path or label for the text of the license. + License_text []string `android:"path"` + // Specifies the package name to which the license applies. + Package_name *string + // Specifies where this license can be used + Visibility []string +} + +type licenseModule struct { + ModuleBase + DefaultableModuleBase + + properties licenseProperties +} + +func (m *licenseModule) DepsMutator(ctx BottomUpMutatorContext) { + // Do nothing. +} + +func (m *licenseModule) GenerateAndroidBuildActions(ctx ModuleContext) { + // Nothing to do. +} + +func (m *licenseModule) GenerateBuildActions(ctx blueprint.ModuleContext) { + // Nothing to do. +} + +func LicenseFactory() Module { + module := &licenseModule{} + + base := module.base() + module.AddProperties( + &base.nameProperties, + &module.properties) + + base.generalProperties = module.GetProperties() + base.customizableProperties = module.GetProperties() + + return module +} diff --git a/android/license_test.go b/android/license_test.go new file mode 100644 index 000000000..cd2cb64ff --- /dev/null +++ b/android/license_test.go @@ -0,0 +1,150 @@ +package android + +import ( + "io/ioutil" + "os" + "testing" +) + +var licenseTests = []struct { + name string + fs map[string][]byte + expectedErrors []string +}{ + { + name: "license must not accept licenses property", + fs: map[string][]byte{ + "top/Blueprints": []byte(` + license { + name: "top_license", + visibility: ["//visibility:private"], + licenses: ["other_license"], + + }`), + }, + expectedErrors: []string{ + `top/Blueprints:5:14: unrecognized property "licenses"`, + }, + }, + { + name: "public license", + fs: map[string][]byte{ + "top/Blueprints": []byte(` + license { + name: "top_proprietary", + license_kinds: ["top_by_exception_only"], + visibility: ["//visibility:public"], + }`), + "other/Blueprints": []byte(` + rule { + name: "arule", + licenses: ["top_proprietary"], + + }`), + "yetmore/Blueprints": []byte(` + package { + default_applicable_licenses: ["top_proprietary"], + }`), + }, + }, + { + name: "multiple licenses", + fs: map[string][]byte{ + "top/Blueprints": []byte(` + package { + default_applicable_licenses: ["top_proprietary"], + } + license { + name: "top_allowed_as_notice", + license_kinds: ["top_notice"], + } + license { + name: "top_proprietary", + license_kinds: ["top_by_exception_only"], + visibility: ["//visibility:public"], + } + rule { + name: "myrule", + licenses: ["top_allowed_as_notice", "top_proprietary"] + }`), + "other/Blueprints": []byte(` + rule { + name: "arule", + licenses: ["top_proprietary"], + + }`), + "yetmore/Blueprints": []byte(` + package { + default_applicable_licenses: ["top_proprietary"], + }`), + }, + }, +} + +func TestLicense(t *testing.T) { + for _, test := range licenseTests { + t.Run(test.name, func(t *testing.T) { + _, errs := testLicense(test.fs) + expectedErrors := test.expectedErrors + if expectedErrors == nil { + FailIfErrored(t, errs) + } else { + for _, expectedError := range expectedErrors { + FailIfNoMatchingErrors(t, expectedError, errs) + } + if len(errs) > len(expectedErrors) { + t.Errorf("additional errors found, expected %d, found %d", len(expectedErrors), len(errs)) + for i, expectedError := range expectedErrors { + t.Errorf("expectedErrors[%d] = %s", i, expectedError) + } + for i, err := range errs { + t.Errorf("errs[%d] = %s", i, err) + } + } + } + }) + } +} +func testLicense(fs map[string][]byte) (*TestContext, []error) { + buildDir, err := ioutil.TempDir("", "license_test") + if err != nil { + return nil, []error{err} + } + defer os.RemoveAll(buildDir) + + // Create a new config per test as visibility information is stored in the config. + env := make(map[string]string) + env["ANDROID_REQUIRE_LICENSES"] = "1" + config := TestArchConfig(buildDir, env) + ctx := NewTestArchContext() + ctx.MockFileSystem(fs) + ctx.RegisterModuleType("package", ModuleFactoryAdaptor(PackageFactory)) + ctx.RegisterModuleType("license", ModuleFactoryAdaptor(LicenseFactory)) + ctx.RegisterModuleType("rule", ModuleFactoryAdaptor(newMockRuleModule)) + ctx.PreArchMutators(registerPackageRenamer) + ctx.Register() + _, errs := ctx.ParseBlueprintsFiles(".") + if len(errs) > 0 { + return ctx, errs + } + _, errs = ctx.PrepareBuildActions(config) + return ctx, errs +} + +type mockRuleModule struct { + ModuleBase + DefaultableModuleBase +} + +func newMockRuleModule() Module { + m := &mockRuleModule{} + InitAndroidModule(m) + InitDefaultableModule(m) + return m +} + +func (p *mockRuleModule) GenerateAndroidBuildActions(ModuleContext) { +} + +func (p *mockRuleModule) DepsMutator(BottomUpMutatorContext) { +} diff --git a/android/module.go b/android/module.go index d34916fc4..7f4e30817 100644 --- a/android/module.go +++ b/android/module.go @@ -208,6 +208,9 @@ type commonProperties struct { // emit build rules for this module Enabled *bool `android:"arch_variant"` + // Names of the licenses that apply to this module. + Licenses []string + // control whether this module compiles for 32-bit, 64-bit, or both. Possible values // are "32" (compile for 32-bit only), "64" (compile for 64-bit only), "both" (compile for both // architectures), or "first" (compile for 64-bit on a 64-bit platform, and 32-bit on a 32-bit diff --git a/android/mutator.go b/android/mutator.go index e003f0bbe..68ba6f476 100644 --- a/android/mutator.go +++ b/android/mutator.go @@ -77,6 +77,7 @@ var preArch = []RegisterMutatorFunc{ ctx.TopDown("load_hooks", LoadHookMutator).Parallel() }, RegisterNamespaceMutator, + registerPackageRenamer, RegisterPrebuiltsPreArchMutators, RegisterDefaultsPreArchMutators, RegisterOverridePreArchMutators, diff --git a/android/package.go b/android/package.go new file mode 100644 index 000000000..6949c4e2d --- /dev/null +++ b/android/package.go @@ -0,0 +1,93 @@ +// Copyright 2019 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 android + +import ( + "fmt" + "sync/atomic" + + "github.com/google/blueprint" +) + +func init() { + RegisterModuleType("package", PackageFactory) +} + +type packageProperties struct { + // Specifies the default visibility for all modules defined in this package. + Default_visibility []string + // Specifies the names of the default licenses for all modules defined in this package. + Default_applicable_licenses []string +} + +type packageModule struct { + ModuleBase + + properties packageProperties + // The module dir + name string `blueprint:"mutated"` +} + +func (p *packageModule) GenerateAndroidBuildActions(ModuleContext) { + // Nothing to do. +} + +func (p *packageModule) GenerateBuildActions(ctx blueprint.ModuleContext) { + // Nothing to do. +} + +func (p *packageModule) DepsMutator(ctx BottomUpMutatorContext) { + // Nothing to do. +} + +func (p *packageModule) Name() string { + return p.name +} + +func registerPackageRenamer(ctx RegisterMutatorsContext) { + ctx.BottomUp("packages", packageRenamer).Parallel() +} + +// packageRenamer ensures that every package gets named +func packageRenamer(ctx BottomUpMutatorContext) { + if p, ok := ctx.Module().(*packageModule); ok { + p.name = "//" + ctx.ModuleDir() + ctx.Rename("//" + ctx.ModuleDir()) + } +} + +// Counter to ensure package modules are created with a unique name within whatever namespace they +// belong. +var packageCount uint32 = 0 + +func PackageFactory() Module { + module := &packageModule{} + + // Get a unique if for the package. Has to be done atomically as the creation of the modules are + // done in parallel. + id := atomic.AddUint32(&packageCount, 1) + module.name = fmt.Sprintf("soong_package_%d", id) + + module.AddProperties(&module.properties) + + // The name is the relative path from build root to the directory containing this + // module. Set that name at the earliest possible moment that information is available + // which is in a LoadHook. + AddLoadHook(module, func(ctx LoadHookContext) { + module.name = "//" + ctx.ModuleDir() + }) + + return module +} diff --git a/android/package_test.go b/android/package_test.go new file mode 100644 index 000000000..c7e9c553f --- /dev/null +++ b/android/package_test.go @@ -0,0 +1,110 @@ +package android + +import ( + "io/ioutil" + "os" + "testing" +) + +var packageTests = []struct { + name string + fs map[string][]byte + expectedErrors []string +}{ + // Package default_visibility handling is tested in visibility_test.go + { + name: "package must not accept visibility and name properties", + fs: map[string][]byte{ + "top/Blueprints": []byte(` + package { + name: "package", + visibility: ["//visibility:private"], + }`), + }, + expectedErrors: []string{ + `top/Blueprints:3:10: unrecognized property "name"`, + `top/Blueprints:4:16: unrecognized property "visibility"`, + }, + }, + { + name: "multiple packages in separate directories", + fs: map[string][]byte{ + "top/Blueprints": []byte(` + package { + }`), + "other/Blueprints": []byte(` + package { + }`), + "other/nested/Blueprints": []byte(` + package { + }`), + }, + }, + { + name: "package must not be specified more than once per package", + fs: map[string][]byte{ + "top/Blueprints": []byte(` + package { + default_visibility: ["//visibility:private"], + default_applicable_licenses: ["license"], + } + + package { + }`), + }, + expectedErrors: []string{ + `"//top" conflicts with existing module`, + }, + }, +} + +func TestPackage(t *testing.T) { + for _, test := range packageTests { + t.Run(test.name, func(t *testing.T) { + _, errs := testPackage(test.fs) + + expectedErrors := test.expectedErrors + if expectedErrors == nil { + FailIfErrored(t, errs) + } else { + for _, expectedError := range expectedErrors { + FailIfNoMatchingErrors(t, expectedError, errs) + } + if len(errs) > len(expectedErrors) { + t.Errorf("additional errors found, expected %d, found %d", len(expectedErrors), len(errs)) + for i, expectedError := range expectedErrors { + t.Errorf("expectedErrors[%d] = %s", i, expectedError) + } + for i, err := range errs { + t.Errorf("errs[%d] = %s", i, err) + } + } + } + }) + } +} + +func testPackage(fs map[string][]byte) (*TestContext, []error) { + buildDir, err := ioutil.TempDir("", "package_test") + if err != nil { + return nil, []error{err} + } + defer os.RemoveAll(buildDir) + + // Create a new config per test as visibility information is stored in the config. + config := TestArchConfig(buildDir, nil) + + ctx := NewTestArchContext() + ctx.MockFileSystem(fs) + ctx.RegisterModuleType("package", ModuleFactoryAdaptor(PackageFactory)) + ctx.PreArchMutators(registerPackageRenamer) + ctx.Register() + + _, errs := ctx.ParseBlueprintsFiles(".") + if len(errs) > 0 { + return ctx, errs + } + + _, errs = ctx.PrepareBuildActions(config) + return ctx, errs +}