Merge "Define release flags in starlark instead of make"
diff --git a/core/Makefile b/core/Makefile
index 14adbb5..ccfa3f2 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -7303,17 +7303,9 @@
haiku: $(SOONG_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_FUZZ_TARGETS)
$(call dist-for-goals,haiku,$(SOONG_FUZZ_PACKAGING_ARCH_MODULES))
$(call dist-for-goals,haiku,$(PRODUCT_OUT)/module-info.json)
-
-.PHONY: haiku-java-device
-haiku-java-device: $(SOONG_JAVA_FUZZ_DEVICE_PACKAGING_ARCH_MODULES) $(ALL_JAVA_FUZZ_DEVICE_TARGETS)
-$(call dist-for-goals,haiku-java-device,$(SOONG_JAVA_FUZZ_DEVICE_PACKAGING_ARCH_MODULES))
-$(call dist-for-goals,haiku-java-device,$(PRODUCT_OUT)/module-info.json)
-
-.PHONY: haiku-java-host
-haiku-java-host: $(SOONG_JAVA_FUZZ_HOST_PACKAGING_ARCH_MODULES) $(ALL_JAVA_FUZZ_HOST_TARGETS)
-$(call dist-for-goals,haiku-java-host,$(SOONG_JAVA_FUZZ_HOST_PACKAGING_ARCH_MODULES))
-$(call dist-for-goals,haiku-java-host,$(PRODUCT_OUT)/module-info.json)
-
+.PHONY: haiku-java
+haiku-java: $(SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_JAVA_FUZZ_TARGETS)
+$(call dist-for-goals,haiku-java,$(SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES))
.PHONY: haiku-rust
haiku-rust: $(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_RUST_FUZZ_TARGETS)
$(call dist-for-goals,haiku-rust,$(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES))
diff --git a/core/config.mk b/core/config.mk
index 396aad0..e272389 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -271,7 +271,7 @@
# Ex: $(call add_soong_config_namespace,acme)
define add_soong_config_namespace
-$(eval SOONG_CONFIG_NAMESPACES += $1) \
+$(eval SOONG_CONFIG_NAMESPACES += $(strip $1)) \
$(eval SOONG_CONFIG_$(strip $1) :=)
endef
@@ -281,8 +281,8 @@
# $1 is the namespace. $2 is the list of variables.
# Ex: $(call add_soong_config_var,acme,COOL_FEATURE_A COOL_FEATURE_B)
define add_soong_config_var
-$(eval SOONG_CONFIG_$(strip $1) += $2) \
-$(foreach v,$(strip $2),$(eval SOONG_CONFIG_$(strip $1)_$v := $($v)))
+$(eval SOONG_CONFIG_$(strip $1) += $(strip $2)) \
+$(foreach v,$(strip $2),$(eval SOONG_CONFIG_$(strip $1)_$v := $(strip $($v))))
endef
# The add_soong_config_var_value function defines a make variable and also adds
@@ -291,7 +291,7 @@
# Ex: $(call add_soong_config_var_value,acme,COOL_FEATURE,true)
define add_soong_config_var_value
-$(eval $2 := $3) \
+$(eval $(strip $2) := $(strip $3)) \
$(call add_soong_config_var,$1,$2)
endef
@@ -299,8 +299,8 @@
#
# internal utility to define a namespace and a variable in it.
define soong_config_define_internal
-$(if $(filter $1,$(SOONG_CONFIG_NAMESPACES)),,$(eval SOONG_CONFIG_NAMESPACES:=$(SOONG_CONFIG_NAMESPACES) $1)) \
-$(if $(filter $2,$(SOONG_CONFIG_$(strip $1))),,$(eval SOONG_CONFIG_$(strip $1):=$(SOONG_CONFIG_$(strip $1)) $2))
+$(if $(filter $1,$(SOONG_CONFIG_NAMESPACES)),,$(eval SOONG_CONFIG_NAMESPACES:=$(SOONG_CONFIG_NAMESPACES) $(strip $1))) \
+$(if $(filter $2,$(SOONG_CONFIG_$(strip $1))),,$(eval SOONG_CONFIG_$(strip $1):=$(SOONG_CONFIG_$(strip $1)) $(strip $2)))
endef
# soong_config_set defines the variable in the given Soong config namespace
@@ -309,7 +309,7 @@
# Ex: $(call soong_config_set,acme,COOL_FEATURE,true)
define soong_config_set
$(call soong_config_define_internal,$1,$2) \
-$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$3)
+$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(strip $3))
endef
# soong_config_append appends to the value of the variable in the given Soong
@@ -318,7 +318,7 @@
# $1 is the namespace, $2 is the variable name, $3 is the value
define soong_config_append
$(call soong_config_define_internal,$1,$2) \
-$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(SOONG_CONFIG_$(strip $1)_$(strip $2)) $3)
+$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(SOONG_CONFIG_$(strip $1)_$(strip $2)) $(strip $3))
endef
# soong_config_append gets to the value of the variable in the given Soong
diff --git a/core/definitions.mk b/core/definitions.mk
index 6abd355..7697211 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -894,7 +894,8 @@
endef
###########################################################
-## Declare license dependencies $(2) for non-module target $(1)
+## Declare license dependencies $(2) with optional colon-separated
+## annotations for non-module target $(1)
###########################################################
define declare-license-deps
$(strip \
@@ -906,7 +907,8 @@
endef
###########################################################
-## Declare license dependencies $(2) for non-module container-type target $(1)
+## Declare license dependencies $(2) with optional colon-separated
+## annotations for non-module container-type target $(1)
##
## Container-type targets are targets like .zip files that
## merely aggregate other files.
diff --git a/core/main.mk b/core/main.mk
index 6a24bd3..cb4dca6 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -2163,10 +2163,11 @@
$(shell rm $(PRODUCT_OUT)/sbom-metadata.csv >/dev/null 2>&1)
$(PRODUCT_OUT)/sbom-metadata.csv: $(installed_files)
rm -f $@
- @echo installed_file$(comma)module_path$(comma)soong_module_type$(comma)is_prebuilt_make_module$(comma)product_copy_files$(comma)kernel_module_copy_files$(comma)is_platform_generated >> $@
+ @echo installed_file$(comma)module_path$(comma)soong_module_type$(comma)is_prebuilt_make_module$(comma)product_copy_files$(comma)kernel_module_copy_files$(comma)is_platform_generated,build_output_path >> $@
$(foreach f,$(installed_files),\
$(eval _module_name := $(ALL_INSTALLED_FILES.$f)) \
$(eval _path_on_device := $(patsubst $(PRODUCT_OUT)/%,%,$f)) \
+ $(eval _build_output_path := $(PRODUCT_OUT)/$(_path_on_device)) \
$(eval _module_path := $(strip $(sort $(ALL_MODULES.$(_module_name).PATH)))) \
$(eval _soong_module_type := $(strip $(sort $(ALL_MODULES.$(_module_name).SOONG_MODULE_TYPE)))) \
$(eval _is_prebuilt_make_module := $(ALL_MODULES.$(_module_name).IS_PREBUILT_MAKE_MODULE)) \
@@ -2184,9 +2185,9 @@
$(eval _is_linker_config := $(if $(findstring $f,$(SYSTEM_LINKER_CONFIG) $(vendor_linker_config_file)),Y)) \
$(eval _is_partition_compat_symlink := $(if $(findstring $f,$(PARTITION_COMPAT_SYMLINKS)),Y)) \
$(eval _is_platform_generated := $(_is_build_prop)$(_is_notice_file)$(_is_dexpreopt_image_profile)$(_is_product_system_other_avbkey)$(_is_event_log_tags_file)$(_is_system_other_odex_marker)$(_is_kernel_modules_blocklist)$(_is_fsverity_build_manifest_apk)$(_is_linker_config)$(_is_partition_compat_symlink)) \
- @echo /$(_path_on_device)$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated) >> $@ $(newline) \
+ @echo /$(_path_on_device)$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated)$(comma)$(_build_output_path) >> $@ $(newline) \
$(if $(_post_installed_dexpreopt_zip), \
- for i in $$(zipinfo -1 $(_post_installed_dexpreopt_zip)); do echo /$$i$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated) >> $@ ; done $(newline) \
+ for i in $$(zipinfo -1 $(_post_installed_dexpreopt_zip)); do echo /$$i$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated)$(comma)$(PRODUCT_OUT)/$$i >> $@ ; done $(newline) \
) \
)
@@ -2196,14 +2197,14 @@
$(PRODUCT_OUT)/sbom.spdx.json: $(PRODUCT_OUT)/sbom.spdx
$(PRODUCT_OUT)/sbom.spdx: $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
rm -rf $@
- $(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr="$(PRODUCT_MANUFACTURER)" --json
+ $(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --json
$(call dist-for-goals,droid,$(PRODUCT_OUT)/sbom.spdx.json:sbom/sbom.spdx.json)
else
apps_only_sbom_files := $(sort $(patsubst %,%.spdx.json,$(filter %.apk,$(apps_only_installed_files))))
$(apps_only_sbom_files): $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
rm -rf $@
- $(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr="$(PRODUCT_MANUFACTURER)" --unbundled
+ $(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --unbundled_apk
sbom: $(apps_only_sbom_files)
diff --git a/core/product_config.rbc b/core/product_config.rbc
index e594894..921f068 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -379,11 +379,7 @@
def _soong_config_set(g, nsname, var, value):
"""Assigns the value to the variable in the namespace."""
_soong_config_namespace(g, nsname)
- if type(value) == "string":
- # Trim right spaces, because in make the variable is set in an $(eval),
- # which will ignore trailing spaces.
- value = value.rstrip(" ")
- g[_soong_config_namespaces_key][nsname][var]=value
+ g[_soong_config_namespaces_key][nsname][var]=_mkstrip(value)
def _soong_config_append(g, nsname, var, value):
"""Appends to the value of the variable in the namespace."""
@@ -391,9 +387,9 @@
ns = g[_soong_config_namespaces_key][nsname]
oldv = ns.get(var)
if oldv == None:
- ns[var] = value
+ ns[var] = _mkstrip(value)
else:
- ns[var] += " " + value
+ ns[var] += " " + _mkstrip(value)
def _soong_config_get(g, nsname, var):
diff --git a/tools/aconfig/src/cache.rs b/tools/aconfig/src/cache.rs
index 7b6edc5..c546f7b 100644
--- a/tools/aconfig/src/cache.rs
+++ b/tools/aconfig/src/cache.rs
@@ -52,8 +52,9 @@
}
impl Cache {
- pub fn new(namespace: String) -> Cache {
- Cache { namespace, items: vec![] }
+ pub fn new(namespace: String) -> Result<Cache> {
+ ensure!(!namespace.is_empty(), "empty namespace");
+ Ok(Cache { namespace, items: vec![] })
}
pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
@@ -69,6 +70,8 @@
source: Source,
declaration: FlagDeclaration,
) -> Result<()> {
+ ensure!(!declaration.name.is_empty(), "empty flag name");
+ ensure!(!declaration.description.is_empty(), "empty flag description");
ensure!(
self.items.iter().all(|item| item.name != declaration.name),
"failed to declare flag {} from {}: flag already declared",
@@ -91,6 +94,8 @@
}
pub fn add_flag_value(&mut self, source: Source, value: FlagValue) -> Result<()> {
+ ensure!(!value.namespace.is_empty(), "empty flag namespace");
+ ensure!(!value.name.is_empty(), "empty flag name");
ensure!(
value.namespace == self.namespace,
"failed to set values for flag {}/{} from {}: expected namespace {}",
@@ -119,6 +124,11 @@
pub fn into_iter(self) -> impl Iterator<Item = Item> {
self.items.into_iter()
}
+
+ pub fn namespace(&self) -> &str {
+ debug_assert!(!self.namespace.is_empty());
+ &self.namespace
+ }
}
#[cfg(test)]
@@ -128,7 +138,7 @@
#[test]
fn test_add_flag_declaration() {
- let mut cache = Cache::new("ns".to_string());
+ let mut cache = Cache::new("ns".to_string()).unwrap();
cache
.add_flag_declaration(
Source::File("first.txt".to_string()),
@@ -154,7 +164,7 @@
item.state == expected.0 && item.permission == expected.1
}
- let mut cache = Cache::new("ns".to_string());
+ let mut cache = Cache::new("ns".to_string()).unwrap();
let error = cache
.add_flag_value(
Source::Memory,
@@ -220,4 +230,67 @@
assert_eq!(&format!("{:?}", error), "failed to set values for flag some-other-namespace/foo from <memory>: expected namespace ns");
assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
}
+
+ #[test]
+ fn test_reject_empty_cache_namespace() {
+ Cache::new("".to_string()).unwrap_err();
+ }
+
+ #[test]
+ fn test_reject_empty_flag_declaration_fields() {
+ let mut cache = Cache::new("ns".to_string()).unwrap();
+
+ let error = cache
+ .add_flag_declaration(
+ Source::Memory,
+ FlagDeclaration { name: "".to_string(), description: "Description".to_string() },
+ )
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "empty flag name");
+
+ let error = cache
+ .add_flag_declaration(
+ Source::Memory,
+ FlagDeclaration { name: "foo".to_string(), description: "".to_string() },
+ )
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "empty flag description");
+ }
+
+ #[test]
+ fn test_reject_empty_flag_value_files() {
+ let mut cache = Cache::new("ns".to_string()).unwrap();
+ cache
+ .add_flag_declaration(
+ Source::Memory,
+ FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
+ )
+ .unwrap();
+
+ let error = cache
+ .add_flag_value(
+ Source::Memory,
+ FlagValue {
+ namespace: "".to_string(),
+ name: "foo".to_string(),
+ state: FlagState::Enabled,
+ permission: Permission::ReadOnly,
+ },
+ )
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "empty flag namespace");
+
+ let error = cache
+ .add_flag_value(
+ Source::Memory,
+ FlagValue {
+ namespace: "ns".to_string(),
+ name: "".to_string(),
+ state: FlagState::Enabled,
+ permission: Permission::ReadOnly,
+ },
+ )
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "empty flag name");
+ }
}
diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen_cpp.rs
new file mode 100644
index 0000000..cb266f1
--- /dev/null
+++ b/tools/aconfig/src/codegen_cpp.rs
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+use anyhow::Result;
+use serde::Serialize;
+use tinytemplate::TinyTemplate;
+
+use crate::aconfig::{FlagState, Permission};
+use crate::cache::{Cache, Item};
+use crate::commands::OutputFile;
+
+pub fn generate_cpp_code(cache: &Cache) -> Result<OutputFile> {
+ let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
+ let readwrite = class_elements.iter().any(|item| item.readwrite);
+ let namespace = cache.namespace().to_lowercase();
+ let context = Context { namespace: namespace.clone(), readwrite, class_elements };
+ let mut template = TinyTemplate::new();
+ template.add_template("cpp_code_gen", include_str!("../templates/cpp.template"))?;
+ let contents = template.render("cpp_code_gen", &context)?;
+ let path = ["aconfig", &(namespace + ".h")].iter().collect();
+ Ok(OutputFile { contents: contents.into(), path })
+}
+
+#[derive(Serialize)]
+struct Context {
+ pub namespace: String,
+ pub readwrite: bool,
+ pub class_elements: Vec<ClassElement>,
+}
+
+#[derive(Serialize)]
+struct ClassElement {
+ pub readwrite: bool,
+ pub default_value: String,
+ pub flag_name: String,
+}
+
+fn create_class_element(item: &Item) -> ClassElement {
+ ClassElement {
+ readwrite: item.permission == Permission::ReadWrite,
+ default_value: if item.state == FlagState::Enabled {
+ "true".to_string()
+ } else {
+ "false".to_string()
+ },
+ flag_name: item.name.clone(),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::aconfig::{FlagDeclaration, FlagState, FlagValue, Permission};
+ use crate::commands::Source;
+
+ #[test]
+ fn test_cpp_codegen_build_time_flag_only() {
+ let namespace = "my_namespace";
+ let mut cache = Cache::new(namespace.to_string()).unwrap();
+ cache
+ .add_flag_declaration(
+ Source::File("aconfig_one.txt".to_string()),
+ FlagDeclaration {
+ name: "my_flag_one".to_string(),
+ description: "buildtime disable".to_string(),
+ },
+ )
+ .unwrap();
+ cache
+ .add_flag_value(
+ Source::Memory,
+ FlagValue {
+ namespace: namespace.to_string(),
+ name: "my_flag_one".to_string(),
+ state: FlagState::Disabled,
+ permission: Permission::ReadOnly,
+ },
+ )
+ .unwrap();
+ cache
+ .add_flag_declaration(
+ Source::File("aconfig_two.txt".to_string()),
+ FlagDeclaration {
+ name: "my_flag_two".to_string(),
+ description: "buildtime enable".to_string(),
+ },
+ )
+ .unwrap();
+ cache
+ .add_flag_value(
+ Source::Memory,
+ FlagValue {
+ namespace: namespace.to_string(),
+ name: "my_flag_two".to_string(),
+ state: FlagState::Enabled,
+ permission: Permission::ReadOnly,
+ },
+ )
+ .unwrap();
+ let expect_content = r#"#ifndef my_namespace_HEADER_H
+ #define my_namespace_HEADER_H
+ #include "my_namespace.h"
+
+ namespace my_namespace {
+
+ class my_flag_one {
+ public:
+ virtual const bool value() {
+ return false;
+ }
+ }
+
+ class my_flag_two {
+ public:
+ virtual const bool value() {
+ return true;
+ }
+ }
+
+ }
+ #endif
+ "#;
+ let file = generate_cpp_code(&cache).unwrap();
+ assert_eq!("aconfig/my_namespace.h", file.path.to_str().unwrap());
+ assert_eq!(
+ expect_content.replace(' ', ""),
+ String::from_utf8(file.contents).unwrap().replace(' ', "")
+ );
+ }
+
+ #[test]
+ fn test_cpp_codegen_runtime_flag() {
+ let namespace = "my_namespace";
+ let mut cache = Cache::new(namespace.to_string()).unwrap();
+ cache
+ .add_flag_declaration(
+ Source::File("aconfig_one.txt".to_string()),
+ FlagDeclaration {
+ name: "my_flag_one".to_string(),
+ description: "buildtime disable".to_string(),
+ },
+ )
+ .unwrap();
+ cache
+ .add_flag_declaration(
+ Source::File("aconfig_two.txt".to_string()),
+ FlagDeclaration {
+ name: "my_flag_two".to_string(),
+ description: "runtime enable".to_string(),
+ },
+ )
+ .unwrap();
+ cache
+ .add_flag_value(
+ Source::Memory,
+ FlagValue {
+ namespace: namespace.to_string(),
+ name: "my_flag_two".to_string(),
+ state: FlagState::Enabled,
+ permission: Permission::ReadWrite,
+ },
+ )
+ .unwrap();
+ let expect_content = r#"#ifndef my_namespace_HEADER_H
+ #define my_namespace_HEADER_H
+ #include "my_namespace.h"
+
+ #include <server_configurable_flags/get_flags.h>
+ using namespace server_configurable_flags;
+
+ namespace my_namespace {
+
+ class my_flag_one {
+ public:
+ virtual const bool value() {
+ return GetServerConfigurableFlag(
+ "my_namespace",
+ "my_flag_one",
+ "false") == "true";
+ }
+ }
+
+ class my_flag_two {
+ public:
+ virtual const bool value() {
+ return GetServerConfigurableFlag(
+ "my_namespace",
+ "my_flag_two",
+ "true") == "true";
+ }
+ }
+
+ }
+ #endif
+ "#;
+ let file = generate_cpp_code(&cache).unwrap();
+ assert_eq!("aconfig/my_namespace.h", file.path.to_str().unwrap());
+ assert_eq!(
+ expect_content.replace(' ', ""),
+ String::from_utf8(file.contents).unwrap().replace(' ', "")
+ );
+ }
+}
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index bbf1272..476a89d 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -16,6 +16,7 @@
use anyhow::Result;
use serde::Serialize;
+use std::path::PathBuf;
use tinytemplate::TinyTemplate;
use crate::aconfig::{FlagState, Permission};
@@ -25,14 +26,14 @@
pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> {
let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
let readwrite = class_elements.iter().any(|item| item.readwrite);
- let namespace = uppercase_first_letter(
- cache.iter().find(|item| !item.namespace.is_empty()).unwrap().namespace.as_str(),
- );
- let context = Context { namespace: namespace.clone(), readwrite, class_elements };
+ let namespace = cache.namespace();
+ let context = Context { namespace: namespace.to_string(), readwrite, class_elements };
let mut template = TinyTemplate::new();
template.add_template("java_code_gen", include_str!("../templates/java.template"))?;
let contents = template.render("java_code_gen", &context)?;
- let path = ["com", "android", "internal", "aconfig", &(namespace + ".java")].iter().collect();
+ let mut path: PathBuf = namespace.split('.').collect();
+ // TODO: Allow customization of the java class name
+ path.push("Flags.java");
Ok(OutputFile { contents: contents.into(), path })
}
@@ -66,21 +67,6 @@
}
}
-fn uppercase_first_letter(s: &str) -> String {
- s.chars()
- .enumerate()
- .map(
- |(index, ch)| {
- if index == 0 {
- ch.to_ascii_uppercase()
- } else {
- ch.to_ascii_lowercase()
- }
- },
- )
- .collect()
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -89,8 +75,8 @@
#[test]
fn test_generate_java_code() {
- let namespace = "TeSTFlaG";
- let mut cache = Cache::new(namespace.to_string());
+ let namespace = "com.example";
+ let mut cache = Cache::new(namespace.to_string()).unwrap();
cache
.add_flag_declaration(
Source::File("test.txt".to_string()),
@@ -120,11 +106,11 @@
},
)
.unwrap();
- let expect_content = r#"package com.android.internal.aconfig;
+ let expect_content = r#"package com.example;
import android.provider.DeviceConfig;
- public final class Testflag {
+ public final class Flags {
public static boolean test() {
return false;
@@ -132,7 +118,7 @@
public static boolean test2() {
return DeviceConfig.getBoolean(
- "Testflag",
+ "com.example",
"test2__test2",
false
);
@@ -141,7 +127,7 @@
}
"#;
let file = generate_java_code(&cache).unwrap();
- assert_eq!("com/android/internal/aconfig/Testflag.java", file.path.to_str().unwrap());
+ assert_eq!("com/example/Flags.java", file.path.to_str().unwrap());
assert_eq!(
expect_content.replace(' ', ""),
String::from_utf8(file.contents).unwrap().replace(' ', "")
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 475d9b8..0bdb0b5 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -24,6 +24,7 @@
use crate::aconfig::{FlagDeclarations, FlagValue};
use crate::cache::Cache;
+use crate::codegen_cpp::generate_cpp_code;
use crate::codegen_java::generate_java_code;
use crate::protos::ProtoParsedFlags;
@@ -58,7 +59,7 @@
declarations: Vec<Input>,
values: Vec<Input>,
) -> Result<Cache> {
- let mut cache = Cache::new(namespace.to_owned());
+ let mut cache = Cache::new(namespace.to_owned())?;
for mut input in declarations {
let mut contents = String::new();
@@ -91,34 +92,38 @@
Ok(cache)
}
-pub fn generate_code(cache: &Cache) -> Result<OutputFile> {
+pub fn create_java_lib(cache: &Cache) -> Result<OutputFile> {
generate_java_code(cache)
}
+pub fn create_cpp_lib(cache: &Cache) -> Result<OutputFile> {
+ generate_cpp_code(cache)
+}
+
#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
-pub enum Format {
+pub enum DumpFormat {
Text,
Debug,
Protobuf,
}
-pub fn dump_cache(cache: Cache, format: Format) -> Result<Vec<u8>> {
+pub fn dump_cache(cache: Cache, format: DumpFormat) -> Result<Vec<u8>> {
match format {
- Format::Text => {
+ DumpFormat::Text => {
let mut lines = vec![];
for item in cache.iter() {
lines.push(format!("{}: {:?}\n", item.name, item.state));
}
Ok(lines.concat().into())
}
- Format::Debug => {
+ DumpFormat::Debug => {
let mut lines = vec![];
for item in cache.iter() {
lines.push(format!("{:?}\n", item));
}
Ok(lines.concat().into())
}
- Format::Protobuf => {
+ DumpFormat::Protobuf => {
let parsed_flags: ProtoParsedFlags = cache.into();
let mut output = vec![];
parsed_flags.write_to_vec(&mut output)?;
@@ -168,7 +173,7 @@
#[test]
fn test_dump_text_format() {
let cache = create_test_cache();
- let bytes = dump_cache(cache, Format::Text).unwrap();
+ let bytes = dump_cache(cache, DumpFormat::Text).unwrap();
let text = std::str::from_utf8(&bytes).unwrap();
assert!(text.contains("a: Disabled"));
}
@@ -179,7 +184,7 @@
use protobuf::Message;
let cache = create_test_cache();
- let bytes = dump_cache(cache, Format::Protobuf).unwrap();
+ let bytes = dump_cache(cache, DumpFormat::Protobuf).unwrap();
let actual = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
assert_eq!(
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 513e313..6db5948 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -18,6 +18,7 @@
use anyhow::{anyhow, ensure, Result};
use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
+use core::any::Any;
use std::fs;
use std::io;
use std::io::Write;
@@ -25,12 +26,13 @@
mod aconfig;
mod cache;
+mod codegen_cpp;
mod codegen_java;
mod commands;
mod protos;
use crate::cache::Cache;
-use commands::{Input, OutputFile, Source};
+use commands::{DumpFormat, Input, OutputFile, Source};
fn cli() -> Command {
Command::new("aconfig")
@@ -48,18 +50,32 @@
.arg(Arg::new("out").long("out").required(true)),
)
.subcommand(
+ Command::new("create-cpp-lib")
+ .arg(Arg::new("cache").long("cache").required(true))
+ .arg(Arg::new("out").long("out").required(true)),
+ )
+ .subcommand(
Command::new("dump")
.arg(Arg::new("cache").long("cache").required(true))
.arg(
Arg::new("format")
.long("format")
- .value_parser(EnumValueParser::<commands::Format>::new())
+ .value_parser(EnumValueParser::<commands::DumpFormat>::new())
.default_value("text"),
)
.arg(Arg::new("out").long("out").default_value("-")),
)
}
+fn get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T>
+where
+ T: Any + Clone + Send + Sync + 'static,
+{
+ matches
+ .get_one::<T>(arg_name)
+ .ok_or(anyhow!("internal error: required argument '{}' not found", arg_name))
+}
+
fn open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>> {
let mut opened_files = vec![];
for path in matches.get_many::<String>(arg_name).unwrap_or_default() {
@@ -89,30 +105,38 @@
let matches = cli().get_matches();
match matches.subcommand() {
Some(("create-cache", sub_matches)) => {
- let namespace = sub_matches.get_one::<String>("namespace").unwrap();
+ let namespace = get_required_arg::<String>(sub_matches, "namespace")?;
let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
let values = open_zero_or_more_files(sub_matches, "values")?;
let cache = commands::create_cache(namespace, declarations, values)?;
- let path = sub_matches.get_one::<String>("cache").unwrap();
+ let path = get_required_arg::<String>(sub_matches, "cache")?;
let file = fs::File::create(path)?;
cache.write_to_writer(file)?;
}
Some(("create-java-lib", sub_matches)) => {
- let path = sub_matches.get_one::<String>("cache").unwrap();
+ let path = get_required_arg::<String>(sub_matches, "cache")?;
let file = fs::File::open(path)?;
let cache = Cache::read_from_reader(file)?;
- let dir = PathBuf::from(sub_matches.get_one::<String>("out").unwrap());
- let generated_file = commands::generate_code(&cache).unwrap();
+ let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
+ let generated_file = commands::create_java_lib(&cache)?;
+ write_output_file_realtive_to_dir(&dir, &generated_file)?;
+ }
+ Some(("create-cpp-lib", sub_matches)) => {
+ let path = get_required_arg::<String>(sub_matches, "cache")?;
+ let file = fs::File::open(path)?;
+ let cache = Cache::read_from_reader(file)?;
+ let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
+ let generated_file = commands::create_cpp_lib(&cache)?;
write_output_file_realtive_to_dir(&dir, &generated_file)?;
}
Some(("dump", sub_matches)) => {
- let path = sub_matches.get_one::<String>("cache").unwrap();
+ let path = get_required_arg::<String>(sub_matches, "cache")?;
let file = fs::File::open(path)?;
let cache = Cache::read_from_reader(file)?;
- let format = sub_matches.get_one("format").unwrap();
+ let format = get_required_arg::<DumpFormat>(sub_matches, "format")?;
let output = commands::dump_cache(cache, *format)?;
- let path = sub_matches.get_one::<String>("out").unwrap();
- let mut file: Box<dyn Write> = if path == "-" {
+ let path = get_required_arg::<String>(sub_matches, "out")?;
+ let mut file: Box<dyn Write> = if *path == "-" {
Box::new(io::stdout())
} else {
Box::new(fs::File::create(path)?)
diff --git a/tools/aconfig/templates/cpp.template b/tools/aconfig/templates/cpp.template
new file mode 100644
index 0000000..ae8b59f
--- /dev/null
+++ b/tools/aconfig/templates/cpp.template
@@ -0,0 +1,25 @@
+#ifndef {namespace}_HEADER_H
+#define {namespace}_HEADER_H
+#include "{namespace}.h"
+{{ if readwrite }}
+#include <server_configurable_flags/get_flags.h>
+using namespace server_configurable_flags;
+{{ endif }}
+namespace {namespace} \{
+ {{ for item in class_elements}}
+ class {item.flag_name} \{
+ public:
+ virtual const bool value() \{
+ {{ if item.readwrite- }}
+ return GetServerConfigurableFlag(
+ "{namespace}",
+ "{item.flag_name}",
+ "{item.default_value}") == "true";
+ {{ -else- }}
+ return {item.default_value};
+ {{ -endif }}
+ }
+ }
+ {{ endfor }}
+}
+#endif
diff --git a/tools/aconfig/templates/java.template b/tools/aconfig/templates/java.template
index ebcd607..89da18b 100644
--- a/tools/aconfig/templates/java.template
+++ b/tools/aconfig/templates/java.template
@@ -1,8 +1,8 @@
-package com.android.internal.aconfig;
+package {namespace};
{{ if readwrite }}
import android.provider.DeviceConfig;
{{ endif }}
-public final class {namespace} \{
+public final class Flags \{
{{ for item in class_elements}}
public static boolean {item.method_name}() \{
{{ if item.readwrite- }}
diff --git a/tools/releasetools/merge_ota.py b/tools/releasetools/merge_ota.py
index 7d3d3a3..441312c 100644
--- a/tools/releasetools/merge_ota.py
+++ b/tools/releasetools/merge_ota.py
@@ -14,6 +14,7 @@
import argparse
import logging
+import shlex
import struct
import sys
import update_payload
@@ -34,6 +35,7 @@
logger = logging.getLogger(__name__)
CARE_MAP_ENTRY = "care_map.pb"
+APEX_INFO_ENTRY = "apex_info.pb"
def WriteDataBlob(payload: Payload, outfp: BinaryIO, read_size=1024*64):
@@ -188,6 +190,22 @@
f"OTA {partition_to_ota[part].name} and {payload.name} have duplicating partition {part}")
partition_to_ota[part] = payload
+def ApexInfo(file_paths):
+ if len(file_paths) > 1:
+ logger.info("More than one target file specified, will ignore "
+ "apex_info.pb (if any)")
+ return None
+ with zipfile.ZipFile(file_paths[0], "r", allowZip64=True) as zfp:
+ if APEX_INFO_ENTRY in zfp.namelist():
+ apex_info_bytes = zfp.read(APEX_INFO_ENTRY)
+ return apex_info_bytes
+ return None
+
+def ParseSignerArgs(args):
+ if args is None:
+ return None
+ return shlex.split(args)
+
def main(argv):
parser = argparse.ArgumentParser(description='Merge multiple partial OTAs')
parser.add_argument('packages', type=str, nargs='+',
@@ -196,6 +214,13 @@
help='Paths to private key for signing payload')
parser.add_argument('--search_path', type=str,
help='Search path for framework/signapk.jar')
+ parser.add_argument('--payload_signer', type=str,
+ help='Path to custom payload signer')
+ parser.add_argument('--payload_signer_args', type=ParseSignerArgs,
+ help='Arguments for payload signer if necessary')
+ parser.add_argument('--payload_signer_maximum_signature_size', type=str,
+ help='Maximum signature size (in bytes) that would be '
+ 'generated by the given payload signer')
parser.add_argument('--output', type=str,
help='Paths to output merged ota', required=True)
parser.add_argument('--metadata_ota', type=str,
@@ -203,6 +228,9 @@
parser.add_argument('--private_key_suffix', type=str,
help='Suffix to be appended to package_key path', default=".pk8")
parser.add_argument('-v', action="store_true", help="Enable verbose logging", dest="verbose")
+ parser.epilog = ('This tool can also be used to resign a regular OTA. For a single regular OTA, '
+ 'apex_info.pb will be written to output. When merging multiple OTAs, '
+ 'apex_info.pb will not be written.')
args = parser.parse_args(argv[1:])
file_paths = args.packages
@@ -225,6 +253,13 @@
merged_manifest = MergeManifests(payloads)
+ # Get signing keys
+ key_passwords = common.GetKeyPasswords([args.package_key])
+
+ generator = PayloadGenerator()
+
+ apex_info_bytes = ApexInfo(file_paths)
+
with tempfile.NamedTemporaryFile() as unsigned_payload:
WriteHeaderAndManifest(merged_manifest, unsigned_payload)
ConcatBlobs(payloads, unsigned_payload)
@@ -236,20 +271,31 @@
if args.package_key:
logger.info("Signing payload...")
- signer = PayloadSigner(args.package_key, args.private_key_suffix)
+ # TODO: remove OPTIONS when no longer used as fallback in payload_signer
+ common.OPTIONS.payload_signer_args = None
+ common.OPTIONS.payload_signer_maximum_signature_size = None
+ signer = PayloadSigner(args.package_key, args.private_key_suffix,
+ key_passwords[args.package_key],
+ payload_signer=args.payload_signer,
+ payload_signer_args=args.payload_signer_args,
+ payload_signer_maximum_signature_size=args.payload_signer_maximum_signature_size)
generator.payload_file = unsigned_payload.name
generator.Sign(signer)
logger.info("Payload size: %d", os.path.getsize(generator.payload_file))
logger.info("Writing to %s", args.output)
+
key_passwords = common.GetKeyPasswords([args.package_key])
with tempfile.NamedTemporaryFile(prefix="signed_ota", suffix=".zip") as signed_ota:
with zipfile.ZipFile(signed_ota, "w") as zfp:
generator.WriteToZip(zfp)
care_map_bytes = MergeCareMap(args.packages)
if care_map_bytes:
- zfp.writestr(CARE_MAP_ENTRY, care_map_bytes)
+ common.ZipWriteStr(zfp, CARE_MAP_ENTRY, care_map_bytes)
+ if apex_info_bytes:
+ logger.info("Writing %s", APEX_INFO_ENTRY)
+ common.ZipWriteStr(zfp, APEX_INFO_ENTRY, apex_info_bytes)
AddOtaMetadata(signed_ota.name, metadata_ota,
args.output, args.package_key, key_passwords[args.package_key])
return 0
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 04ef5ef..afbe81a 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -525,8 +525,7 @@
def ParseInfoDict(target_file_path):
- with zipfile.ZipFile(target_file_path, 'r', allowZip64=True) as zfp:
- return common.LoadInfoDict(zfp)
+ return common.LoadInfoDict(target_file_path)
def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
diff --git a/tools/releasetools/payload_signer.py b/tools/releasetools/payload_signer.py
index 4f342ac..9933aef 100644
--- a/tools/releasetools/payload_signer.py
+++ b/tools/releasetools/payload_signer.py
@@ -36,11 +36,16 @@
(OPTIONS.package_key) and calls openssl for the signing works.
"""
- def __init__(self, package_key=None, private_key_suffix=None, pw=None, payload_signer=None):
+ def __init__(self, package_key=None, private_key_suffix=None, pw=None, payload_signer=None,
+ payload_signer_args=None, payload_signer_maximum_signature_size=None):
if package_key is None:
package_key = OPTIONS.package_key
if private_key_suffix is None:
private_key_suffix = OPTIONS.private_key_suffix
+ if payload_signer_args is None:
+ payload_signer_args = OPTIONS.payload_signer_args
+ if payload_signer_maximum_signature_size is None:
+ payload_signer_maximum_signature_size = OPTIONS.payload_signer_maximum_signature_size
if payload_signer is None:
# Prepare the payload signing key.
@@ -59,10 +64,10 @@
signing_key)
else:
self.signer = payload_signer
- self.signer_args = OPTIONS.payload_signer_args
- if OPTIONS.payload_signer_maximum_signature_size:
+ self.signer_args = payload_signer_args
+ if payload_signer_maximum_signature_size:
self.maximum_signature_size = int(
- OPTIONS.payload_signer_maximum_signature_size)
+ payload_signer_maximum_signature_size)
else:
# The legacy config uses RSA2048 keys.
logger.warning("The maximum signature size for payload signer is not"
diff --git a/tools/sbom/generate-sbom.py b/tools/sbom/generate-sbom.py
index 56509c9..2415f7e 100755
--- a/tools/sbom/generate-sbom.py
+++ b/tools/sbom/generate-sbom.py
@@ -19,7 +19,6 @@
Usage example:
generate-sbom.py --output_file out/target/product/vsoc_x86_64/sbom.spdx \
--metadata out/target/product/vsoc_x86_64/sbom-metadata.csv \
- --product_out_dir=out/target/product/vsoc_x86_64 \
--build_version $(cat out/target/product/vsoc_x86_64/build_fingerprint.txt) \
--product_mfr=Google
"""
@@ -89,11 +88,11 @@
parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Print more information.')
parser.add_argument('--output_file', required=True, help='The generated SBOM file in SPDX format.')
parser.add_argument('--metadata', required=True, help='The SBOM metadata file path.')
- parser.add_argument('--product_out_dir', required=True, help='The parent directory of all the installed files.')
parser.add_argument('--build_version', required=True, help='The build version.')
parser.add_argument('--product_mfr', required=True, help='The product manufacturer.')
parser.add_argument('--json', action='store_true', default=False, help='Generated SBOM file in SPDX JSON format')
- parser.add_argument('--unbundled', action='store_true', default=False, help='Generate SBOM file for unbundled module')
+ parser.add_argument('--unbundled_apk', action='store_true', default=False, help='Generate SBOM for unbundled APKs')
+ parser.add_argument('--unbundled_apex', action='store_true', default=False, help='Generate SBOM for unbundled APEXs')
return parser.parse_args()
@@ -127,7 +126,6 @@
def checksum(file_path):
- file_path = args.product_out_dir + '/' + file_path
h = hashlib.sha1()
if os.path.islink(file_path):
h.update(os.readlink(file_path).encode('utf-8'))
@@ -265,8 +263,8 @@
def get_sbom_fragments(installed_file_metadata, metadata_file_path):
"""Return SPDX fragment of source/prebuilt packages, which usually contains a SOURCE/PREBUILT
- package, a UPSTREAM package if it's a source package and a external SBOM document reference if
- it's a prebuilt package with sbom_ref defined in its METADATA file.
+ package, a UPSTREAM package and an external SBOM document reference if sbom_ref defined in its
+ METADATA file.
See go/android-spdx and go/android-sbom-gen for more details.
"""
@@ -303,25 +301,33 @@
prebuilt_package = sbom_data.Package(id=prebuilt_package_id,
name=name,
download_location=sbom_data.VALUE_NONE,
- version=args.build_version,
+ version=version if version else args.build_version,
supplier='Organization: ' + args.product_mfr)
- packages.append(prebuilt_package)
- if metadata_file_path:
- metadata_proto = metadata_file_protos[metadata_file_path]
- if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
- sbom_url = metadata_proto.third_party.sbom_ref.url
- sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
- upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
- if sbom_url and sbom_checksum and upstream_element_id:
- doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
- external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
- uri=sbom_url,
- checksum=sbom_checksum)
- relationships.append(
- sbom_data.Relationship(id1=prebuilt_package_id,
- relationship=sbom_data.RelationshipType.VARIANT_OF,
- id2=doc_ref_id + ':' + upstream_element_id))
+ upstream_package_id = new_package_id(name, PKG_UPSTREAM)
+ upstream_package = sbom_data.Package(id=upstream_package_id, name=name, version = version,
+ supplier=('Organization: ' + homepage) if homepage else sbom_data.VALUE_NOASSERTION,
+ download_location=download_location)
+ packages += [prebuilt_package, upstream_package]
+ relationships.append(sbom_data.Relationship(id1=prebuilt_package_id,
+ relationship=sbom_data.RelationshipType.VARIANT_OF,
+ id2=upstream_package_id))
+
+ if metadata_file_path:
+ metadata_proto = metadata_file_protos[metadata_file_path]
+ if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
+ sbom_url = metadata_proto.third_party.sbom_ref.url
+ sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
+ upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
+ if sbom_url and sbom_checksum and upstream_element_id:
+ doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
+ external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
+ uri=sbom_url,
+ checksum=sbom_checksum)
+ relationships.append(
+ sbom_data.Relationship(id1=upstream_package_id,
+ relationship=sbom_data.RelationshipType.VARIANT_OF,
+ id2=doc_ref_id + ':' + upstream_element_id))
return external_doc_ref, packages, relationships
@@ -334,9 +340,8 @@
return h.hexdigest()
-def save_report(report):
- prefix, _ = os.path.splitext(args.output_file)
- with open(prefix + '-gen-report.txt', 'w', encoding='utf-8') as report_file:
+def save_report(report_file_path, report):
+ with open(report_file_path, 'w', encoding='utf-8') as report_file:
for type, issues in report.items():
report_file.write(type + '\n')
for issue in issues:
@@ -394,7 +399,7 @@
installed_file_metadata['installed_file'], installed_file_metadata['module_path']))
-def generate_sbom_for_unbundled():
+def generate_sbom_for_unbundled_apk():
with open(args.metadata, newline='') as sbom_metadata_file:
reader = csv.DictReader(sbom_metadata_file)
doc = sbom_data.Document(name=args.build_version,
@@ -402,7 +407,7 @@
creators=['Organization: ' + args.product_mfr])
for installed_file_metadata in reader:
installed_file = installed_file_metadata['installed_file']
- if args.output_file != args.product_out_dir + installed_file + '.spdx.json':
+ if args.output_file != installed_file_metadata['build_output_path'] + '.spdx.json':
continue
module_path = installed_file_metadata['module_path']
@@ -412,7 +417,9 @@
version=args.build_version,
supplier='Organization: ' + args.product_mfr)
file_id = new_file_id(installed_file)
- file = sbom_data.File(id=file_id, name=installed_file, checksum=checksum(installed_file))
+ file = sbom_data.File(id=file_id,
+ name=installed_file,
+ checksum=checksum(installed_file_metadata['build_output_path']))
relationship = sbom_data.Relationship(id1=file_id,
relationship=sbom_data.RelationshipType.GENERATED_FROM,
id2=package_id)
@@ -435,24 +442,25 @@
args = get_args()
log('Args:', vars(args))
- if args.unbundled:
- generate_sbom_for_unbundled()
+ if args.unbundled_apk:
+ generate_sbom_for_unbundled_apk()
return
global metadata_file_protos
metadata_file_protos = {}
- doc = sbom_data.Document(name=args.build_version,
- namespace=f'https://www.google.com/sbom/spdx/android/{args.build_version}',
- creators=['Organization: ' + args.product_mfr])
-
product_package = sbom_data.Package(id=sbom_data.SPDXID_PRODUCT,
name=sbom_data.PACKAGE_NAME_PRODUCT,
download_location=sbom_data.VALUE_NONE,
version=args.build_version,
supplier='Organization: ' + args.product_mfr,
files_analyzed=True)
- doc.packages.append(product_package)
+
+ doc = sbom_data.Document(name=args.build_version,
+ namespace=f'https://www.google.com/sbom/spdx/android/{args.build_version}',
+ creators=['Organization: ' + args.product_mfr])
+ if not args.unbundled_apex:
+ doc.packages.append(product_package)
doc.packages.append(sbom_data.Package(id=sbom_data.SPDXID_PLATFORM,
name=sbom_data.PACKAGE_NAME_PLATFORM,
@@ -478,18 +486,21 @@
module_path = installed_file_metadata['module_path']
product_copy_files = installed_file_metadata['product_copy_files']
kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files']
+ build_output_path = installed_file_metadata['build_output_path']
if not installed_file_has_metadata(installed_file_metadata, report):
continue
- file_path = args.product_out_dir + '/' + installed_file
- if not (os.path.islink(file_path) or os.path.isfile(file_path)):
+ if not (os.path.islink(build_output_path) or os.path.isfile(build_output_path)):
report[ISSUE_INSTALLED_FILE_NOT_EXIST].append(installed_file)
continue
file_id = new_file_id(installed_file)
doc.files.append(
- sbom_data.File(id=file_id, name=installed_file, checksum=checksum(installed_file)))
- product_package.file_ids.append(file_id)
+ sbom_data.File(id=file_id, name=installed_file, checksum=checksum(build_output_path)))
+ if not args.unbundled_apex:
+ product_package.file_ids.append(file_id)
+ elif len(doc.files) > 1:
+ doc.add_relationship(sbom_data.Relationship(doc.files[0].id, sbom_data.RelationshipType.CONTAINS, file_id))
if is_source_package(installed_file_metadata) or is_prebuilt_package(installed_file_metadata):
metadata_file_path = get_metadata_file_path(installed_file_metadata)
@@ -533,16 +544,31 @@
relationship=sbom_data.RelationshipType.GENERATED_FROM,
id2=sbom_data.SPDXID_PLATFORM))
- product_package.verification_code = generate_package_verification_code(doc.files)
+ if not args.unbundled_apex:
+ product_package.verification_code = generate_package_verification_code(doc.files)
+
+ if args.unbundled_apex:
+ doc.describes = doc.files[0].id
# Save SBOM records to output file
doc.created = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
- with open(args.output_file, 'w', encoding="utf-8") as file:
- sbom_writers.TagValueWriter.write(doc, file)
+ prefix = args.output_file
+ if prefix.endswith('.spdx'):
+ prefix = prefix.removesuffix('.spdx')
+ elif prefix.endswith('.spdx.json'):
+ prefix = prefix.removesuffix('.spdx.json')
+
+ output_file = prefix + '.spdx'
+ if args.unbundled_apex:
+ output_file = prefix + '-fragment.spdx'
+ with open(output_file, 'w', encoding="utf-8") as file:
+ sbom_writers.TagValueWriter.write(doc, file, fragment=args.unbundled_apex)
if args.json:
- with open(args.output_file+'.json', 'w', encoding="utf-8") as file:
+ with open(prefix + '.spdx.json', 'w', encoding="utf-8") as file:
sbom_writers.JSONWriter.write(doc, file)
+ save_report(prefix + '-gen-report.txt', report)
+
if __name__ == '__main__':
main()
diff --git a/tools/sbom/sbom_data.py b/tools/sbom/sbom_data.py
index d2ef48d..14c4eb2 100644
--- a/tools/sbom/sbom_data.py
+++ b/tools/sbom/sbom_data.py
@@ -80,6 +80,7 @@
DESCRIBES = 'DESCRIBES'
VARIANT_OF = 'VARIANT_OF'
GENERATED_FROM = 'GENERATED_FROM'
+ CONTAINS = 'CONTAINS'
@dataclass
diff --git a/tools/sbom/sbom_writers.py b/tools/sbom/sbom_writers.py
index b1c66c5..85dee9d 100644
--- a/tools/sbom/sbom_writers.py
+++ b/tools/sbom/sbom_writers.py
@@ -110,24 +110,26 @@
return tagvalues
@staticmethod
- def marshal_described_element(sbom_doc):
+ def marshal_described_element(sbom_doc, fragment):
if not sbom_doc.describes:
return None
product_package = [p for p in sbom_doc.packages if p.id == sbom_doc.describes]
if product_package:
tagvalues = TagValueWriter.marshal_package(product_package[0])
- tagvalues.append(
- f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
+ if not fragment:
+ tagvalues.append(
+ f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
tagvalues.append('')
return tagvalues
file = [f for f in sbom_doc.files if f.id == sbom_doc.describes]
if file:
- tagvalues = [
- f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}'
- ]
+ tagvalues = TagValueWriter.marshal_file(file[0])
+ if not fragment:
+ tagvalues.append(
+ f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
return tagvalues
@@ -180,6 +182,8 @@
def marshal_files(sbom_doc):
tagvalues = []
for file in sbom_doc.files:
+ if file.id == sbom_doc.describes:
+ continue
tagvalues += TagValueWriter.marshal_file(file)
return tagvalues
@@ -204,9 +208,9 @@
content = []
if not fragment:
content += TagValueWriter.marshal_doc_headers(sbom_doc)
- described_element = TagValueWriter.marshal_described_element(sbom_doc)
- if described_element:
- content += described_element
+ described_element = TagValueWriter.marshal_described_element(sbom_doc, fragment)
+ if described_element:
+ content += described_element
content += TagValueWriter.marshal_files(sbom_doc)
tagvalues, marshaled_relationships = TagValueWriter.marshal_packages(sbom_doc)
content += tagvalues