diff --git a/bp2build/Android.bp b/bp2build/Android.bp index cb25627b2..17f356707 100644 --- a/bp2build/Android.bp +++ b/bp2build/Android.bp @@ -63,6 +63,7 @@ bootstrap_go_package { "java_plugin_conversion_test.go", "java_proto_conversion_test.go", "linker_config_conversion_test.go", + "ndk_headers_conversion_test.go", "performance_test.go", "prebuilt_etc_conversion_test.go", "python_binary_conversion_test.go", diff --git a/bp2build/ndk_headers_conversion_test.go b/bp2build/ndk_headers_conversion_test.go new file mode 100644 index 000000000..c7cc6b21a --- /dev/null +++ b/bp2build/ndk_headers_conversion_test.go @@ -0,0 +1,164 @@ +// Copyright 2022 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 bp2build + +import ( + "fmt" + "testing" + + "android/soong/cc" +) + +func TestNdkHeaderFilepaths(t *testing.T) { + bpTemplate := ` + ndk_headers { + name: "foo", + srcs: %v, + exclude_srcs: %v, + } + ` + testCases := []struct { + desc string + srcs string + excludeSrcs string + expectedHdrs string + }{ + { + desc: "Single header file", + srcs: `["foo.h"]`, + excludeSrcs: `[]`, + expectedHdrs: `["foo.h"]`, + }, + { + desc: "Multiple header files", + srcs: `["foo.h", "foo_other.h"]`, + excludeSrcs: `[]`, + expectedHdrs: `[ + "foo.h", + "foo_other.h", + ]`, + }, + { + desc: "Multiple header files with excludes", + srcs: `["foo.h", "foo_other.h"]`, + excludeSrcs: `["foo_other.h"]`, + expectedHdrs: `["foo.h"]`, + }, + { + desc: "Multiple header files via Soong-supported globs", + srcs: `["*.h"]`, + excludeSrcs: `[]`, + expectedHdrs: `[ + "foo.h", + "foo_other.h", + ]`, + }, + } + for _, testCase := range testCases { + fs := map[string]string{ + "foo.h": "", + "foo_other.h": "", + } + expectedApiContributionTargetName := "foo.contribution" + expectedBazelTarget := MakeBazelTargetNoRestrictions( + "cc_api_headers", + expectedApiContributionTargetName, + AttrNameToString{ + "hdrs": testCase.expectedHdrs, + }, + ) + RunBp2BuildTestCase(t, cc.RegisterNdkModuleTypes, Bp2buildTestCase{ + Description: testCase.desc, + Blueprint: fmt.Sprintf(bpTemplate, testCase.srcs, testCase.excludeSrcs), + ExpectedBazelTargets: []string{expectedBazelTarget}, + Filesystem: fs, + }) + } +} + +func TestNdkHeaderIncludeDir(t *testing.T) { + bpTemplate := ` + ndk_headers { + name: "foo", + from: %v, + to: "this/value/is/ignored", + } + ` + testCases := []struct { + desc string + from string + expectedIncludeDir string + }{ + { + desc: "Empty `from` value", + from: `""`, + expectedIncludeDir: `""`, + }, + { + desc: "Non-Empty `from` value", + from: `"include"`, + expectedIncludeDir: `"include"`, + }, + } + for _, testCase := range testCases { + expectedApiContributionTargetName := "foo.contribution" + expectedBazelTarget := MakeBazelTargetNoRestrictions( + "cc_api_headers", + expectedApiContributionTargetName, + AttrNameToString{ + "include_dir": testCase.expectedIncludeDir, + }, + ) + RunBp2BuildTestCase(t, cc.RegisterNdkModuleTypes, Bp2buildTestCase{ + Description: testCase.desc, + Blueprint: fmt.Sprintf(bpTemplate, testCase.from), + ExpectedBazelTargets: []string{expectedBazelTarget}, + }) + } +} + +func TestVersionedNdkHeaderFilepaths(t *testing.T) { + bp := ` + versioned_ndk_headers { + name: "common_libc", + from: "include" + } + ` + fs := map[string]string{ + "include/math.h": "", + "include/stdio.h": "", + "include/arm/arm.h": "", + "include/x86/x86.h": "", + } + expectedApiContributionTargetName := "common_libc.contribution" + expectedBazelTarget := MakeBazelTargetNoRestrictions( + "cc_api_headers", + expectedApiContributionTargetName, + AttrNameToString{ + "include_dir": `"include"`, + "hdrs": `[ + "include/math.h", + "include/stdio.h", + "include/arm/arm.h", + "include/x86/x86.h", + ]`, + }, + ) + RunBp2BuildTestCase(t, cc.RegisterNdkModuleTypes, Bp2buildTestCase{ + Blueprint: bp, + Filesystem: fs, + ExpectedBazelTargets: []string{expectedBazelTarget}, + }) +} diff --git a/cc/ndk_headers.go b/cc/ndk_headers.go index ef38a064a..5e0694880 100644 --- a/cc/ndk_headers.go +++ b/cc/ndk_headers.go @@ -19,8 +19,10 @@ import ( "path/filepath" "github.com/google/blueprint" + "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/bazel" ) var ( @@ -79,6 +81,7 @@ type headerProperties struct { type headerModule struct { android.ModuleBase + android.BazelModuleBase properties headerProperties @@ -144,6 +147,47 @@ func (m *headerModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { } } +const ( + apiContributionSuffix = ".contribution" +) + +// apiContributionTargetName returns the name of the cc_api(headers|contribution) bp2build target of ndk modules +// A suffix is necessary to prevent a name collision with the base ndk_(library|header) target in the same bp2build bazel package +func apiContributionTargetName(moduleName string) string { + return moduleName + apiContributionSuffix +} + +// TODO(b/243196151): Populate `system` and `arch` metadata +type bazelCcApiHeadersAttributes struct { + Hdrs bazel.LabelListAttribute + Include_dir *string +} + +func createCcApiHeadersTarget(ctx android.TopDownMutatorContext, includes []string, excludes []string, include_dir *string) { + props := bazel.BazelTargetModuleProperties{ + Rule_class: "cc_api_headers", + Bzl_load_location: "//build/bazel/rules/apis:cc_api_contribution.bzl", + } + attrs := &bazelCcApiHeadersAttributes{ + Hdrs: bazel.MakeLabelListAttribute( + android.BazelLabelForModuleSrcExcludes( + ctx, + includes, + excludes, + ), + ), + Include_dir: include_dir, + } + ctx.CreateBazelTargetModule(props, android.CommonAttributes{ + Name: apiContributionTargetName(ctx.ModuleName()), + }, attrs) +} + +func (h *headerModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) { + // Generate `cc_api_headers` target for Multi-tree API export + createCcApiHeadersTarget(ctx, h.properties.Srcs, h.properties.Exclude_srcs, h.properties.From) +} + // ndk_headers installs the sets of ndk headers defined in the srcs property // to the sysroot base + "usr/include" + to directory + directory component. // ndk_headers requires the license file to be specified. Example: @@ -158,6 +202,7 @@ func ndkHeadersFactory() android.Module { module := &headerModule{} module.AddProperties(&module.properties) android.InitAndroidModule(module) + android.InitBazelModule(module) return module } @@ -190,6 +235,7 @@ type versionedHeaderProperties struct { // Note that this is really only built to handle bionic/libc/include. type versionedHeaderModule struct { android.ModuleBase + android.BazelModuleBase properties versionedHeaderProperties @@ -197,6 +243,11 @@ type versionedHeaderModule struct { licensePath android.Path } +// Return the glob pattern to find all .h files beneath `dir` +func headerGlobPattern(dir string) string { + return filepath.Join(dir, "**", "*.h") +} + func (m *versionedHeaderModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { if String(m.properties.License) == "" { ctx.PropertyErrorf("license", "field is required") @@ -206,7 +257,7 @@ func (m *versionedHeaderModule) GenerateAndroidBuildActions(ctx android.ModuleCo fromSrcPath := android.PathForModuleSrc(ctx, String(m.properties.From)) toOutputPath := getCurrentIncludePath(ctx).Join(ctx, String(m.properties.To)) - srcFiles := ctx.GlobFiles(filepath.Join(fromSrcPath.String(), "**/*.h"), nil) + srcFiles := ctx.GlobFiles(headerGlobPattern(fromSrcPath.String()), nil) var installPaths []android.WritablePath for _, header := range srcFiles { installDir := getHeaderInstallDir(ctx, header, String(m.properties.From), String(m.properties.To)) @@ -222,6 +273,13 @@ func (m *versionedHeaderModule) GenerateAndroidBuildActions(ctx android.ModuleCo processHeadersWithVersioner(ctx, fromSrcPath, toOutputPath, srcFiles, installPaths) } +func (h *versionedHeaderModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) { + // Glob all .h files under `From` + includePattern := headerGlobPattern(proptools.String(h.properties.From)) + // Generate `cc_api_headers` target for Multi-tree API export + createCcApiHeadersTarget(ctx, []string{includePattern}, []string{}, h.properties.From) +} + func processHeadersWithVersioner(ctx android.ModuleContext, srcDir, outDir android.Path, srcFiles android.Paths, installPaths []android.WritablePath) android.Path { // The versioner depends on a dependencies directory to simplify determining include paths @@ -271,16 +329,19 @@ func versionedNdkHeadersFactory() android.Module { module.AddProperties(&module.properties) android.InitAndroidModule(module) + android.InitBazelModule(module) return module } -// preprocessed_ndk_header { -// name: "foo", -// preprocessor: "foo.sh", -// srcs: [...], -// to: "android", -// } +// preprocessed_ndk_header { +// +// name: "foo", +// preprocessor: "foo.sh", +// srcs: [...], +// to: "android", +// +// } // // Will invoke the preprocessor as: // diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go index 6c200f5dd..622558edf 100644 --- a/cc/ndk_sysroot.go +++ b/cc/ndk_sysroot.go @@ -57,15 +57,18 @@ import ( ) func init() { - android.RegisterModuleType("ndk_headers", ndkHeadersFactory) - android.RegisterModuleType("ndk_library", NdkLibraryFactory) - android.RegisterModuleType("versioned_ndk_headers", versionedNdkHeadersFactory) - android.RegisterModuleType("preprocessed_ndk_headers", preprocessedNdkHeadersFactory) - android.RegisterSingletonType("ndk", NdkSingleton) - + RegisterNdkModuleTypes(android.InitRegistrationContext) pctx.Import("android/soong/android") } +func RegisterNdkModuleTypes(ctx android.RegistrationContext) { + ctx.RegisterModuleType("ndk_headers", ndkHeadersFactory) + ctx.RegisterModuleType("ndk_library", NdkLibraryFactory) + ctx.RegisterModuleType("versioned_ndk_headers", versionedNdkHeadersFactory) + ctx.RegisterModuleType("preprocessed_ndk_headers", preprocessedNdkHeadersFactory) + ctx.RegisterSingletonType("ndk", NdkSingleton) +} + func getNdkInstallBase(ctx android.PathContext) android.InstallPath { return android.PathForNdkInstall(ctx) }