mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 10:33:49 +00:00
Support workspace to workspace path dependencies (#4833)
Add support for path dependencies from a package in one workspace to a package in another workspace, which it self has workspace dependencies. Say we have a main workspace with packages `a` and `b`, and a second workspace with `c` and `d`. We have `a -> b`, `b -> c`, `c -> d`. This would previously lead to a mangled path for `d`, which is now fixed. Like distribution paths, we split workspace paths into an absolute install path and a relative (or absolute, if the user provided an absolute path) lock path. Part of https://github.com/astral-sh/uv/issues/3943
This commit is contained in:
parent
b5ec859273
commit
abb6ac5127
13 changed files with 410 additions and 88 deletions
|
@ -201,19 +201,37 @@ pub(crate) fn lower_requirement(
|
|||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||
return Err(LoweringError::ConflictingUrls);
|
||||
}
|
||||
let path = workspace
|
||||
let member = workspace
|
||||
.packages()
|
||||
.get(&requirement.name)
|
||||
.ok_or(LoweringError::UndeclaredWorkspacePackage)?
|
||||
.clone();
|
||||
// The lockfile is relative to the workspace root.
|
||||
let relative_to_workspace = relative_to(path.root(), workspace.install_path())
|
||||
|
||||
// Say we have:
|
||||
// ```
|
||||
// root
|
||||
// ├── main_workspace <- We want to the path from here ...
|
||||
// │ ├── pyproject.toml
|
||||
// │ └── uv.lock
|
||||
// └──current_workspace
|
||||
// └── packages
|
||||
// └── current_package <- ... to here.
|
||||
// └── pyproject.toml
|
||||
// ```
|
||||
// The path we need in the lockfile: `../current_workspace/packages/current_project`
|
||||
// member root: `/root/current_workspace/packages/current_project`
|
||||
// workspace install root: `/root/current_workspace`
|
||||
// relative to workspace: `packages/current_project`
|
||||
// workspace lock root: `../current_workspace`
|
||||
// relative to main workspace: `../current_workspace/packages/current_project`
|
||||
let relative_to_workspace = relative_to(member.root(), workspace.install_path())
|
||||
.map_err(LoweringError::RelativeTo)?;
|
||||
let url = VerbatimUrl::parse_absolute_path(path.root())?
|
||||
.with_given(relative_to_workspace.to_string_lossy());
|
||||
let relative_to_main_workspace = workspace.lock_path().join(relative_to_workspace);
|
||||
let url = VerbatimUrl::parse_absolute_path(member.root())?
|
||||
.with_given(relative_to_main_workspace.to_string_lossy());
|
||||
RequirementSource::Directory {
|
||||
install_path: path.root().clone(),
|
||||
lock_path: relative_to_workspace,
|
||||
install_path: member.root().clone(),
|
||||
lock_path: relative_to_main_workspace,
|
||||
url,
|
||||
editable: editable.unwrap_or(true),
|
||||
}
|
||||
|
|
|
@ -57,7 +57,8 @@ impl Metadata {
|
|||
/// dependencies.
|
||||
pub async fn from_workspace(
|
||||
metadata: Metadata23,
|
||||
project_root: &Path,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
preview_mode: PreviewMode,
|
||||
) -> Result<Self, MetadataError> {
|
||||
// Lower the requirements.
|
||||
|
@ -66,13 +67,14 @@ impl Metadata {
|
|||
requires_dist,
|
||||
provides_extras,
|
||||
dev_dependencies,
|
||||
} = RequiresDist::from_workspace(
|
||||
} = RequiresDist::from_project_maybe_workspace(
|
||||
pypi_types::RequiresDist {
|
||||
name: metadata.name,
|
||||
requires_dist: metadata.requires_dist,
|
||||
provides_extras: metadata.provides_extras,
|
||||
},
|
||||
project_root,
|
||||
install_path,
|
||||
lock_path,
|
||||
preview_mode,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -42,15 +42,16 @@ impl RequiresDist {
|
|||
|
||||
/// Lower by considering `tool.uv` in `pyproject.toml` if present, used for Git and directory
|
||||
/// dependencies.
|
||||
pub async fn from_workspace(
|
||||
pub async fn from_project_maybe_workspace(
|
||||
metadata: pypi_types::RequiresDist,
|
||||
project_root: &Path,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
preview_mode: PreviewMode,
|
||||
) -> Result<Self, MetadataError> {
|
||||
// TODO(konsti): Limit discovery for Git checkouts to Git root.
|
||||
// TODO(konsti): Cache workspace discovery.
|
||||
let Some(project_workspace) =
|
||||
ProjectWorkspace::from_maybe_project_root(project_root, None).await?
|
||||
ProjectWorkspace::from_maybe_project_root(install_path, lock_path, None).await?
|
||||
else {
|
||||
return Ok(Self::from_metadata23(metadata));
|
||||
};
|
||||
|
@ -58,7 +59,7 @@ impl RequiresDist {
|
|||
Self::from_project_workspace(metadata, &project_workspace, preview_mode)
|
||||
}
|
||||
|
||||
pub fn from_project_workspace(
|
||||
fn from_project_workspace(
|
||||
metadata: pypi_types::RequiresDist,
|
||||
project_workspace: &ProjectWorkspace,
|
||||
preview_mode: PreviewMode,
|
||||
|
@ -159,6 +160,7 @@ mod test {
|
|||
let pyproject_toml = PyProjectToml::from_string(contents.to_string())?;
|
||||
let path = Path::new("pyproject.toml");
|
||||
let project_workspace = ProjectWorkspace::from_project(
|
||||
path,
|
||||
path,
|
||||
pyproject_toml
|
||||
.project
|
||||
|
|
|
@ -423,8 +423,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
/// Return the [`RequiresDist`] from a `pyproject.toml`, if it can be statically extracted.
|
||||
pub(crate) async fn requires_dist(&self, project_root: &Path) -> Result<RequiresDist, Error> {
|
||||
let requires_dist = read_requires_dist(project_root).await?;
|
||||
let requires_dist =
|
||||
RequiresDist::from_workspace(requires_dist, project_root, self.preview_mode).await?;
|
||||
let requires_dist = RequiresDist::from_project_maybe_workspace(
|
||||
requires_dist,
|
||||
project_root,
|
||||
project_root,
|
||||
self.preview_mode,
|
||||
)
|
||||
.await?;
|
||||
Ok(requires_dist)
|
||||
}
|
||||
|
||||
|
@ -916,7 +921,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map(|reporter| reporter.on_build_start(source));
|
||||
|
||||
let (disk_filename, filename, metadata) = self
|
||||
.build_distribution(source, &resource.path, None, &cache_shard)
|
||||
.build_distribution(source, &resource.install_path, None, &cache_shard)
|
||||
.await?;
|
||||
|
||||
if let Some(task) = task {
|
||||
|
@ -978,14 +983,19 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
if let Some(metadata) = read_cached_metadata(&metadata_entry).await? {
|
||||
debug!("Using cached metadata for: {source}");
|
||||
return Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, resource.path.as_ref(), self.preview_mode)
|
||||
.await?,
|
||||
Metadata::from_workspace(
|
||||
metadata,
|
||||
resource.install_path.as_ref(),
|
||||
resource.lock_path.as_ref(),
|
||||
self.preview_mode,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
}
|
||||
|
||||
// If the backend supports `prepare_metadata_for_build_wheel`, use it.
|
||||
if let Some(metadata) = self
|
||||
.build_metadata(source, &resource.path, None)
|
||||
.build_metadata(source, &resource.install_path, None)
|
||||
.boxed_local()
|
||||
.await?
|
||||
{
|
||||
|
@ -998,8 +1008,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map_err(Error::CacheWrite)?;
|
||||
|
||||
return Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, resource.path.as_ref(), self.preview_mode)
|
||||
.await?,
|
||||
Metadata::from_workspace(
|
||||
metadata,
|
||||
resource.install_path.as_ref(),
|
||||
resource.lock_path.as_ref(),
|
||||
self.preview_mode,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1010,7 +1025,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map(|reporter| reporter.on_build_start(source));
|
||||
|
||||
let (_disk_filename, _filename, metadata) = self
|
||||
.build_distribution(source, &resource.path, None, &cache_shard)
|
||||
.build_distribution(source, &resource.install_path, None, &cache_shard)
|
||||
.await?;
|
||||
|
||||
if let Some(task) = task {
|
||||
|
@ -1025,7 +1040,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map_err(Error::CacheWrite)?;
|
||||
|
||||
Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, resource.path.as_ref(), self.preview_mode).await?,
|
||||
Metadata::from_workspace(
|
||||
metadata,
|
||||
resource.install_path.as_ref(),
|
||||
resource.lock_path.as_ref(),
|
||||
self.preview_mode,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -1036,15 +1057,17 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
cache_shard: &CacheShard,
|
||||
) -> Result<Revision, Error> {
|
||||
// Verify that the source tree exists.
|
||||
if !resource.path.is_dir() {
|
||||
if !resource.install_path.is_dir() {
|
||||
return Err(Error::NotFound(resource.url.clone()));
|
||||
}
|
||||
|
||||
// Determine the last-modified time of the source distribution.
|
||||
let Some(modified) =
|
||||
ArchiveTimestamp::from_source_tree(&resource.path).map_err(Error::CacheRead)?
|
||||
ArchiveTimestamp::from_source_tree(&resource.install_path).map_err(Error::CacheRead)?
|
||||
else {
|
||||
return Err(Error::DirWithoutEntrypoint(resource.path.to_path_buf()));
|
||||
return Err(Error::DirWithoutEntrypoint(
|
||||
resource.install_path.to_path_buf(),
|
||||
));
|
||||
};
|
||||
|
||||
// Read the existing metadata from the cache. We treat source trees as if `--refresh` is
|
||||
|
@ -1225,7 +1248,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
if let Some(metadata) = read_cached_metadata(&metadata_entry).await? {
|
||||
debug!("Using cached metadata for: {source}");
|
||||
return Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, fetch.path(), self.preview_mode).await?,
|
||||
Metadata::from_workspace(
|
||||
metadata,
|
||||
fetch.path(),
|
||||
fetch.path(),
|
||||
self.preview_mode,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1245,7 +1274,8 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map_err(Error::CacheWrite)?;
|
||||
|
||||
return Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, fetch.path(), self.preview_mode).await?,
|
||||
Metadata::from_workspace(metadata, fetch.path(), fetch.path(), self.preview_mode)
|
||||
.await?,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1271,7 +1301,8 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map_err(Error::CacheWrite)?;
|
||||
|
||||
Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, fetch.path(), self.preview_mode).await?,
|
||||
Metadata::from_workspace(metadata, fetch.path(), fetch.path(), self.preview_mode)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use tracing::{debug, trace};
|
|||
|
||||
use pep508_rs::{RequirementOrigin, VerbatimUrl};
|
||||
use pypi_types::{Requirement, RequirementSource};
|
||||
use uv_fs::{absolutize_path, Simplified};
|
||||
use uv_fs::{absolutize_path, normalize_path, relative_to, Simplified};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
|
@ -46,20 +46,29 @@ pub enum WorkspaceError {
|
|||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(serde::Serialize))]
|
||||
pub struct Workspace {
|
||||
/// The path to the workspace root, the directory containing the top level `pyproject.toml` with
|
||||
/// The path to the workspace root.
|
||||
///
|
||||
/// The workspace root is the directory containing the top level `pyproject.toml` with
|
||||
/// the `uv.tool.workspace`, or the `pyproject.toml` in an implicit single workspace project.
|
||||
install_path: PathBuf,
|
||||
/// The same path as `install_path`, but relative to the main workspace.
|
||||
///
|
||||
/// We use this value to compute relative paths for workspace-to-workspace dependencies. It's an
|
||||
/// empty path for the main workspace.
|
||||
lock_path: PathBuf,
|
||||
/// The members of the workspace.
|
||||
packages: BTreeMap<PackageName, WorkspaceMember>,
|
||||
/// The sources table from the workspace `pyproject.toml`. It is overridden by the project
|
||||
/// sources.
|
||||
/// The sources table from the workspace `pyproject.toml`.
|
||||
///
|
||||
/// This table is overridden by the project sources.
|
||||
sources: BTreeMap<PackageName, Source>,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
/// Find the workspace containing the given path.
|
||||
///
|
||||
/// Unlike the [`ProjectWorkspace`] discovery, this does not require a current project.
|
||||
/// Unlike the [`ProjectWorkspace`] discovery, this does not require a current project. It also
|
||||
/// always uses absolute path, i.e. this method only supports discovering the main workspace.
|
||||
///
|
||||
/// Steps of workspace discovery: Start by looking at the closest `pyproject.toml`:
|
||||
/// * If it's an explicit workspace root: Collect workspace from this root, we're done.
|
||||
|
@ -71,20 +80,21 @@ impl Workspace {
|
|||
path: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
) -> Result<Workspace, WorkspaceError> {
|
||||
let project_root = path
|
||||
let path = absolutize_path(path)
|
||||
.map_err(WorkspaceError::Normalize)?
|
||||
.to_path_buf();
|
||||
|
||||
let project_path = path
|
||||
.ancestors()
|
||||
.find(|path| path.join("pyproject.toml").is_file())
|
||||
.ok_or(WorkspaceError::MissingPyprojectToml)?;
|
||||
.ok_or(WorkspaceError::MissingPyprojectToml)?
|
||||
.to_path_buf();
|
||||
|
||||
let pyproject_path = project_root.join("pyproject.toml");
|
||||
let pyproject_path = project_path.join("pyproject.toml");
|
||||
let contents = fs_err::tokio::read_to_string(&pyproject_path).await?;
|
||||
let pyproject_toml = PyProjectToml::from_string(contents)
|
||||
.map_err(|err| WorkspaceError::Toml(pyproject_path.clone(), Box::new(err)))?;
|
||||
|
||||
let project_path = absolutize_path(project_root)
|
||||
.map_err(WorkspaceError::Normalize)?
|
||||
.to_path_buf();
|
||||
|
||||
// Check if the project is explicitly marked as unmanaged.
|
||||
if pyproject_toml
|
||||
.tool
|
||||
|
@ -150,6 +160,8 @@ impl Workspace {
|
|||
pyproject_toml,
|
||||
});
|
||||
Self::collect_members(
|
||||
workspace_root.clone(),
|
||||
// This method supports only absolute paths.
|
||||
workspace_root,
|
||||
workspace_definition,
|
||||
workspace_pyproject_toml,
|
||||
|
@ -248,6 +260,12 @@ impl Workspace {
|
|||
&self.install_path
|
||||
}
|
||||
|
||||
/// The same path as `install_path()`, but relative to the main workspace. We use this value
|
||||
/// to compute relative paths for workspace-to-workspace dependencies.
|
||||
pub fn lock_path(&self) -> &PathBuf {
|
||||
&self.lock_path
|
||||
}
|
||||
|
||||
/// The path to the workspace virtual environment.
|
||||
pub fn venv(&self) -> PathBuf {
|
||||
self.install_path.join(".venv")
|
||||
|
@ -266,6 +284,7 @@ impl Workspace {
|
|||
/// Collect the workspace member projects from the `members` and `excludes` entries.
|
||||
async fn collect_members(
|
||||
workspace_root: PathBuf,
|
||||
lock_path: PathBuf,
|
||||
workspace_definition: ToolUvWorkspace,
|
||||
workspace_pyproject_toml: PyProjectToml,
|
||||
current_project: Option<WorkspaceMember>,
|
||||
|
@ -387,6 +406,7 @@ impl Workspace {
|
|||
|
||||
Ok(Workspace {
|
||||
install_path: workspace_root,
|
||||
lock_path,
|
||||
packages: workspace_members,
|
||||
sources: workspace_sources,
|
||||
})
|
||||
|
@ -541,7 +561,7 @@ impl ProjectWorkspace {
|
|||
}
|
||||
|
||||
/// Discover the workspace starting from the directory containing the `pyproject.toml`.
|
||||
pub async fn from_project_root(
|
||||
async fn from_project_root(
|
||||
project_root: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
) -> Result<Self, WorkspaceError> {
|
||||
|
@ -557,17 +577,25 @@ impl ProjectWorkspace {
|
|||
.clone()
|
||||
.ok_or_else(|| WorkspaceError::MissingProject(pyproject_path.clone()))?;
|
||||
|
||||
Self::from_project(project_root, &project, &pyproject_toml, stop_discovery_at).await
|
||||
Self::from_project(
|
||||
project_root,
|
||||
Path::new(""),
|
||||
&project,
|
||||
&pyproject_toml,
|
||||
stop_discovery_at,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// If the current directory contains a `pyproject.toml` with a `project` table, discover the
|
||||
/// workspace and return it, otherwise it is a dynamic path dependency and we return `Ok(None)`.
|
||||
pub async fn from_maybe_project_root(
|
||||
project_root: &Path,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
) -> Result<Option<Self>, WorkspaceError> {
|
||||
// Read the `pyproject.toml`.
|
||||
let pyproject_path = project_root.join("pyproject.toml");
|
||||
let pyproject_path = install_path.join("pyproject.toml");
|
||||
let Ok(contents) = fs_err::tokio::read_to_string(&pyproject_path).await else {
|
||||
// No `pyproject.toml`, but there may still be a `setup.py` or `setup.cfg`.
|
||||
return Ok(None);
|
||||
|
@ -582,7 +610,14 @@ impl ProjectWorkspace {
|
|||
};
|
||||
|
||||
Ok(Some(
|
||||
Self::from_project(project_root, &project, &pyproject_toml, stop_discovery_at).await?,
|
||||
Self::from_project(
|
||||
install_path,
|
||||
lock_path,
|
||||
&project,
|
||||
&pyproject_toml,
|
||||
stop_discovery_at,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -609,12 +644,13 @@ impl ProjectWorkspace {
|
|||
|
||||
/// Find the workspace for a project.
|
||||
pub async fn from_project(
|
||||
project_path: &Path,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
project: &Project,
|
||||
project_pyproject_toml: &PyProjectToml,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
) -> Result<Self, WorkspaceError> {
|
||||
let project_path = absolutize_path(project_path)
|
||||
let project_path = absolutize_path(install_path)
|
||||
.map_err(WorkspaceError::Normalize)?
|
||||
.to_path_buf();
|
||||
|
||||
|
@ -669,6 +705,8 @@ impl ProjectWorkspace {
|
|||
project_name: project.name.clone(),
|
||||
workspace: Workspace {
|
||||
install_path: project_path.clone(),
|
||||
// The workspace and the project are the same, so the relative path is, too.
|
||||
lock_path: lock_path.to_path_buf(),
|
||||
packages: current_project_as_members,
|
||||
// There may be package sources, but we don't need to duplicate them into the
|
||||
// workspace sources.
|
||||
|
@ -682,8 +720,28 @@ impl ProjectWorkspace {
|
|||
workspace_root.simplified_display()
|
||||
);
|
||||
|
||||
// Say we have:
|
||||
// ```
|
||||
// root
|
||||
// ├── main_workspace <- The reference point
|
||||
// │ ├── pyproject.toml
|
||||
// │ └── uv.lock
|
||||
// └──current_workspace <- We want this relative to the main workspace
|
||||
// └── packages
|
||||
// └── current_package <- We have this relative to the main workspace
|
||||
// └── pyproject.toml
|
||||
// ```
|
||||
// The lock path we need: `../current_workspace`
|
||||
// workspace root: `/root/current_workspace`
|
||||
// project path: `/root/current_workspace/packages/current_project`
|
||||
// relative to workspace: `../..`
|
||||
// lock path: `../current_workspace`
|
||||
let up_to_root = relative_to(&workspace_root, &project_path)?;
|
||||
let lock_path = normalize_path(&lock_path.join(up_to_root));
|
||||
|
||||
let workspace = Workspace::collect_members(
|
||||
workspace_root,
|
||||
lock_path,
|
||||
workspace_definition,
|
||||
workspace_pyproject_toml,
|
||||
Some(current_project),
|
||||
|
@ -901,10 +959,17 @@ impl VirtualProject {
|
|||
///
|
||||
/// Similar to calling [`ProjectWorkspace::discover`] with a fallback to [`Workspace::discover`],
|
||||
/// but avoids rereading the `pyproject.toml` (and relying on error-handling as control flow).
|
||||
///
|
||||
/// This method requires an absolute path and panics otherwise, i.e. this method only supports
|
||||
/// discovering the main workspace.
|
||||
pub async fn discover(
|
||||
path: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
) -> Result<Self, WorkspaceError> {
|
||||
assert!(
|
||||
path.is_absolute(),
|
||||
"virtual project discovery with relative path"
|
||||
);
|
||||
let project_root = path
|
||||
.ancestors()
|
||||
.take_while(|path| {
|
||||
|
@ -931,6 +996,7 @@ impl VirtualProject {
|
|||
// If the `pyproject.toml` contains a `[project]` table, it's a project.
|
||||
let project = ProjectWorkspace::from_project(
|
||||
project_root,
|
||||
Path::new(""),
|
||||
project,
|
||||
&pyproject_toml,
|
||||
stop_discovery_at,
|
||||
|
@ -950,6 +1016,7 @@ impl VirtualProject {
|
|||
|
||||
let workspace = Workspace::collect_members(
|
||||
project_path,
|
||||
PathBuf::new(),
|
||||
workspace.clone(),
|
||||
pyproject_toml,
|
||||
None,
|
||||
|
@ -1032,6 +1099,7 @@ mod tests {
|
|||
"project_name": "bird-feeder",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-in-example/examples/bird-feeder",
|
||||
"lock_path": "",
|
||||
"packages": {
|
||||
"bird-feeder": {
|
||||
"root": "[ROOT]/albatross-in-example/examples/bird-feeder",
|
||||
|
@ -1067,6 +1135,7 @@ mod tests {
|
|||
"project_name": "bird-feeder",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder",
|
||||
"lock_path": "",
|
||||
"packages": {
|
||||
"bird-feeder": {
|
||||
"root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder",
|
||||
|
@ -1101,6 +1170,7 @@ mod tests {
|
|||
"project_name": "albatross",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-root-workspace",
|
||||
"lock_path": "",
|
||||
"packages": {
|
||||
"albatross": {
|
||||
"root": "[ROOT]/albatross-root-workspace",
|
||||
|
@ -1159,6 +1229,7 @@ mod tests {
|
|||
"project_name": "albatross",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-virtual-workspace",
|
||||
"lock_path": "../..",
|
||||
"packages": {
|
||||
"albatross": {
|
||||
"root": "[ROOT]/albatross-virtual-workspace/packages/albatross",
|
||||
|
@ -1211,6 +1282,7 @@ mod tests {
|
|||
"project_name": "albatross",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-just-project",
|
||||
"lock_path": "",
|
||||
"packages": {
|
||||
"albatross": {
|
||||
"root": "[ROOT]/albatross-just-project",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue