diff --git a/packages/client/src/file/builder.rs b/packages/client/src/file/builder.rs index cbe2942f6..ab4cd39cd 100644 --- a/packages/client/src/file/builder.rs +++ b/packages/client/src/file/builder.rs @@ -65,6 +65,7 @@ impl Builder { .collect(), executable: self.executable, module: self.module, + size: None, })) } } diff --git a/packages/client/src/graph/data.rs b/packages/client/src/graph/data.rs index a56bb2653..ac9f79f9b 100644 --- a/packages/client/src/graph/data.rs +++ b/packages/client/src/graph/data.rs @@ -155,6 +155,10 @@ pub struct File { #[serde(default, skip_serializing_if = "Option::is_none")] #[tangram_serialize(id = 3, default, skip_serializing_if = "Option::is_none")] pub module: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + #[tangram_serialize(id = 4, default, skip_serializing_if = "Option::is_none")] + pub size: Option, } #[serde_as] diff --git a/packages/client/src/graph/object.rs b/packages/client/src/graph/object.rs index a4d9e8682..3f9712719 100644 --- a/packages/client/src/graph/object.rs +++ b/packages/client/src/graph/object.rs @@ -48,6 +48,7 @@ pub struct File { pub dependencies: BTreeMap>, pub executable: bool, pub module: Option, + pub size: Option, } #[derive(Clone, Debug)] @@ -288,11 +289,13 @@ impl File { .collect(); let executable = self.executable; let module = self.module; + let size = self.size; tg::graph::data::File { contents, dependencies, executable, module, + size, } } @@ -319,11 +322,13 @@ impl File { .collect::>()?; let executable = data.executable; let module = data.module; + let size = data.size; let file = tg::graph::File { contents, dependencies, executable, module, + size, }; Ok(file) } diff --git a/packages/client/src/value/parse.rs b/packages/client/src/value/parse.rs index e69486de2..ec45d72e4 100644 --- a/packages/client/src/value/parse.rs +++ b/packages/client/src/value/parse.rs @@ -394,6 +394,7 @@ fn directory_node_entry(input: &mut Input) -> ModalResult ModalResult ModalResult { string .map(|s| { let bytes = Bytes::from(s.into_bytes()); + let size = bytes.len().to_u64().unwrap(); let leaf = tg::blob::object::Leaf { bytes }; let blob_object = tg::blob::Object::Leaf(leaf); let blob = tg::Blob::with_object(blob_object); @@ -436,6 +439,7 @@ fn file_string(input: &mut Input) -> ModalResult { dependencies: BTreeMap::new(), executable: false, module: None, + size: Some(size), }; let object = tg::file::Object::Node(node); tg::File::with_object(object).into() @@ -467,6 +471,7 @@ fn file_node(input: &mut Input) -> ModalResult { let mut dependencies = BTreeMap::new(); let mut executable = false; let mut module = None; + let mut size = None; for (key, value) in entries { match key.as_str() { "contents" => { @@ -519,6 +524,16 @@ fn file_node(input: &mut Input) -> ModalResult { .map_err(|_| tg::error!("expected a module kind"))?; module.replace(value); }, + "size" => { + let value = value + .try_unwrap_number_ref() + .map_err(|_| tg::error!("expected number for size"))?; + size = Some( + value + .to_u64() + .ok_or_else(|| tg::error!("size must be a non-negative integer"))?, + ); + }, _ => { return Err(tg::error!("unexpected field in file: {}", key)); }, @@ -530,6 +545,7 @@ fn file_node(input: &mut Input) -> ModalResult { dependencies, executable, module, + size, }; let object = tg::file::Object::Node(node); Ok(tg::File::with_object(object).into()) @@ -1096,6 +1112,7 @@ fn parse_graph_node(map: &tg::value::Map) -> tg::Result { let mut dependencies = BTreeMap::new(); let mut executable = false; let mut module = None; + let mut size = None; let mut artifact = None; let mut path = None; for (key, value) in map { @@ -1177,6 +1194,16 @@ fn parse_graph_node(map: &tg::value::Map) -> tg::Result { .map_err(|_| tg::error!("expected a module kind"))?; module.replace(value); }, + "size" => { + let value = value + .try_unwrap_number_ref() + .map_err(|_| tg::error!("expected number for size"))?; + size = Some( + value + .to_u64() + .ok_or_else(|| tg::error!("size must be a non-negative integer"))?, + ); + }, _ => { return Err(tg::error!("unexpected field in graph node: {}", key)); }, @@ -1196,6 +1223,7 @@ fn parse_graph_node(map: &tg::value::Map) -> tg::Result { dependencies, executable, module, + size, })) }, "symlink" => Ok(tg::graph::Node::Symlink(tg::graph::Symlink { diff --git a/packages/client/src/value/print.rs b/packages/client/src/value/print.rs index b1fcc5411..81a19983a 100644 --- a/packages/client/src/value/print.rs +++ b/packages/client/src/value/print.rs @@ -391,6 +391,9 @@ where if file.executable { self.map_entry("executable", |s| s.bool(file.executable))?; } + if let Some(size) = file.size { + self.map_entry("size", |s| s.number(size.to_f64().unwrap()))?; + } if let Some(module) = &file.module { self.map_entry("module", |s| s.string(&module.to_string()))?; } diff --git a/packages/clients/js/src/file.ts b/packages/clients/js/src/file.ts index 0703783aa..7b9a27f15 100644 --- a/packages/clients/js/src/file.ts +++ b/packages/clients/js/src/file.ts @@ -113,7 +113,8 @@ export class File { ); let executable = arg.executable ?? false; let module = arg.module; - let object = { contents, dependencies, executable, module }; + let size = arg.size ?? undefined; + let object = { contents, dependencies, executable, module, size }; return tg.File.withObject(object); } diff --git a/packages/clients/js/src/graph.ts b/packages/clients/js/src/graph.ts index 18fe1df61..fb011122b 100644 --- a/packages/clients/js/src/graph.ts +++ b/packages/clients/js/src/graph.ts @@ -83,12 +83,14 @@ export class Graph { ); let executable = node.executable ?? false; let module = node.module ?? undefined; + let size = node.size ?? undefined; return { kind: "file" as const, contents, dependencies, executable, module, + size, }; } else if (node.kind === "symlink") { let artifact = tg.Graph.Edge.fromArg(node.artifact, arg.nodes); @@ -195,6 +197,9 @@ export class Graph { if ("executable" in argNode) { node.executable = argNode.executable; } + if ("size" in argNode) { + node.size = argNode.size; + } nodes.push(node); } else if (argNode.kind === "symlink") { let artifact: tg.Graph.Arg.Edge | undefined; @@ -332,6 +337,7 @@ export namespace Graph { | undefined; executable?: boolean | undefined; module?: string | undefined; + size?: number | undefined; }; export type Symlink = { @@ -560,6 +566,7 @@ export namespace Graph { }; executable: boolean; module: string | undefined; + size: number | undefined; }; export namespace File { @@ -581,6 +588,12 @@ export namespace Graph { if (object.executable !== false) { data.executable = object.executable; } + if (object.module !== undefined) { + data.module = object.module; + } + if (object.size !== undefined) { + data.size = object.size; + } return data; }; @@ -609,6 +622,7 @@ export namespace Graph { ), executable: data.executable ?? false, module: data.module ?? undefined, + size: data.size ?? undefined, }; }; @@ -998,6 +1012,7 @@ export namespace Graph { }; executable?: boolean; module?: string; + size?: number; }; export type Symlink = { diff --git a/packages/js/src/tangram.d.ts b/packages/js/src/tangram.d.ts index 2626b188c..9d748ea34 100644 --- a/packages/js/src/tangram.d.ts +++ b/packages/js/src/tangram.d.ts @@ -496,6 +496,8 @@ declare namespace tg { } | undefined; executable?: boolean | undefined; + module?: string | undefined; + size?: number | undefined; }; type Symlink = { @@ -557,6 +559,8 @@ declare namespace tg { [reference: tg.Reference]: tg.Graph.Dependency | undefined; }; executable: boolean; + module: string | undefined; + size: number | undefined; }; type Symlink = { diff --git a/packages/server/src/checkin/artifact.rs b/packages/server/src/checkin/artifact.rs index f07ec3a12..29157faa6 100644 --- a/packages/server/src/checkin/artifact.rs +++ b/packages/server/src/checkin/artifact.rs @@ -198,11 +198,18 @@ impl Server { .collect::>()?; let executable = file.executable; let module = file.module; + let size = file.size.or_else(|| { + file.contents.as_ref().and_then(|contents| match contents { + Contents::Write(output) => Some(output.length), + Contents::Id { .. } => None, + }) + }); let data = tg::file::data::Node { contents: Some(contents), dependencies, executable, module, + size, }; tg::file::Data::Node(data).into() }, @@ -470,11 +477,18 @@ impl Server { .collect::>()?; let executable = file.executable; let module = file.module; + let size = file.size.or_else(|| { + file.contents.as_ref().and_then(|contents| match contents { + Contents::Write(output) => Some(output.length), + Contents::Id { .. } => None, + }) + }); let data = tg::graph::data::File { contents: Some(contents), dependencies, executable, module, + size, }; tg::graph::data::Node::File(data) }, @@ -911,6 +925,12 @@ impl Server { dependencies, executable: file.executable, module: file.module, + size: file.size.or_else(|| { + file.contents.as_ref().and_then(|contents| match contents { + Contents::Write(output) => Some(output.length), + Contents::Id { .. } => None, + }) + }), }) }, Variant::Directory(directory) => { diff --git a/packages/server/src/checkin/blob.rs b/packages/server/src/checkin/blob.rs index ffeffbcc6..508acb647 100644 --- a/packages/server/src/checkin/blob.rs +++ b/packages/server/src/checkin/blob.rs @@ -143,6 +143,7 @@ impl Server { .unwrap() .variant .unwrap_file_mut(); + file.size = Some(output.length); file.contents = Some(Contents::Write(Box::new(output))); } diff --git a/packages/server/src/checkin/graph.rs b/packages/server/src/checkin/graph.rs index f31def269..2f9b9ed2d 100644 --- a/packages/server/src/checkin/graph.rs +++ b/packages/server/src/checkin/graph.rs @@ -190,6 +190,7 @@ pub struct File { pub dependencies: BTreeMap>, pub executable: bool, pub module: Option, + pub size: Option, } #[derive(Clone, Debug)] diff --git a/packages/server/src/checkin/input.rs b/packages/server/src/checkin/input.rs index 45e9decaa..07fed09fe 100644 --- a/packages/server/src/checkin/input.rs +++ b/packages/server/src/checkin/input.rs @@ -228,6 +228,7 @@ impl Server { dependencies: BTreeMap::new(), executable: metadata.permissions().mode() & 0o111 != 0, module: None, + size: Some(metadata.len()), }) } else if metadata.is_symlink() { let path = std::fs::read_link(&item.path) diff --git a/packages/server/src/checkin/lock.rs b/packages/server/src/checkin/lock.rs index a4d4a9eaa..37c1a89d0 100644 --- a/packages/server/src/checkin/lock.rs +++ b/packages/server/src/checkin/lock.rs @@ -357,6 +357,7 @@ impl Server { dependencies, executable: file.executable, module: file.module, + size: file.size, }; (tg::graph::data::Node::File(data), children) }, diff --git a/packages/server/src/checkin/solve.rs b/packages/server/src/checkin/solve.rs index 803ab4f1b..0c3d2153b 100644 --- a/packages/server/src/checkin/solve.rs +++ b/packages/server/src/checkin/solve.rs @@ -893,11 +893,18 @@ impl Server { .collect(); let executable = file.executable; let module = file.module; + let size = file.size.or_else(|| { + contents.as_ref().and_then(|contents| match contents { + Contents::Write(output) => Some(output.length), + Contents::Id { .. } => None, + }) + }); Variant::File(File { contents, dependencies, executable, module, + size, }) }, tg::artifact::Data::Symlink(tg::symlink::Data::Node(symlink)) => { @@ -1063,11 +1070,18 @@ impl Server { ); } } + let size = file.size.or_else(|| { + contents.as_ref().and_then(|contents| match contents { + Contents::Write(output) => Some(output.length), + Contents::Id { .. } => None, + }) + }); Variant::File(File { contents, dependencies, executable: file.executable, module: file.module, + size, }) }, diff --git a/packages/server/src/checkout/lock.rs b/packages/server/src/checkout/lock.rs index 604292a1e..1b6224df9 100644 --- a/packages/server/src/checkout/lock.rs +++ b/packages/server/src/checkout/lock.rs @@ -315,6 +315,7 @@ impl Server { dependencies, executable: node.executable, module: node.module, + size: node.size, }; let node = tg::graph::data::Node::File(file); Ok(node) @@ -535,6 +536,7 @@ impl Server { dependencies, executable: node.executable, module: node.module, + size: node.size, }; let node = tg::graph::data::Node::File(file); Ok(node) diff --git a/packages/server/src/vfs/provider.rs b/packages/server/src/vfs/provider.rs index 87b1ac6f2..75be47469 100644 --- a/packages/server/src/vfs/provider.rs +++ b/packages/server/src/vfs/provider.rs @@ -1036,7 +1036,9 @@ impl Provider { match artifact { Some(artifact) if matches!(artifact.kind(), tg::artifact::Kind::File) => { let (file, _) = self.file_node_inner(artifact).await?; - let size = if let Some(contents) = file.contents.as_ref() { + let size = if let Some(size) = file.size { + size + } else if let Some(contents) = file.contents.as_ref() { self.blob_length_inner(contents).await? } else { 0 @@ -1747,9 +1749,13 @@ impl Provider { match artifact { Some(artifact) if matches!(artifact.kind(), tg::artifact::Kind::File) => { let (file, _) = self.file_node_sync_inner(artifact, transaction)?; - let size = file.contents.as_ref().map_or(Ok(0), |contents| { - self.blob_length_sync_inner(contents, transaction) - })?; + let size = if let Some(size) = file.size { + size + } else { + file.contents.as_ref().map_or(Ok(0), |contents| { + self.blob_length_sync_inner(contents, transaction) + })? + }; Ok(vfs::Attrs::new(vfs::FileType::File { executable: file.executable, size, diff --git a/packages/server/src/write.rs b/packages/server/src/write.rs index e0699151f..0303f619a 100644 --- a/packages/server/src/write.rs +++ b/packages/server/src/write.rs @@ -73,6 +73,7 @@ impl Server { dependencies: BTreeMap::new(), executable: false, module: None, + size: Some(blob.length), }); let id = tg::file::Id::new(&data.serialize()?); let path = self.cache_path().join(id.to_string());