Skip to content

Commit 6238a1e

Browse files
vmagrometa-codesync[bot]
authored andcommitted
[antlir2][cad_stack] content-based tracking of stacks of objects
Summary: Build a new library crate `cad_stack` that is a simple object storage mechanism that uses a stack of directories as the store backend with only the top being mutable. This crate is generic, but is meant to serve as a friendly storage mechanism to bridge the gap between how buck2/RE stores action inputs+outputs and how antlir2 needs to have full BTRFS subvolumes that have historically been snapshots of other local-only subvolumes. The following diff stack uses this to build an RE-capable image build backend. Test Plan: ``` ❯ buck2 test fbcode//antlir/antlir2/cad_stack/... Buck UI: https://www.internalfb.com/buck2/ff7a9cd6-94e6-470b-af77-5ddbaa20c796 Tests finished: Pass 6. Fail 0. Fatal 0. Skip 0. Omit 0. Infra Failure 0. Build failure 0 ``` https://www.internalfb.com/intern/testinfra/testrun/11540474199761526 Reviewed By: sergeyfd Differential Revision: D92884086 fbshipit-source-id: c8540aa724a9cc9d5416297aafc166d6fa8ea05f
1 parent 8dc0b51 commit 6238a1e

File tree

5 files changed

+464
-0
lines changed

5 files changed

+464
-0
lines changed

antlir/antlir2/cad_stack/BUCK

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
load("//antlir/bzl:build_defs.bzl", "rust_library")
2+
3+
oncall("antlir")
4+
5+
rust_library(
6+
name = "cad_stack",
7+
srcs = glob(["src/**/*.rs"]),
8+
test_deps = ["tempfile"],
9+
deps = [
10+
"blake3",
11+
"cap-std",
12+
"nonempty",
13+
"serde",
14+
"serde_json",
15+
"thiserror",
16+
],
17+
)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
use std::fmt::Debug;
9+
use std::marker::PhantomData;
10+
11+
use serde::Deserialize;
12+
use serde::Serialize;
13+
14+
use crate::Object;
15+
use crate::Result;
16+
17+
/// All [Object]s are uniquely identified by a [Checksum]
18+
pub struct Checksum<T: Object> {
19+
hash: blake3::Hash,
20+
_phantom: PhantomData<fn() -> T>,
21+
}
22+
23+
// Manual Copy/Clone impls to avoid unnecessary `T: Copy/Clone` bounds
24+
// that #[derive] would add. The fields (blake3::Hash and PhantomData) are
25+
// unconditionally Copy regardless of T.
26+
impl<T: Object> Copy for Checksum<T> {}
27+
28+
impl<T: Object> Clone for Checksum<T> {
29+
fn clone(&self) -> Self {
30+
*self
31+
}
32+
}
33+
34+
impl<T: Object> PartialEq<Checksum<T>> for Checksum<T> {
35+
fn eq(&self, other: &Checksum<T>) -> bool {
36+
self.hash == other.hash
37+
}
38+
}
39+
40+
impl<T: Object> Eq for Checksum<T> {}
41+
42+
impl<T: Object> std::hash::Hash for Checksum<T> {
43+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
44+
self.hash.hash(state);
45+
}
46+
}
47+
48+
impl<T: Object> Checksum<T> {
49+
pub fn new(hash: blake3::Hash) -> Self {
50+
Self {
51+
hash,
52+
_phantom: PhantomData,
53+
}
54+
}
55+
56+
pub(crate) fn hex(&self) -> String {
57+
self.hash.to_hex().to_string()
58+
}
59+
}
60+
61+
impl<T: Object> Debug for Checksum<T> {
62+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63+
f.debug_tuple(&format!("Checksum<{}>", std::any::type_name::<T>()))
64+
.field(&self.hash.to_hex())
65+
.finish()
66+
}
67+
}
68+
69+
impl<'de, T: Object> Deserialize<'de> for Checksum<T> {
70+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
71+
where
72+
D: serde::Deserializer<'de>,
73+
{
74+
let hex = String::deserialize(deserializer)?;
75+
let hash = blake3::Hash::from_hex(hex)
76+
.map_err(|e| serde::de::Error::custom(format!("invalid hash: {e}")))?;
77+
Ok(Self {
78+
hash,
79+
_phantom: PhantomData,
80+
})
81+
}
82+
}
83+
84+
impl<T: Object> Serialize for Checksum<T> {
85+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86+
where
87+
S: serde::Serializer,
88+
{
89+
serializer.serialize_str(&self.hash.to_hex())
90+
}
91+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
mod checksum;
9+
mod object;
10+
mod object_store;
11+
12+
pub use checksum::Checksum;
13+
pub use object::Object;
14+
pub use object_store::ObjectStore;
15+
16+
#[derive(Debug, thiserror::Error)]
17+
pub enum Error {
18+
#[error("no such object '{0}'")]
19+
NotFound(String),
20+
#[error(transparent)]
21+
Io(#[from] std::io::Error),
22+
#[error(transparent)]
23+
Json(#[from] serde_json::Error),
24+
}
25+
26+
pub type Result<T, E = Error> = std::result::Result<T, E>;
27+
28+
#[doc(hidden)]
29+
pub mod __deps {
30+
pub use serde_json;
31+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
use std::fs::File;
9+
10+
use crate::Checksum;
11+
use crate::Result;
12+
13+
pub trait Object: Sized {
14+
fn from_file(file: File) -> Result<Self>;
15+
16+
fn to_file(&self, file: &mut File) -> Result<()>;
17+
18+
fn checksum(&self) -> Result<Checksum<Self>>;
19+
}
20+
21+
#[macro_export]
22+
macro_rules! json_object {
23+
($t:ty) => {
24+
impl $crate::Object for $t {
25+
fn from_file(file: std::fs::File) -> $crate::Result<Self> {
26+
$crate::__deps::serde_json::from_reader(std::io::BufReader::new(file))
27+
.map_err($crate::Error::from)
28+
}
29+
30+
fn to_file(&self, file: &mut std::fs::File) -> $crate::Result<()> {
31+
use std::io::Write;
32+
let mut w = std::io::BufWriter::new(file);
33+
$crate::__deps::serde_json::to_writer_pretty(&mut w, &self)
34+
.map_err($crate::Error::from)?;
35+
w.flush().map_err($crate::Error::from)
36+
}
37+
38+
fn checksum(&self) -> $crate::Result<$crate::Checksum<Self>> {
39+
let bytes = $crate::__deps::serde_json::to_vec_pretty(&self)?;
40+
Ok($crate::Checksum::new(blake3::hash(&bytes)))
41+
}
42+
}
43+
};
44+
}

0 commit comments

Comments
 (0)