diff --git a/packages/cli/src/artifact/checkout.rs b/packages/cli/src/artifact/checkout.rs index 76992374f..8612c0955 100644 --- a/packages/cli/src/artifact/checkout.rs +++ b/packages/cli/src/artifact/checkout.rs @@ -42,7 +42,7 @@ impl Cli { }; // Get the artifact. - let referent = self.get_reference(&args.reference).await?; + let (referent, _) = self.get_reference(&args.reference).await?; let Either::Right(object) = referent.item else { return Err(tg::error!("expected an object")); }; diff --git a/packages/cli/src/cat.rs b/packages/cli/src/cat.rs index b80743091..cf59ea6ff 100644 --- a/packages/cli/src/cat.rs +++ b/packages/cli/src/cat.rs @@ -14,7 +14,7 @@ impl Cli { pub async fn command_cat(&self, args: Args) -> tg::Result<()> { let handle = self.handle().await?; for reference in &args.references { - let referent = self.get_reference(reference).await?; + let (referent, _) = self.get_reference(reference).await?; let Either::Right(object) = referent.item else { return Err(tg::error!("expected an object")); }; diff --git a/packages/cli/src/checksum.rs b/packages/cli/src/checksum.rs index 76ae3aefa..8c1195a67 100644 --- a/packages/cli/src/checksum.rs +++ b/packages/cli/src/checksum.rs @@ -21,7 +21,7 @@ pub struct Args { impl Cli { pub async fn command_checksum(&self, args: Args) -> tg::Result<()> { let handle = self.handle().await?; - let referent = self.get_reference(&args.reference).await?; + let (referent, _) = self.get_reference(&args.reference).await?; let Either::Right(object) = referent.item else { return Err(tg::error!("expected an object")); }; diff --git a/packages/cli/src/config.rs b/packages/cli/src/config.rs index 873dc4768..77d07c43c 100644 --- a/packages/cli/src/config.rs +++ b/packages/cli/src/config.rs @@ -202,6 +202,10 @@ pub struct Build { #[serde(default, skip_serializing_if = "Option::is_none")] pub max_depth: Option, + /// Configure if path and tag are set for builds. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub set_path_and_tag: Option, + /// The remotes to build for. #[serde(default, skip_serializing_if = "Option::is_none")] pub remotes: Option>, diff --git a/packages/cli/src/get.rs b/packages/cli/src/get.rs index d9bf0a638..dbeb535b4 100644 --- a/packages/cli/src/get.rs +++ b/packages/cli/src/get.rs @@ -23,7 +23,7 @@ pub struct Args { impl Cli { pub async fn command_get(&self, args: Args) -> tg::Result<()> { let handle = self.handle().await?; - let referent = self.get_reference(&args.reference).await?; + let (referent, _) = self.get_reference(&args.reference).await?; eprintln!("{} item {}", "info".blue().bold(), referent.item); if let Some(path) = &referent.path { let path = path.display(); diff --git a/packages/cli/src/lib.rs b/packages/cli/src/lib.rs index c0d66f369..ce9f02a38 100644 --- a/packages/cli/src/lib.rs +++ b/packages/cli/src/lib.rs @@ -3,7 +3,7 @@ use crossterm::{style::Stylize as _, tty::IsTty as _}; use futures::FutureExt as _; use num::ToPrimitive as _; use std::{fmt::Write as _, path::PathBuf, sync::Mutex, time::Duration}; -use tangram_client::{self as tg, Client}; +use tangram_client::{self as tg, Client, Handle as _}; use tangram_either::Either; use tangram_server::Server; use tokio::io::AsyncWriteExt as _; @@ -190,7 +190,7 @@ impl Cli { Ok(config) => config, Err(error) => { eprintln!("{} failed to read the config", "error".red().bold()); - Cli::print_error(&error, None); + Cli::print_error(&error, None, None); return 1.into(); }, }; @@ -272,7 +272,7 @@ impl Cli { Ok(()) => 0.into(), Err(error) => { eprintln!("{} failed to run the command", "error".red().bold()); - Cli::print_error(&error, cli.config.as_ref()); + Cli::print_error(&error, cli.config.as_ref(), None); 1.into() }, }; @@ -896,7 +896,7 @@ impl Cli { Ok(()) } - fn print_error(error: &tg::Error, config: Option<&Config>) { + fn print_error(error: &tg::Error, config: Option<&Config>, source_map: Option<&tg::SourceMap>) { let options = config .as_ref() .and_then(|config| config.advanced.as_ref()) @@ -911,6 +911,9 @@ impl Cli { errors.reverse(); } for error in errors { + let error = source_map + .map(|map| map.convert_error(error.clone())) + .unwrap_or_else(|| error.clone()); let message = error.message.as_deref().unwrap_or("an error occurred"); eprintln!("{} {message}", "->".red()); if let Some(location) = &error.location { @@ -938,7 +941,10 @@ impl Cli { } } - fn print_diagnostic(diagnostic: &tg::Diagnostic) { + fn print_diagnostic(diagnostic: &tg::Diagnostic, source_map: Option<&tg::SourceMap>) { + let diagnostic = source_map + .map(|map| map.convert_diagnostic(diagnostic.clone())) + .unwrap_or_else(|| diagnostic.clone()); let title = match diagnostic.severity { tg::diagnostic::Severity::Error => "error".red().bold(), tg::diagnostic::Severity::Warning => "warning".yellow().bold(), @@ -1012,7 +1018,7 @@ impl Cli { async fn get_reference( &self, reference: &tg::Reference, - ) -> tg::Result>> { + ) -> tg::Result<(tg::Referent>, Option)> { let handle = self.handle().await?; let mut item = reference.item().clone(); let mut options = reference.options().cloned(); @@ -1025,8 +1031,22 @@ impl Cli { .map_err(|source| tg::error!(!source, "failed to get the absolute path"))?; } let reference = tg::Reference::with_item_and_options(&item, options.as_ref()); - let referent = reference.get(&handle).await?; - Ok(referent) + let output = handle + .try_get_reference(&reference) + .await? + .ok_or_else(|| tg::error!("failed to get reference"))?; + let item = output + .referent + .item + .map_left(tg::Build::with_id) + .map_right(tg::Object::with_id); + let referent = tg::Referent { + item, + path: output.referent.path, + subpath: output.referent.subpath, + tag: output.referent.tag, + }; + Ok((referent, output.lockfile)) } /// Initialize V8. diff --git a/packages/cli/src/package/check.rs b/packages/cli/src/package/check.rs index 3b8583b90..e56b148d3 100644 --- a/packages/cli/src/package/check.rs +++ b/packages/cli/src/package/check.rs @@ -29,11 +29,22 @@ impl Cli { .map(|option| option.unwrap_or_else(|| "default".to_owned())); // Get the reference. - let referent = self.get_reference(&args.reference).await?; + let (referent, lockfile) = self.get_reference(&args.reference).await?; let Either::Right(object) = referent.item else { return Err(tg::error!("expected an object")); }; + // Get the source map. + let source_map = if let Some(lockfile) = lockfile { + let lockfile = tg::Lockfile::try_read(&lockfile) + .await? + .ok_or_else(|| tg::error!(%path = lockfile.display(), "failed to read lockfile"))?; + let source_map = tg::SourceMap::new(&handle, &lockfile, object.clone()).await?; + Some(source_map) + } else { + None + }; + // Resolve the referent. let referent = if let Some(subpath) = &referent.subpath { let directory = object @@ -96,7 +107,7 @@ impl Cli { // Print the diagnostics. for diagnostic in &output.diagnostics { - Self::print_diagnostic(diagnostic); + Self::print_diagnostic(diagnostic, source_map.as_ref()); } if !output.diagnostics.is_empty() { diff --git a/packages/cli/src/package/document.rs b/packages/cli/src/package/document.rs index 308a2291b..cc460675f 100644 --- a/packages/cli/src/package/document.rs +++ b/packages/cli/src/package/document.rs @@ -43,7 +43,7 @@ impl Cli { } // Get the reference. - let referent = self.get_reference(&args.reference).await?; + let (referent, _) = self.get_reference(&args.reference).await?; let Either::Right(object) = referent.item else { return Err(tg::error!("expected an object")); }; diff --git a/packages/cli/src/progress.rs b/packages/cli/src/progress.rs index 28c6f8a09..62592d331 100644 --- a/packages/cli/src/progress.rs +++ b/packages/cli/src/progress.rs @@ -72,7 +72,7 @@ impl Cli { }, tg::progress::Event::Diagnostic(diagnostic) => { - Self::print_diagnostic(&diagnostic); + Self::print_diagnostic(&diagnostic, None); }, tg::progress::Event::Start(indicator) | tg::progress::Event::Update(indicator) => { diff --git a/packages/cli/src/pull.rs b/packages/cli/src/pull.rs index 30ebccc5a..e6f65b14c 100644 --- a/packages/cli/src/pull.rs +++ b/packages/cli/src/pull.rs @@ -27,7 +27,7 @@ impl Cli { let handle = self.handle().await?; // Get the reference. - let referent = self.get_reference(&args.reference).await?; + let (referent, _) = self.get_reference(&args.reference).await?; let item = match referent.item { Either::Left(build) => Either::Left(build), Either::Right(object) => { diff --git a/packages/cli/src/push.rs b/packages/cli/src/push.rs index ba92e00e8..22901888b 100644 --- a/packages/cli/src/push.rs +++ b/packages/cli/src/push.rs @@ -34,7 +34,7 @@ impl Cli { let remote = args.remote.unwrap_or_else(|| "default".to_owned()); // Get the reference. - let referent = self.get_reference(&args.reference).await?; + let (referent, _) = self.get_reference(&args.reference).await?; let item = match referent.item { Either::Left(build) => Either::Left(build), Either::Right(object) => { diff --git a/packages/cli/src/tag/put.rs b/packages/cli/src/tag/put.rs index 1dc75135b..3c0836491 100644 --- a/packages/cli/src/tag/put.rs +++ b/packages/cli/src/tag/put.rs @@ -30,7 +30,7 @@ impl Cli { .map(|option| option.unwrap_or_else(|| "default".to_owned())); // Get the reference. - let referent = self.get_reference(&args.reference).await?; + let (referent, _) = self.get_reference(&args.reference).await?; let item = match referent.item { Either::Left(build) => Either::Left(build), Either::Right(object) => { diff --git a/packages/cli/src/target/build.rs b/packages/cli/src/target/build.rs index a8048db4d..7cb5d7da1 100644 --- a/packages/cli/src/target/build.rs +++ b/packages/cli/src/target/build.rs @@ -178,7 +178,7 @@ impl Cli { } // Get the reference. - let referent = self.get_reference(&reference).await?; + let (referent, lockfile) = self.get_reference(&reference).await?; let Either::Right(object) = referent.item else { return Err(tg::error!("expected an object")); }; @@ -192,6 +192,17 @@ impl Cli { object }; + // Get the source map. + let (source_map, lock) = if let Some(lockfile) = lockfile { + let lockfile = tg::Lockfile::try_read(&lockfile) + .await? + .ok_or_else(|| tg::error!(%path = lockfile.display(), "failed to read lockfile"))?; + let source_map = tg::SourceMap::new(&handle, &lockfile, object.clone()).await?; + (Some(source_map), Some(lockfile)) + } else { + (None, None) + }; + // Create the target. let target = if let tg::Object::Target(target) = object { // If the object is a target, then use it. @@ -341,6 +352,7 @@ impl Cli { let id = target.id(&handle).await?; let arg = tg::target::build::Arg { create: args.create, + lock, parent: None, remote: remote.clone(), retry, @@ -408,7 +420,8 @@ impl Cli { expand_on_create: true, }; let item = crate::viewer::Item::Build(build); - let mut viewer = crate::viewer::Viewer::new(&handle, item, options); + let mut viewer = + crate::viewer::Viewer::new(&handle, item, options, source_map); match args.view { View::None => (), diff --git a/packages/cli/src/view.rs b/packages/cli/src/view.rs index 276117f33..8d426afb5 100644 --- a/packages/cli/src/view.rs +++ b/packages/cli/src/view.rs @@ -1,5 +1,5 @@ use crate::Cli; -use tangram_client as tg; +use tangram_client::{self as tg, handle::Ext as _}; use tangram_either::Either; use tangram_futures::task::Task; @@ -41,7 +41,7 @@ impl Cli { let handle = self.handle().await?; // Get the reference. - let referent = self.get_reference(&args.reference).await?; + let (referent, lockfile) = self.get_reference(&args.reference).await?; let item = match referent.item { Either::Left(build) => Either::Left(build), Either::Right(object) => { @@ -57,9 +57,37 @@ impl Cli { Either::Right(object) }, }; - let item = match item { - Either::Left(build) => crate::viewer::Item::Build(build), - Either::Right(object) => crate::viewer::Item::Value(object.into()), + let (item, source_map) = match item { + Either::Left(build) => { + let source_map = if let Some(lockfile) = handle.get_build(build.id()).await?.lock { + // Only attempt to load the target if the lock is set. + if let Some(executable) = + &*build.target(&handle).await?.executable(&handle).await? + { + let object = executable.object()[0].clone(); + let source_map = tg::SourceMap::new(&handle, &lockfile, object).await?; + Some(source_map) + } else { + None + } + } else { + None + }; + (crate::viewer::Item::Build(build), source_map) + }, + Either::Right(object) => { + // Get the source map. + let source_map = if let Some(lockfile) = lockfile { + let lockfile = tg::Lockfile::try_read(&lockfile).await?.ok_or_else( + || tg::error!(%path = lockfile.display(), "failed to read lockfile"), + )?; + let source_map = tg::SourceMap::new(&handle, &lockfile, object.clone()).await?; + Some(source_map) + } else { + None + }; + (crate::viewer::Item::Value(object.into()), source_map) + }, }; // Run the view. @@ -78,7 +106,7 @@ impl Cli { condensed_builds: false, expand_on_create: matches!(kind, Kind::Inline), }; - let mut viewer = crate::viewer::Viewer::new(&handle, item, options); + let mut viewer = crate::viewer::Viewer::new(&handle, item, options, source_map); match kind { Kind::Inline => { viewer.run_inline(stop).await?; diff --git a/packages/cli/src/viewer.rs b/packages/cli/src/viewer.rs index f65d43ae3..0b5a16cf2 100644 --- a/packages/cli/src/viewer.rs +++ b/packages/cli/src/viewer.rs @@ -145,7 +145,12 @@ where } } - pub fn new(handle: &H, item: Item, options: Options) -> Self { + pub fn new( + handle: &H, + item: Item, + options: Options, + source_map: Option, + ) -> Self { let (update_sender, update_receiver) = std::sync::mpsc::channel(); let data = Data::new(); let tree = Tree::new( @@ -154,6 +159,7 @@ where options, data.update_sender(), update_sender.clone(), + source_map, ); Self { data, diff --git a/packages/cli/src/viewer/tree.rs b/packages/cli/src/viewer/tree.rs index 4035a90e6..9106c2d49 100644 --- a/packages/cli/src/viewer/tree.rs +++ b/packages/cli/src/viewer/tree.rs @@ -38,6 +38,7 @@ struct Node { log_task: Option>, options: Rc, parent: Option>>, + source_map: Option>, title: String, update_receiver: NodeUpdateReceiver, update_sender: NodeUpdateSender, @@ -217,6 +218,7 @@ where let depth = parent.borrow().depth + 1; let (update_sender, update_receiver) = std::sync::mpsc::channel(); let options = parent.borrow().options.clone(); + let source_map = parent.borrow().source_map.clone(); let parent = Rc::downgrade(parent); let title = item.map_or(String::new(), |item| Self::item_title(item)); @@ -258,6 +260,7 @@ where log_task: None, options, parent: Some(parent), + source_map, title, update_receiver, update_sender, @@ -1323,8 +1326,11 @@ where options: Options, data: data::UpdateSender, viewer: super::UpdateSender, + source_map: Option, ) -> Self { let options = Rc::new(options); + let source_map = source_map.map(Rc::new); + let (update_sender, update_receiver) = std::sync::mpsc::channel(); let title = Self::item_title(&item); let expand_task = if options.expand_on_create { @@ -1367,6 +1373,7 @@ where log_task: None, options: options.clone(), parent: None, + source_map, title, update_receiver, update_sender, diff --git a/packages/client/src/artifact/checkin.rs b/packages/client/src/artifact/checkin.rs index 821d0ad6a..b476f6bdc 100644 --- a/packages/client/src/artifact/checkin.rs +++ b/packages/client/src/artifact/checkin.rs @@ -34,6 +34,7 @@ pub struct Arg { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct Output { pub artifact: tg::artifact::Id, + pub lockfile: Option, } impl tg::Artifact { diff --git a/packages/client/src/build/get.rs b/packages/client/src/build/get.rs index 90e9e5e37..474318219 100644 --- a/packages/client/src/build/get.rs +++ b/packages/client/src/build/get.rs @@ -18,6 +18,9 @@ pub struct Output { pub host: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub log: Option, diff --git a/packages/client/src/handle.rs b/packages/client/src/handle.rs index ea4a4d06e..eb01866a1 100644 --- a/packages/client/src/handle.rs +++ b/packages/client/src/handle.rs @@ -1,6 +1,5 @@ use crate as tg; use futures::{Future, Stream}; -use tangram_either::Either; use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite}; mod either; @@ -204,8 +203,7 @@ pub trait Handle: Clone + Unpin + Send + Sync + 'static { fn try_get_reference( &self, reference: &tg::Reference, - ) -> impl Future>>>> - + Send; + ) -> impl Future>> + Send; fn list_remotes( &self, diff --git a/packages/client/src/handle/either.rs b/packages/client/src/handle/either.rs index 3394be825..3a4c3c87d 100644 --- a/packages/client/src/handle/either.rs +++ b/packages/client/src/handle/either.rs @@ -391,8 +391,7 @@ where fn try_get_reference( &self, reference: &tg::Reference, - ) -> impl Future>>>> - + Send { + ) -> impl Future>> + Send { match self { Either::Left(s) => s.try_get_reference(reference).left_future(), Either::Right(s) => s.try_get_reference(reference).right_future(), diff --git a/packages/client/src/handle/ext.rs b/packages/client/src/handle/ext.rs index d166a0f58..195a2e322 100644 --- a/packages/client/src/handle/ext.rs +++ b/packages/client/src/handle/ext.rs @@ -9,7 +9,6 @@ use std::{ io::SeekFrom, sync::{Arc, Mutex}, }; -use tangram_either::Either; pub trait Ext: tg::Handle { fn try_read_blob( @@ -400,8 +399,7 @@ pub trait Ext: tg::Handle { fn get_reference( &self, reference: &tg::Reference, - ) -> impl Future>>> + Send - { + ) -> impl Future> + Send { self.try_get_reference(reference).map(|result| { result .and_then(|option| option.ok_or_else(|| tg::error!("failed to get the reference"))) diff --git a/packages/client/src/lib.rs b/packages/client/src/lib.rs index 4960eb40d..66e083100 100644 --- a/packages/client/src/lib.rs +++ b/packages/client/src/lib.rs @@ -40,6 +40,7 @@ pub use self::{ range::Range, reference::Reference, referent::Referent, + sourcemap::SourceMap, symlink::Handle as Symlink, tag::Tag, target::Handle as Target, @@ -78,6 +79,7 @@ pub mod reference; pub mod referent; pub mod remote; pub mod runtime; +pub mod sourcemap; pub mod symlink; pub mod tag; pub mod target; @@ -816,8 +818,7 @@ impl tg::Handle for Client { fn try_get_reference( &self, reference: &tg::Reference, - ) -> impl Future>>>> - + Send { + ) -> impl Future>> + Send { self.try_get_reference(reference) } diff --git a/packages/client/src/reference.rs b/packages/client/src/reference.rs index a42abeaf0..cc5491a0a 100644 --- a/packages/client/src/reference.rs +++ b/packages/client/src/reference.rs @@ -140,10 +140,9 @@ impl Reference { where H: tg::Handle, { - handle - .get_reference(self) - .await - .map(|referent| tg::Referent { + handle.get_reference(self).await.map(|output| { + let referent = output.referent; + tg::Referent { item: referent .item .map_left(tg::Build::with_id) @@ -151,7 +150,8 @@ impl Reference { path: referent.path, subpath: referent.subpath, tag: referent.tag, - }) + } + }) } } diff --git a/packages/client/src/reference/get.rs b/packages/client/src/reference/get.rs index 962964505..cf62f34df 100644 --- a/packages/client/src/reference/get.rs +++ b/packages/client/src/reference/get.rs @@ -1,12 +1,17 @@ use crate as tg; +use std::path::PathBuf; use tangram_either::Either; use tangram_http::{incoming::response::Ext as _, outgoing::request::Ext as _}; +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Output { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lockfile: Option, + pub referent: tg::Referent>, +} + impl tg::Client { - pub async fn try_get_reference( - &self, - reference: &tg::Reference, - ) -> tg::Result>>> { + pub async fn try_get_reference(&self, reference: &tg::Reference) -> tg::Result> { let method = http::Method::GET; let path = reference.uri().path(); let query = reference.uri().query().unwrap_or_default(); diff --git a/packages/client/src/sourcemap.rs b/packages/client/src/sourcemap.rs new file mode 100644 index 000000000..271fb1caf --- /dev/null +++ b/packages/client/src/sourcemap.rs @@ -0,0 +1,149 @@ +use crate as tg; +use std::{collections::BTreeMap, sync::Arc}; + +pub struct SourceMap { + objects: BTreeMap>, +} + +impl SourceMap { + pub async fn new(handle: &H, lockfile: &tg::Lockfile, object: tg::Object) -> tg::Result + where + H: tg::Handle, + { + let edge = tg::Referent { + item: object.clone(), + path: Some(".".into()), + subpath: None, + tag: None, + }; + let mut stack = vec![(edge, 0)]; + let mut visited = vec![false; lockfile.nodes.len()]; + let mut objects = BTreeMap::new(); + while let Some((edge, node)) = stack.pop() { + if visited[node] { + continue; + } + visited[node] = true; + objects.insert(edge.item.id(handle).await?, edge.clone()); + + let edges = get_edges(handle, &edge.item, node, lockfile).await?; + stack.extend(edges); + } + Ok(Self { objects }) + } + + pub fn lookup(&self, object: &tg::object::Id) -> Option> { + self.objects.get(object).cloned() + } + + pub fn convert_diagnostic(&self, mut diagnostic: tg::Diagnostic) -> tg::Diagnostic { + diagnostic.location = diagnostic.location.map(|mut location| { + location.module = self.convert_module(location.module); + location + }); + diagnostic + } + + pub fn convert_error(&self, mut error: tg::Error) -> tg::Error { + if let Some(location) = error.location.as_mut() { + if let tg::error::Source::Module(module) = &mut location.source { + *module = self.convert_module(module.clone()); + } + } + if let Some(source) = error.source.as_ref() { + let source = self.convert_error(source.as_ref().clone()); + error.source.replace(Arc::new(source)); + } + error + } + + pub fn convert_module(&self, mut module: tg::Module) -> tg::Module { + let tg::module::Item::Object(object) = &module.referent.item else { + return module; + }; + let Some(referent) = self.lookup(object) else { + return module; + }; + module.referent.path = referent.path; + module.referent.tag = referent.tag; + module + } +} + +async fn get_edges( + handle: &impl tg::Handle, + object: &tg::Object, + node: usize, + lockfile: &tg::Lockfile, +) -> tg::Result, usize)>> { + match (&lockfile.nodes[node], object) { + (tg::lockfile::Node::Directory(node), tg::Object::Directory(object)) => { + let entries = object.entries(handle).await?; + let edges = node + .entries + .iter() + .filter_map(|(name, value)| { + let node = value.as_ref().left().copied()?; + let item = entries.get(name)?.clone().into(); + let path = None; + let subpath = None; + let tag = None; + let referent = tg::Referent { + item, + path, + tag, + subpath, + }; + Some((referent, node)) + }) + .collect(); + Ok(edges) + }, + (tg::lockfile::Node::File(node), tg::Object::File(object)) => { + let dependencies = object.dependencies(handle).await?; + let edges = node + .dependencies + .iter() + .filter_map(|(reference, referent)| { + let node = referent.item.as_ref().left().copied()?; + let item = dependencies.get(reference)?.item.clone(); + let path = referent.path.clone(); + let subpath = referent.subpath.clone(); + let tag = referent.tag.clone(); + let referent = tg::Referent { + item, + path, + subpath, + tag, + }; + Some((referent, node)) + }) + .collect(); + Ok(edges) + }, + ( + tg::lockfile::Node::Symlink(tg::lockfile::Symlink::Artifact { artifact, .. }), + tg::Object::Symlink(object), + ) => { + let Some(node) = artifact.as_ref().left().copied() else { + return Ok(Vec::new()); + }; + let item = object + .artifact(handle) + .await? + .ok_or_else(|| tg::error!("expected an artifact"))? + .into(); + let path = None; + let subpath = None; + let tag = None; + let referent = tg::Referent { + item, + path, + tag, + subpath, + }; + Ok(vec![(referent, node)]) + }, + _ => Ok(Vec::new()), + } +} diff --git a/packages/client/src/target/build.rs b/packages/client/src/target/build.rs index 5a5aff5d1..47e4a3dcc 100644 --- a/packages/client/src/target/build.rs +++ b/packages/client/src/target/build.rs @@ -10,6 +10,9 @@ pub struct Arg { #[serde(default = "return_true", skip_serializing_if = "is_true")] pub create: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub lock: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub parent: Option, @@ -76,6 +79,7 @@ impl Default for Arg { fn default() -> Self { Self { create: true, + lock: None, parent: None, remote: None, retry: tg::build::Retry::default(), diff --git a/packages/server/src/artifact/checkin.rs b/packages/server/src/artifact/checkin.rs index 5c5dd3bda..eb2957b5c 100644 --- a/packages/server/src/artifact/checkin.rs +++ b/packages/server/src/artifact/checkin.rs @@ -78,7 +78,11 @@ impl Server { .ok_or_else(|| tg::error!("cannot check in the cache directory"))?? .parse()?; if path.components().count() == 1 { - let output = tg::artifact::checkin::Output { artifact: id }; + let lockfile = None; + let output = tg::artifact::checkin::Output { + artifact: id, + lockfile, + }; return Ok(output); } let path = path.components().skip(1).collect::(); @@ -89,7 +93,11 @@ impl Server { .ok_or_else(|| tg::error!("invalid path"))?; let artifact = directory.get(self, path).await?; let id = artifact.id(self).await?; - let output = tg::artifact::checkin::Output { artifact: id }; + let lockfile = None; + let output = tg::artifact::checkin::Output { + artifact: id, + lockfile, + }; return Ok(output); } @@ -135,14 +143,16 @@ impl Server { let artifact = output_graph.nodes[0].id.clone(); // If this is a non-destructive checkin, then attempt to write a lockfile. - if arg.lockfile && !arg.destructive && artifact.is_directory() { + let lockfile = if arg.lockfile && !arg.destructive && artifact.is_directory() { self.try_write_lockfile(&input_graph, &object_graph) .await - .map_err(|source| tg::error!(!source, "failed to write lockfile"))?; - } + .map_err(|source| tg::error!(!source, "failed to write lockfile"))? + } else { + None + }; // Create the output. - let output = tg::artifact::checkin::Output { artifact }; + let output = tg::artifact::checkin::Output { artifact, lockfile }; Ok(output) } diff --git a/packages/server/src/artifact/checkin/lockfile.rs b/packages/server/src/artifact/checkin/lockfile.rs index ee40bf97c..b8049a8bf 100644 --- a/packages/server/src/artifact/checkin/lockfile.rs +++ b/packages/server/src/artifact/checkin/lockfile.rs @@ -13,7 +13,7 @@ impl Server { &self, input: &input::Graph, object: &object::Graph, - ) -> tg::Result<()> { + ) -> tg::Result> { // Create the lockfile. let lockfile = self .create_lockfile(object, &input.nodes[0].arg.path) @@ -22,7 +22,7 @@ impl Server { // Skip empty lockfiles. if lockfile.nodes.is_empty() { - return Ok(()); + return Ok(None); }; // Get the path to the root of the input graph. @@ -57,7 +57,7 @@ impl Server { }, } - Ok(()) + Ok(Some(lockfile_path)) } async fn create_lockfile( diff --git a/packages/server/src/artifact/checkin/object.rs b/packages/server/src/artifact/checkin/object.rs index cc45b454c..559043b98 100644 --- a/packages/server/src/artifact/checkin/object.rs +++ b/packages/server/src/artifact/checkin/object.rs @@ -38,10 +38,10 @@ pub struct Edge { #[derive(Clone, Debug)] struct RemappedEdge { pub id: Either, - pub _path: Option, + pub path: Option, pub reference: tg::Reference, pub subpath: Option, - pub _tag: Option, + pub tag: Option, } impl Server { @@ -114,12 +114,13 @@ impl Server { paths, )) .await; + let tag = unify.nodes.get(&edge.referent).unwrap().tag.clone(); let edge = Edge { index: dependency_index, - path: None, + path: edge.path.clone(), reference: reference.clone(), subpath: edge.subpath.clone(), - tag: None, + tag, }; nodes[index].edges.push(edge); } @@ -314,10 +315,10 @@ impl Server { }; RemappedEdge { id, - _path: edge.path.clone(), + path: edge.path.clone(), reference: edge.reference.clone(), subpath: edge.subpath.clone(), - _tag: edge.tag.clone(), + tag: edge.tag.clone(), } }) .collect::>(); @@ -403,10 +404,10 @@ impl Server { }; RemappedEdge { id, - _path: edge.path.clone(), + path: edge.path.clone(), reference: edge.reference.clone(), subpath: edge.subpath.clone(), - _tag: edge.tag.clone(), + tag: edge.tag.clone(), } }) .collect::>(); @@ -459,14 +460,21 @@ impl Server { } => (contents, executable), }; // Compute the dependencies, which will be shared in all cases. + let set_path_and_tag = self + .config() + .build + .as_ref() + .map_or(false, |build| build.set_path_and_tag); let dependencies = edges .into_iter() .map(|edge| { + let path = set_path_and_tag.then(|| edge.path.clone()).flatten(); + let tag = set_path_and_tag.then(|| edge.tag.clone()).flatten(); let dependency = tg::Referent { item: edge.id, - path: None, + path, subpath: edge.subpath, - tag: None, + tag, }; (edge.reference, dependency) }) diff --git a/packages/server/src/build/get.rs b/packages/server/src/build/get.rs index fc6cd85f8..4f52609c8 100644 --- a/packages/server/src/build/get.rs +++ b/packages/server/src/build/get.rs @@ -44,6 +44,8 @@ impl Server { pub error: Option>, pub host: String, #[serde(default)] + pub lock: Option, + #[serde(default)] pub log: Option, #[serde(default)] pub logs_count: Option, @@ -92,6 +94,7 @@ impl Server { depth, error, host, + lock, log, logs_complete, logs_count, @@ -126,6 +129,7 @@ impl Server { depth: row.depth, error: row.error.map(|error| error.0), host: row.host, + lock: row.lock, log: row.log, logs_count: row.logs_count, logs_depth: row.logs_depth, diff --git a/packages/server/src/config.rs b/packages/server/src/config.rs index 2c3349d88..24ef2f101 100644 --- a/packages/server/src/config.rs +++ b/packages/server/src/config.rs @@ -58,6 +58,7 @@ pub struct Build { pub concurrency: usize, pub heartbeat_interval: Duration, pub max_depth: u64, + pub set_path_and_tag: bool, pub remotes: Vec, } @@ -194,6 +195,7 @@ impl Default for Build { concurrency: n.into(), heartbeat_interval: Duration::from_secs(1), max_depth: 4096, + set_path_and_tag: false, remotes: Vec::new(), } } diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index 40a0d9479..352a1ae8e 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -1221,8 +1221,7 @@ impl tg::Handle for Server { fn try_get_reference( &self, reference: &tg::Reference, - ) -> impl Future>>>> - { + ) -> impl Future>> { self.try_get_reference(reference) } diff --git a/packages/server/src/reference/get.rs b/packages/server/src/reference/get.rs index 5493fb81f..c9a57ba3f 100644 --- a/packages/server/src/reference/get.rs +++ b/packages/server/src/reference/get.rs @@ -9,8 +9,8 @@ impl Server { pub async fn try_get_reference( &self, reference: &tg::Reference, - ) -> tg::Result>>> { - match &reference.item() { + ) -> tg::Result> { + let (referent, lockfile) = match &reference.item() { tg::reference::Item::Build(build) => { let item = Either::Left(build.clone()); let output = tg::Referent { @@ -19,7 +19,7 @@ impl Server { subpath: None, tag: None, }; - Ok(Some(output)) + (output, None) }, tg::reference::Item::Object(object) => { let item = Either::Right(object.clone()); @@ -32,7 +32,7 @@ impl Server { subpath, tag: None, }; - Ok(Some(output)) + (output, None) }, tg::reference::Item::Path(path) => { let arg = tg::artifact::checkin::Arg { @@ -54,13 +54,13 @@ impl Server { let subpath = reference .options() .and_then(|options| options.subpath.clone()); - let output = tg::Referent { + let referent = tg::Referent { item, path: None, subpath, tag: None, }; - Ok(Some(output)) + (referent, output.lockfile) }, tg::reference::Item::Tag(tag) => { let Some(tg::tag::get::Output { item, .. }) = self.try_get_tag(tag).await? else { @@ -75,9 +75,11 @@ impl Server { subpath, tag: None, }; - Ok(Some(output)) + (output, None) }, - } + }; + + Ok(Some(tg::reference::get::Output { lockfile, referent })) } } diff --git a/packages/server/src/runtime/js/syscall/target.rs b/packages/server/src/runtime/js/syscall/target.rs index 8382f44a8..9f9fa72bd 100644 --- a/packages/server/src/runtime/js/syscall/target.rs +++ b/packages/server/src/runtime/js/syscall/target.rs @@ -14,6 +14,7 @@ pub async fn output(state: Rc, args: (tg::Target,)) -> tg::Result tg::Result>>> { + ) -> tg::Result> { Err(tg::error!("forbidden")) }