Add ability to manually write to binary files.
I was updating the format of PackageTableHeader to add an additional field (and due to that change incremented the file version). This broke several tests under aconfig_storage_read_api and aconfig_storage_write_api that were operating on files written in the old schema. I tried to re-generate them using aconfig create-storage as explained in aosp/2933375, but was having some trouble. Figure if we can just update the files directly it will be easier to make updates in the future anyway. This isn't bypassing logic that's tested - IIUC the tests cover reading the file correctly (writing is covered in separate tests). Usage: $ aconfig-storage print --file=path/to/flag.map --type=flag_map --format=json > flag_map.json $ vim flag_map.json // Manually make updates $ aconfig-storage write-bytes --input-file=flag_map.json --output-file=path/to/flag.map --type=flag_map Change-Id: I212bf0b97483740b30130eb121acb895d350da84 Test: manual (adding debug-only tooling) + cargo t Bug: 316357686
This commit is contained in:
@@ -14,6 +14,7 @@ rust_defaults {
|
||||
"libclap",
|
||||
"libcxx",
|
||||
"libaconfig_storage_protos",
|
||||
"libserde",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -36,7 +37,10 @@ rust_binary_host {
|
||||
name: "aconfig-storage",
|
||||
defaults: ["aconfig_storage_file.defaults"],
|
||||
srcs: ["src/main.rs"],
|
||||
rustlibs: ["libaconfig_storage_file"],
|
||||
rustlibs: [
|
||||
"libaconfig_storage_file",
|
||||
"libserde_json",
|
||||
],
|
||||
}
|
||||
|
||||
rust_test_host {
|
||||
|
@@ -14,6 +14,8 @@ tempfile = "3.9.0"
|
||||
thiserror = "1.0.56"
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
cxx = "1.0"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.93"
|
||||
|
||||
[[bin]]
|
||||
name = "aconfig-storage"
|
||||
|
@@ -20,10 +20,11 @@
|
||||
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
||||
use crate::{AconfigStorageError, StorageFileType};
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
/// Flag info header struct
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct FlagInfoHeader {
|
||||
pub version: u32,
|
||||
pub container: String,
|
||||
@@ -89,7 +90,7 @@ impl FlagInfoHeader {
|
||||
}
|
||||
|
||||
/// bit field for flag info
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum FlagInfoBit {
|
||||
HasServerOverride = 1 << 0,
|
||||
IsReadWrite = 1 << 1,
|
||||
@@ -97,7 +98,7 @@ pub enum FlagInfoBit {
|
||||
}
|
||||
|
||||
/// Flag info node struct
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct FlagInfoNode {
|
||||
pub attributes: u8,
|
||||
}
|
||||
@@ -138,7 +139,7 @@ impl FlagInfoNode {
|
||||
}
|
||||
|
||||
/// Flag info list struct
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct FlagInfoList {
|
||||
pub header: FlagInfoHeader,
|
||||
pub nodes: Vec<FlagInfoNode>,
|
||||
|
@@ -23,10 +23,11 @@ use crate::{
|
||||
};
|
||||
use crate::{AconfigStorageError, StorageFileType, StoredFlagType};
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
/// Flag table header struct
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct FlagTableHeader {
|
||||
pub version: u32,
|
||||
pub container: String,
|
||||
@@ -95,7 +96,7 @@ impl FlagTableHeader {
|
||||
}
|
||||
|
||||
/// Flag table node struct
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct FlagTableNode {
|
||||
pub package_id: u32,
|
||||
pub flag_name: String,
|
||||
@@ -154,7 +155,7 @@ impl FlagTableNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct FlagTable {
|
||||
pub header: FlagTableHeader,
|
||||
pub buckets: Vec<Option<u32>>,
|
||||
|
@@ -20,10 +20,11 @@
|
||||
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
||||
use crate::{AconfigStorageError, StorageFileType};
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
/// Flag value header struct
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct FlagValueHeader {
|
||||
pub version: u32,
|
||||
pub container: String,
|
||||
@@ -89,7 +90,7 @@ impl FlagValueHeader {
|
||||
}
|
||||
|
||||
/// Flag value list struct
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct FlagValueList {
|
||||
pub header: FlagValueHeader,
|
||||
pub booleans: Vec<bool>,
|
||||
|
@@ -41,6 +41,7 @@ pub mod sip_hasher13;
|
||||
pub mod test_utils;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::fs::File;
|
||||
use std::hash::Hasher;
|
||||
@@ -107,7 +108,7 @@ impl TryFrom<u8> for StorageFileType {
|
||||
|
||||
/// Flag type enum as stored by storage file
|
||||
/// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum StoredFlagType {
|
||||
ReadWriteBoolean = 0,
|
||||
ReadOnlyBoolean = 1,
|
||||
|
@@ -20,9 +20,29 @@ use aconfig_storage_file::{
|
||||
list_flags, list_flags_with_info, read_file_to_bytes, AconfigStorageError, FlagInfoList,
|
||||
FlagTable, FlagValueList, PackageTable, StorageFileType,
|
||||
};
|
||||
|
||||
use clap::{builder::ArgAction, Arg, Command};
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
/**
|
||||
* Usage Examples
|
||||
*
|
||||
* Print file:
|
||||
* $ aconfig-storage print --file=path/to/flag.map --type=flag_map
|
||||
*
|
||||
* List flags:
|
||||
* $ aconfig-storage list --flag-map=path/to/flag.map \
|
||||
* --flag-val=path/to/flag.val --package-map=path/to/package.map
|
||||
*
|
||||
* Write binary file for testing:
|
||||
* $ aconfig-storage print --file=path/to/flag.map --type=flag_map --format=json > flag_map.json
|
||||
* $ vim flag_map.json // Manually make updates
|
||||
* $ aconfig-storage write-bytes --input-file=flag_map.json --output-file=path/to/flag.map --type=flag_map
|
||||
*/
|
||||
fn cli() -> Command {
|
||||
Command::new("aconfig-storage")
|
||||
.subcommand_required(true)
|
||||
@@ -34,7 +54,8 @@ fn cli() -> Command {
|
||||
.long("type")
|
||||
.required(true)
|
||||
.value_parser(|s: &str| StorageFileType::try_from(s)),
|
||||
),
|
||||
)
|
||||
.arg(Arg::new("format").long("format").required(false).action(ArgAction::Set)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("list")
|
||||
@@ -50,41 +71,75 @@ fn cli() -> Command {
|
||||
Arg::new("flag-info").long("flag-info").required(false).action(ArgAction::Set),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("write-bytes")
|
||||
// Where to write the output bytes. Suggest to use the StorageFileType names (e.g. flag.map).
|
||||
.arg(
|
||||
Arg::new("output-file")
|
||||
.long("output-file")
|
||||
.required(true)
|
||||
.action(ArgAction::Set),
|
||||
)
|
||||
// Input file should be json.
|
||||
.arg(
|
||||
Arg::new("input-file").long("input-file").required(true).action(ArgAction::Set),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("type")
|
||||
.long("type")
|
||||
.required(true)
|
||||
.value_parser(|s: &str| StorageFileType::try_from(s)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn print_storage_file(
|
||||
file_path: &str,
|
||||
file_type: &StorageFileType,
|
||||
as_json: bool,
|
||||
) -> Result<(), AconfigStorageError> {
|
||||
let bytes = read_file_to_bytes(file_path)?;
|
||||
match file_type {
|
||||
StorageFileType::PackageMap => {
|
||||
let package_table = PackageTable::from_bytes(&bytes)?;
|
||||
println!("{:?}", package_table);
|
||||
println!("{}", to_print_format(package_table, as_json));
|
||||
}
|
||||
StorageFileType::FlagMap => {
|
||||
let flag_table = FlagTable::from_bytes(&bytes)?;
|
||||
println!("{:?}", flag_table);
|
||||
println!("{}", to_print_format(flag_table, as_json));
|
||||
}
|
||||
StorageFileType::FlagVal => {
|
||||
let flag_value = FlagValueList::from_bytes(&bytes)?;
|
||||
println!("{:?}", flag_value);
|
||||
println!("{}", to_print_format(flag_value, as_json));
|
||||
}
|
||||
StorageFileType::FlagInfo => {
|
||||
let flag_info = FlagInfoList::from_bytes(&bytes)?;
|
||||
println!("{:?}", flag_info);
|
||||
println!("{}", to_print_format(flag_info, as_json));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_print_format<T>(file_contents: T, as_json: bool) -> String
|
||||
where
|
||||
T: Serialize + fmt::Debug,
|
||||
{
|
||||
if as_json {
|
||||
serde_json::to_string(&file_contents).unwrap()
|
||||
} else {
|
||||
format!("{:?}", file_contents)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), AconfigStorageError> {
|
||||
let matches = cli().get_matches();
|
||||
match matches.subcommand() {
|
||||
Some(("print", sub_matches)) => {
|
||||
let file_path = sub_matches.get_one::<String>("file").unwrap();
|
||||
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
|
||||
print_storage_file(file_path, file_type)?
|
||||
let format = sub_matches.get_one::<String>("format");
|
||||
let as_json: bool = format == Some(&"json".to_string());
|
||||
print_storage_file(file_path, file_type, as_json)?
|
||||
}
|
||||
Some(("list", sub_matches)) => {
|
||||
let package_map = sub_matches.get_one::<String>("package-map").unwrap();
|
||||
@@ -96,10 +151,10 @@ fn main() -> Result<(), AconfigStorageError> {
|
||||
let flags = list_flags_with_info(package_map, flag_map, flag_val, info_file)?;
|
||||
for flag in flags.iter() {
|
||||
println!(
|
||||
"{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
|
||||
flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
|
||||
flag.is_readwrite, flag.has_server_override, flag.has_local_override,
|
||||
);
|
||||
"{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
|
||||
flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
|
||||
flag.is_readwrite, flag.has_server_override, flag.has_local_override,
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@@ -113,6 +168,40 @@ fn main() -> Result<(), AconfigStorageError> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Converts JSON of the file into raw bytes (as is used on-device).
|
||||
// Intended to generate/easily update these files for testing.
|
||||
Some(("write-bytes", sub_matches)) => {
|
||||
let input_file_path = sub_matches.get_one::<String>("input-file").unwrap();
|
||||
let input_json = fs::read_to_string(input_file_path).unwrap();
|
||||
|
||||
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
|
||||
let output_bytes: Vec<u8>;
|
||||
match file_type {
|
||||
StorageFileType::FlagVal => {
|
||||
let list: FlagValueList = serde_json::from_str(&input_json).unwrap();
|
||||
output_bytes = list.into_bytes();
|
||||
}
|
||||
StorageFileType::FlagInfo => {
|
||||
let list: FlagInfoList = serde_json::from_str(&input_json).unwrap();
|
||||
output_bytes = list.into_bytes();
|
||||
}
|
||||
StorageFileType::FlagMap => {
|
||||
let table: FlagTable = serde_json::from_str(&input_json).unwrap();
|
||||
output_bytes = table.into_bytes();
|
||||
}
|
||||
StorageFileType::PackageMap => {
|
||||
let table: PackageTable = serde_json::from_str(&input_json).unwrap();
|
||||
output_bytes = table.into_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
let output_file_path = sub_matches.get_one::<String>("output-file").unwrap();
|
||||
let file = File::create(output_file_path);
|
||||
if file.is_err() {
|
||||
panic!("can't make file");
|
||||
}
|
||||
let _ = file.unwrap().write_all(&output_bytes);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
|
@@ -20,10 +20,11 @@
|
||||
use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
||||
use crate::{AconfigStorageError, StorageFileType};
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
/// Package table header struct
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct PackageTableHeader {
|
||||
pub version: u32,
|
||||
pub container: String,
|
||||
@@ -92,7 +93,7 @@ impl PackageTableHeader {
|
||||
}
|
||||
|
||||
/// Package table node struct
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct PackageTableNode {
|
||||
pub package_name: String,
|
||||
pub package_id: u32,
|
||||
@@ -151,7 +152,7 @@ impl PackageTableNode {
|
||||
}
|
||||
|
||||
/// Package table struct
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
pub struct PackageTable {
|
||||
pub header: PackageTableHeader,
|
||||
pub buckets: Vec<Option<u32>>,
|
||||
|
Reference in New Issue
Block a user