| // Copyright 2024 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 ( |
| "bytes" |
| "encoding/csv" |
| "fmt" |
| "slices" |
| "strconv" |
| "strings" |
| |
| "github.com/google/blueprint" |
| "github.com/google/blueprint/gobtools" |
| ) |
| |
| var ( |
| // Constants of property names used in compliance metadata of modules |
| ComplianceMetadataProp = struct { |
| NAME string |
| PACKAGE string |
| MODULE_TYPE string |
| OS string |
| ARCH string |
| IS_PRIMARY_ARCH string |
| VARIANT string |
| IS_STATIC_LIB string |
| INSTALLED_FILES string |
| BUILT_FILES string |
| STATIC_DEPS string |
| STATIC_DEP_FILES string |
| WHOLE_STATIC_DEPS string |
| WHOLE_STATIC_DEP_FILES string |
| LICENSES string |
| |
| // module_type=package |
| PKG_DEFAULT_APPLICABLE_LICENSES string |
| |
| // module_type=license |
| LIC_LICENSE_KINDS string |
| LIC_LICENSE_TEXT string |
| LIC_PACKAGE_NAME string |
| |
| // module_type=license_kind |
| LK_CONDITIONS string |
| LK_URL string |
| }{ |
| "name", |
| "package", |
| "module_type", |
| "os", |
| "arch", |
| "is_primary_arch", |
| "variant", |
| "is_static_lib", |
| "installed_files", |
| "built_files", |
| "static_deps", |
| "static_dep_files", |
| "whole_static_deps", |
| "whole_static_dep_files", |
| "licenses", |
| |
| "pkg_default_applicable_licenses", |
| |
| "lic_license_kinds", |
| "lic_license_text", |
| "lic_package_name", |
| |
| "lk_conditions", |
| "lk_url", |
| } |
| |
| // A constant list of all property names in compliance metadata |
| // Order of properties here is the order of columns in the exported CSV file. |
| COMPLIANCE_METADATA_PROPS = []string{ |
| ComplianceMetadataProp.NAME, |
| ComplianceMetadataProp.PACKAGE, |
| ComplianceMetadataProp.MODULE_TYPE, |
| ComplianceMetadataProp.OS, |
| ComplianceMetadataProp.ARCH, |
| ComplianceMetadataProp.VARIANT, |
| ComplianceMetadataProp.IS_STATIC_LIB, |
| ComplianceMetadataProp.IS_PRIMARY_ARCH, |
| // Space separated installed files |
| ComplianceMetadataProp.INSTALLED_FILES, |
| // Space separated built files |
| ComplianceMetadataProp.BUILT_FILES, |
| // Space separated module names of static dependencies |
| ComplianceMetadataProp.STATIC_DEPS, |
| // Space separated file paths of static dependencies |
| ComplianceMetadataProp.STATIC_DEP_FILES, |
| // Space separated module names of whole static dependencies |
| ComplianceMetadataProp.WHOLE_STATIC_DEPS, |
| // Space separated file paths of whole static dependencies |
| ComplianceMetadataProp.WHOLE_STATIC_DEP_FILES, |
| ComplianceMetadataProp.LICENSES, |
| // module_type=package |
| ComplianceMetadataProp.PKG_DEFAULT_APPLICABLE_LICENSES, |
| // module_type=license |
| ComplianceMetadataProp.LIC_LICENSE_KINDS, |
| ComplianceMetadataProp.LIC_LICENSE_TEXT, // resolve to file paths |
| ComplianceMetadataProp.LIC_PACKAGE_NAME, |
| // module_type=license_kind |
| ComplianceMetadataProp.LK_CONDITIONS, |
| ComplianceMetadataProp.LK_URL, |
| } |
| ) |
| |
| // ComplianceMetadataInfo provides all metadata of a module, e.g. name, module type, package, license, |
| // dependencies, built/installed files, etc. It is a wrapper on a map[string]string with some utility |
| // methods to get/set properties' values. |
| type ComplianceMetadataInfo struct { |
| properties map[string]string |
| } |
| |
| type complianceMetadataInfoGob struct { |
| Properties map[string]string |
| } |
| |
| func NewComplianceMetadataInfo() *ComplianceMetadataInfo { |
| return &ComplianceMetadataInfo{ |
| properties: map[string]string{}, |
| } |
| } |
| |
| func (m *ComplianceMetadataInfo) ToGob() *complianceMetadataInfoGob { |
| return &complianceMetadataInfoGob{ |
| Properties: m.properties, |
| } |
| } |
| |
| func (m *ComplianceMetadataInfo) FromGob(data *complianceMetadataInfoGob) { |
| m.properties = data.Properties |
| } |
| |
| func (c *ComplianceMetadataInfo) GobEncode() ([]byte, error) { |
| return gobtools.CustomGobEncode[complianceMetadataInfoGob](c) |
| } |
| |
| func (c *ComplianceMetadataInfo) GobDecode(data []byte) error { |
| return gobtools.CustomGobDecode[complianceMetadataInfoGob](data, c) |
| } |
| |
| func (c *ComplianceMetadataInfo) SetStringValue(propertyName string, value string) { |
| if !slices.Contains(COMPLIANCE_METADATA_PROPS, propertyName) { |
| panic(fmt.Errorf("Unknown metadata property: %s.", propertyName)) |
| } |
| c.properties[propertyName] = value |
| } |
| |
| func (c *ComplianceMetadataInfo) SetListValue(propertyName string, value []string) { |
| c.SetStringValue(propertyName, strings.TrimSpace(strings.Join(value, " "))) |
| } |
| |
| func (c *ComplianceMetadataInfo) getStringValue(propertyName string) string { |
| if !slices.Contains(COMPLIANCE_METADATA_PROPS, propertyName) { |
| panic(fmt.Errorf("Unknown metadata property: %s.", propertyName)) |
| } |
| return c.properties[propertyName] |
| } |
| |
| func (c *ComplianceMetadataInfo) getAllValues() map[string]string { |
| return c.properties |
| } |
| |
| var ( |
| ComplianceMetadataProvider = blueprint.NewProvider[*ComplianceMetadataInfo]() |
| ) |
| |
| // buildComplianceMetadataProvider starts with the ModuleContext.ComplianceMetadataInfo() and fills in more common metadata |
| // for different module types without accessing their private fields but through android.Module interface |
| // and public/private fields of package android. The final metadata is stored to a module's ComplianceMetadataProvider. |
| func buildComplianceMetadataProvider(ctx *moduleContext, m *ModuleBase) { |
| complianceMetadataInfo := ctx.ComplianceMetadataInfo() |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.NAME, m.Name()) |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.PACKAGE, ctx.ModuleDir()) |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.MODULE_TYPE, ctx.ModuleType()) |
| |
| switch ctx.ModuleType() { |
| case "license": |
| licenseModule := m.module.(*licenseModule) |
| complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LIC_LICENSE_KINDS, licenseModule.properties.License_kinds) |
| complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LIC_LICENSE_TEXT, PathsForModuleSrc(ctx, licenseModule.properties.License_text).Strings()) |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.LIC_PACKAGE_NAME, String(licenseModule.properties.Package_name)) |
| case "license_kind": |
| licenseKindModule := m.module.(*licenseKindModule) |
| complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LK_CONDITIONS, licenseKindModule.properties.Conditions) |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.LK_URL, licenseKindModule.properties.Url) |
| default: |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.OS, ctx.Os().String()) |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.ARCH, ctx.Arch().String()) |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.IS_PRIMARY_ARCH, strconv.FormatBool(ctx.PrimaryArch())) |
| complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.VARIANT, ctx.ModuleSubDir()) |
| if m.primaryLicensesProperty != nil && m.primaryLicensesProperty.getName() == "licenses" { |
| complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LICENSES, m.primaryLicensesProperty.getStrings()) |
| } |
| |
| var installed InstallPaths |
| installed = append(installed, ctx.installFiles...) |
| installed = append(installed, ctx.katiInstalls.InstallPaths()...) |
| installed = append(installed, ctx.katiSymlinks.InstallPaths()...) |
| installed = append(installed, ctx.katiInitRcInstalls.InstallPaths()...) |
| installed = append(installed, ctx.katiVintfInstalls.InstallPaths()...) |
| complianceMetadataInfo.SetListValue(ComplianceMetadataProp.INSTALLED_FILES, FirstUniqueStrings(installed.Strings())) |
| } |
| ctx.setProvider(ComplianceMetadataProvider, complianceMetadataInfo) |
| } |
| |
| func init() { |
| RegisterComplianceMetadataSingleton(InitRegistrationContext) |
| } |
| |
| func RegisterComplianceMetadataSingleton(ctx RegistrationContext) { |
| ctx.RegisterParallelSingletonType("compliance_metadata_singleton", complianceMetadataSingletonFactory) |
| } |
| |
| var ( |
| // sqlite3 command line tool |
| sqlite3 = pctx.HostBinToolVariable("sqlite3", "sqlite3") |
| |
| // Command to import .csv files to sqlite3 database |
| importCsv = pctx.AndroidStaticRule("importCsv", |
| blueprint.RuleParams{ |
| Command: `rm -rf $out && ` + |
| `${sqlite3} $out ".import --csv $in modules" && ` + |
| `${sqlite3} $out ".import --csv ${make_metadata} make_metadata" && ` + |
| `${sqlite3} $out ".import --csv ${make_modules} make_modules"`, |
| CommandDeps: []string{"${sqlite3}"}, |
| }, "make_metadata", "make_modules") |
| ) |
| |
| func complianceMetadataSingletonFactory() Singleton { |
| return &complianceMetadataSingleton{} |
| } |
| |
| type complianceMetadataSingleton struct { |
| } |
| |
| func writerToCsv(csvWriter *csv.Writer, row []string) { |
| err := csvWriter.Write(row) |
| if err != nil { |
| panic(err) |
| } |
| } |
| |
| // Collect compliance metadata from all Soong modules, write to a CSV file and |
| // import compliance metadata from Make and Soong to a sqlite3 database. |
| func (c *complianceMetadataSingleton) GenerateBuildActions(ctx SingletonContext) { |
| if !ctx.Config().HasDeviceProduct() { |
| return |
| } |
| var buffer bytes.Buffer |
| csvWriter := csv.NewWriter(&buffer) |
| |
| // Collect compliance metadata of modules in Soong and write to out/soong/compliance-metadata/<product>/soong-modules.csv file. |
| columnNames := []string{"id"} |
| columnNames = append(columnNames, COMPLIANCE_METADATA_PROPS...) |
| writerToCsv(csvWriter, columnNames) |
| |
| rowId := -1 |
| ctx.VisitAllModules(func(module Module) { |
| if !module.Enabled(ctx) { |
| return |
| } |
| moduleType := ctx.ModuleType(module) |
| if moduleType == "package" { |
| metadataMap := map[string]string{ |
| ComplianceMetadataProp.NAME: ctx.ModuleName(module), |
| ComplianceMetadataProp.MODULE_TYPE: ctx.ModuleType(module), |
| ComplianceMetadataProp.PKG_DEFAULT_APPLICABLE_LICENSES: strings.Join(module.base().primaryLicensesProperty.getStrings(), " "), |
| } |
| rowId = rowId + 1 |
| metadata := []string{strconv.Itoa(rowId)} |
| for _, propertyName := range COMPLIANCE_METADATA_PROPS { |
| metadata = append(metadata, metadataMap[propertyName]) |
| } |
| writerToCsv(csvWriter, metadata) |
| return |
| } |
| if provider, ok := ctx.otherModuleProvider(module, ComplianceMetadataProvider); ok { |
| metadataInfo := provider.(*ComplianceMetadataInfo) |
| rowId = rowId + 1 |
| metadata := []string{strconv.Itoa(rowId)} |
| for _, propertyName := range COMPLIANCE_METADATA_PROPS { |
| metadata = append(metadata, metadataInfo.getStringValue(propertyName)) |
| } |
| writerToCsv(csvWriter, metadata) |
| return |
| } |
| }) |
| csvWriter.Flush() |
| |
| deviceProduct := ctx.Config().DeviceProduct() |
| modulesCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "soong-modules.csv") |
| WriteFileRuleVerbatim(ctx, modulesCsv, buffer.String()) |
| |
| // Metadata generated in Make |
| makeMetadataCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-metadata.csv") |
| makeModulesCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-modules.csv") |
| |
| // Import metadata from Make and Soong to sqlite3 database |
| complianceMetadataDb := PathForOutput(ctx, "compliance-metadata", deviceProduct, "compliance-metadata.db") |
| ctx.Build(pctx, BuildParams{ |
| Rule: importCsv, |
| Input: modulesCsv, |
| Implicits: []Path{ |
| makeMetadataCsv, |
| makeModulesCsv, |
| }, |
| Output: complianceMetadataDb, |
| Args: map[string]string{ |
| "make_metadata": makeMetadataCsv.String(), |
| "make_modules": makeModulesCsv.String(), |
| }, |
| }) |
| |
| // Phony rule "compliance-metadata.db". "m compliance-metadata.db" to create the compliance metadata database. |
| ctx.Build(pctx, BuildParams{ |
| Rule: blueprint.Phony, |
| Inputs: []Path{complianceMetadataDb}, |
| Output: PathForPhony(ctx, "compliance-metadata.db"), |
| }) |
| |
| } |