Skip to content

feat: configuration of project sources#130

Open
victor-linroth-sensmetry wants to merge 28 commits intomainfrom
feat/config-sources
Open

feat: configuration of project sources#130
victor-linroth-sensmetry wants to merge 28 commits intomainfrom
feat/config-sources

Conversation

@victor-linroth-sensmetry
Copy link
Collaborator

@victor-linroth-sensmetry victor-linroth-sensmetry commented Nov 28, 2025

Main new features added by this PR:

  • Config options for project sources in sysand.toml (or given config). Allows for portable specification of local and remote sources.
  • Command line options --editable, --path, --url-src and --url-kpar for sysand add that automatically updates config file. These also get removed with sysand remove.
  • Documentation explaining the above.

In order to simplify implementing the above the core library was build out with the following changes:

  • New macro crate with ProjectRead and ProjectMut derive macros for easy enum composition of project structs.
  • New CachedProject<Local, Remote> struct for when the Local project is easier to access (e.g. on disk or in memory) vs Remote, but you still want .sources() to give the remote sources.
  • Implementation of CombinedResolver simplified using derive(ProjectRead) and CachedProject.
  • New ProjectReference<Project> struct. When working with ReadEnvironment the projects need to be clonable, but this might not always be a good idea, e.g. some project hold ownership of temp dirs. ProjectReference is basically and Arc wrapper for a project to be used as a compatibility layer.
  • New AnyProject struct combining the different projects supported for source configuration. Should be extended to include Git projects in future.
  • LocalSrcProject and LocalKparProject now has optional nominal paths in addition to the regular path. This comes from the choice to have configured source always be relative to the project root but this not always being available everywhere. (Could perhaps be simplified if we added a Global Context).

In addition there where some small changes that came about when experimenting with different solutions:

  • MemoryEnv can now be used with a generic project.
  • MemoryResolver can be conveniently constructed from iterator/array/Vec.

These ended up not being used here, but they could be useful at some later point so where left in.

Lastly, the verbose/quiet options were removed from the config. They were mostly placeholders to begin with and now that there are more real options present in the config they don't serve much of a purpose. Removing them also means that the logger can be initialized almost immediately on start, opening up the potential for logging more things, like e.g. the loading of the config files.

@tilowiklundSensmetry
Copy link
Member

Is there a particular reason for expanding CombinedResolver with an Override case, as opposed to having a generic OverrideResolver<T> and use StandardResolver = OverrideResolver<CombinedResolver>?

@victor-linroth-sensmetry
Copy link
Collaborator Author

Is there a particular reason for expanding CombinedResolver with an Override case, as opposed to having a generic OverrideResolver<T> and use StandardResolver = OverrideResolver<CombinedResolver>?

No particular reason, just the first thing that came to mind. Do you think that would be a better solution?

@tilowiklundSensmetry
Copy link
Member

Is there a particular reason for expanding CombinedResolver with an Override case, as opposed to having a generic OverrideResolver<T> and use StandardResolver = OverrideResolver<CombinedResolver>?

No particular reason, just the first thing that came to mind. Do you think that would be a better solution?

It might be easier to test/mock, and be a bit easier to reuse elsewhere. You can simply have OverrideResolver<Memory/Null> instead of having to create a CombinedResolver<Memory/Null, Memory/Null, Memory/Null, Override, Memory/Null>.

No very strong preference though, so don't bother if it's a lot of work to refactor.

@tilowiklundSensmetry
Copy link
Member

Everything looks good to me in principle, would be a good to have a proper code review from @andrius-puksta-sensmetry before merging though.

@victor-linroth-sensmetry
Copy link
Collaborator Author

No very strong preference though, so don't bother if it's a lot of work to refactor.

Well, this was precisely the type of feedback I was looking for, so I'll definitely consider it.

Everything looks good to me in principle, would be a good to have a proper code review from @andrius-puksta-sensmetry before merging though.

It's still a draft so far. I want to add some quality of life command line stuff also.

@victor-linroth-sensmetry victor-linroth-sensmetry changed the title Feat/config sources feat: configuration of project sources Dec 29, 2025
@victor-linroth-sensmetry
Copy link
Collaborator Author

victor-linroth-sensmetry commented Jan 20, 2026

Currently sysand_core::lock::Source and typed_path::Utf8UnixPath have to be in scope when using the ProjectRead macro. This is certainly less than ideal but it's not obvious to me what the best solution would be.

The problem stem from the fact that reference of types in macros should be absolute but the absolute paths vary depending on if you are inside or outside the crate. E.g. it's ::sysand_core::lock::Source if outside and ::crate::lock::Source if inside. If typed_path::Utf8UnixPath is part of the public API it should probably be reexported by sysand_core and then it's the same problem there.

One could try to add them as associated types to ProjectRead but then you introduce the additional complexity that it can vary with trait implementations. I'm unsure if thats desirable or necessary.

Another solution would be to refactor the crates so that they are always used outside of the crate where they are exported. Would essentially mean that all the project stuff goes into it's own crate (except for AnyProject which is derived using the macro).

Edit: I think the appropriate course of action is to factor out the projects into their own crate, but this would be a pretty wide reaching change so best to do as a separate PR after this is merged.

@victor-linroth-sensmetry
Copy link
Collaborator Author

Unsure if core/tests/project_no_derive.rs has any value left now that I have it working.

@victor-linroth-sensmetry
Copy link
Collaborator Author

victor-linroth-sensmetry commented Jan 20, 2026

Still left to make sure Windows paths are handled properly (don't think they are at the moment).

Edit: Since the sources only have paths that are Utf8UnixPaths, I think this should be covered.

Signed-off-by: victor.linroth.sensmetry <victor.linroth@sensmetry.com>
Signed-off-by: victor.linroth.sensmetry <victor.linroth@sensmetry.com>
Signed-off-by: victor.linroth.sensmetry <victor.linroth@sensmetry.com>
Signed-off-by: victor.linroth.sensmetry <victor.linroth@sensmetry.com>
Signed-off-by: victor.linroth.sensmetry <victor.linroth@sensmetry.com>
MemoryResolver::from(overrides),
standard_resolver(
cwd,
if local_env_path.is_dir() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is eating errors. Maybe we should add is_file() and is_dir() to wrapfs, but make them e.g. fn is_file() -> Result<bool, SomeError>? This is mostly a nice-to-have, so can be done later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What error? You can run sysand info on an IRI without having an env.

Ok(src_path)
}

fn relativize(path: &Utf8Path, root: &Utf8Path) -> Utf8PathBuf {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn relativize(path: &Utf8Path, root: &Utf8Path) -> Utf8PathBuf {
/// Create a relative path from `root` to `path`. Both of them must either
/// be absolute or otherwise start with the same prefix.
fn relativize(path: &Utf8Path, root: &Utf8Path) -> Utf8PathBuf {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe return Option<Utf8PathBuf> so that caller can decide what to do in case relative path cannot be constructed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is useful (i.e. should be used, but currently isn't) in lots of places, so it should be moved to some utils file.

// If prefixes (e.g. C: vs D: on Windows) differ, no relative path is possible.
if path.components().next() != root.components().next() {
return path.to_path_buf();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what to do about Windows UNC vs non-UNC paths, as they always have different first components. Relevant here is that std's canonicalize returns UNC paths on Windows, but calling that on both inputs here would be kind of wasteful.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows root-relative paths are also not handled correctly here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be a good idea after all to canonicalize both paths (or ensuring only canonical paths are given as args here) to avoid subtle bugs due to symlinks and Windows weirndess.


Sometimes you may wish to use a project that isn't resolvable through an
available index or you want to override the dependency resolution for other
reasons. In any case you can do this by adding the appropriate IRI and `Source`
Copy link
Collaborator

@andrius-puksta-sensmetry andrius-puksta-sensmetry Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
reasons. In any case you can do this by adding the appropriate IRI and `Source`
reasons. You may also just want to give the usage a different identifier for readability. In any case you can do this by adding the appropriate IRI and `sources`

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is a great way of doing general aliasing, as then you may want to still want to go through regular dependency resolution. Maybe there's a way to add that as a separate feature in the future?

file, but it is also possible to do through the command line interface with the
[`sysand add`](../commands/add.md) command.

## Local projects
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mention the exact CLI flag to use for each type here?

the following entry to your `sysand.toml`.

```toml
[[project]]
Copy link
Collaborator

@andrius-puksta-sensmetry andrius-puksta-sensmetry Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename this to [[source]] to give us space to put the current project metadata here (if we ever decide to do this)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I thought about that a too, but naming it something else breaks the symmetry with lockfile format. There is potentially more configurations we want to add here like e.g. aliasing (having multiple identifiers) and disabling checksums (in case the project is expected to change a lot like editable projects and sources pointing to git branches). I think having the lockfile and config mirror each other here would be nice.

Comment on lines +9 to +10
#[proc_macro_derive(ProjectRead)]
pub fn project_read_derive(input: TokenStream) -> TokenStream {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a doc comment describing what this derive generates (i.e. what the derived impl does given an example arg).

Comment on lines +201 to +202
#[proc_macro_derive(ProjectMut)]
pub fn project_mut_derive(input: TokenStream) -> TokenStream {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description also needed here

Comment on lines +175 to +179
for r in root_iter {
if let Utf8Component::Normal(_) = r {
result.push("..");
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not always correct due to symlinks, unless both paths are already canonical.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also return an empty path if path and base are the same.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the paths will use platform-dependent separator: \ on Windows and / everywhere? else. Is config.toml intended to be portable?

Copy link
Collaborator Author

@victor-linroth-sensmetry victor-linroth-sensmetry Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not always correct due to symlinks, unless both paths are already canonical.

Guess it's easiest to just canonicalize them both.

This will also return an empty path if path and base are the same.

No it will be .? (Maybe some warning is appropriate here though since this doesn't make much sense for actual usage.)

And the paths will use platform-dependent separator: \ on Windows and / everywhere? else. Is config.toml intended to be portable?

I'm pretty sure all paths get transformed to Utf8UnixPaths before going into any file.

Signed-off-by: victor.linroth.sensmetry <victor.linroth@sensmetry.com>
Signed-off-by: victor.linroth.sensmetry <victor.linroth@sensmetry.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants