Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for index packages #2175

Open
wants to merge 43 commits into
base: master
Choose a base branch
from
Open

Support for index packages #2175

wants to merge 43 commits into from

Conversation

jneem
Copy link
Member

@jneem jneem commented Feb 19, 2025

This adds support for a package index, but requires that all the index packages have exact version constraints. As a result, we don't yet need to do version resolution.

The main points of this PR are

  • to define the index format
  • to decide when/how to refresh the index, and how to download index packages
  • to add primitive index-manipulation tooling, so that we can start hosting (and using) index packages

Copy link
Contributor

github-actions bot commented Feb 19, 2025

@jneem jneem marked this pull request as ready for review February 27, 2025 13:55
@jneem jneem requested a review from yannham February 27, 2025 13:59
Copy link
Member

@yannham yannham left a comment

Choose a reason for hiding this comment

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

Partial review. Stopped just before package/index/mod.rs, will continue later.

/// names *must* match the package id in the index, and you must ensure that
/// the version you push to github matches the SHA-1 hash in the index.
/// 7. Open a pull request to `github.com/nickel-lang/nickel-mind` to make your index
/// modifications public.
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if the whole description of the process really belongs to the CLI help message. What do you think about putting it somewhere (manual maybe?) and linking to the page here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, I've added a manual page about package management in general and included this there.

let snap = manifest.snapshot_dependencies(config.clone())?;
let index = PackageIndex::shared(config.clone())?;
let resolution = resolve::resolve(&manifest, snap, index, config)?;
let package_map = resolution.package_map(&manifest)?;
eprintln!("{package_map}");
Copy link
Member

Choose a reason for hiding this comment

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

left-over debug statement?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, printing the package map is the main job of this command. (It maybe be time to remove the command, though. It was mainly just to help debug things while implementing...)

@@ -92,7 +125,23 @@ impl PackageCommand {
..config
};

manifest.snapshot_dependencies(config)?;
manifest.snapshot_dependencies(config.clone())?;
Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity, why do we need to take a config as an owned value?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm, I think maybe I can get rid of this one. In some other places it's just convenient to store copies of the config so that we can figure out where various files go.

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, we now no longer store Config in Snapshot.

/// the version you push to github matches the SHA-1 hash in the index.
/// 7. Open a pull request to `github.com/nickel-lang/nickel-mind` to make your index
/// modifications public.
PublishToLocalIndex {
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: this sounds a bit long for a command name.

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed to "publish".

write!(f, "package {id}@{version} is already present in the index")
}
Error::PackageIndexSerialization { error, pkg } => {
write!(f, "error serializing package. Failed package {pkg:?}, caused by {error}\n{INTERNAL_ERROR_MSG}")
Copy link
Member

Choose a reason for hiding this comment

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

There is some duplication in the error message. Maybe it's a mix of two different versions?

Copy link
Member Author

Choose a reason for hiding this comment

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

I shorted the message. For some reason, rustfmt took that as a cue to spread the code over more lines 🤔

Copy link
Member

@yannham yannham left a comment

Choose a reason for hiding this comment

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

Second chunk: I stopped just before resolve.rs.


fn ensure_downloaded_to(&self, index_id: &PreciseId, target_dir: &Path) -> Result<(), Error> {
let PreciseId::Github { org, name, commit } = index_id;
let url = format!("https://github.com/{org}/{name}.git");
Copy link
Member

Choose a reason for hiding this comment

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

Can we have any injection/url encoding issue here, or are org and name sanitized as an invariant?

Copy link
Member Author

Choose a reason for hiding this comment

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

The manifest format ensures that org and name are nickel idents (in the lexer sense, not the "interned string" sense). I'll add a comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I take it back: the FromStr implementation checks the names, but the manifest doesn't go through that. I'll fix this...

return Ok(());
}

eprintln!(
Copy link
Member

Choose a reason for hiding this comment

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

random thought: maybe we will want to have separate macros for outputting status update and errors (so that we can style them a bit, like putting 🫠 in front of errors (or not))

Copy link
Member Author

Choose a reason for hiding this comment

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

I added info and warn macros that just wrap eprintln for now.

}
}

static ID_REGEX: LazyLock<Regex> =
Copy link
Member

Choose a reason for hiding this comment

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

I didn't know something like LazyLock existed in the standard library. I wonder if this deprecates the usage we make of lazy_static elsewhere in Nickel.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I think so. It's only been in the stdlib since 1.80

}
}

/// The same as [`Dependency`], but only for the packages that have fixed, unresolvable, versions.
Copy link
Member

@yannham yannham Mar 6, 2025

Choose a reason for hiding this comment

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

What does "unresolvable" means in this context? Just we don't get to pick a different one, like rigid version constraints, or something like a precise git ref?

Copy link
Member Author

Choose a reason for hiding this comment

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

It means that the resolver doesn't care about (or get to pick) the version. Or in other words, a path or git dependency.

Git deps don't need precise versions to be "unresolvable": if a git dep points at HEAD, then we just fetch HEAD and that's the package we depend on. The resolver won't consider whether it should take a different revision.

On the other hand, rigid version constraints on an index package are still "resolvable" in this sense, because their presence affects the resolver's decisions.

Copy link
Member

Choose a reason for hiding this comment

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

I see. Maybe it would be clearer to mention it explicitly ("git ref or path"), because "version" is overloaded quite a bit in our context (I initially though version would only correspond to version bounds, which is why "unresolvable version" sounded a bit paradoxical).

/// This is currently identical to `IndexDependency`, but we keep
/// them separate so it's clear which things are part of our public
/// serialization API.
struct IndexDependencyFormat {
Copy link
Member

Choose a reason for hiding this comment

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

isn't this struct defined two times, here in manifest but also in index::serialize?

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, but one of them defines how to deserialize the manifest (going through the RichTerm deserializer) and the other defines how to serialize/deserialize the index through serde_json. The current differences are just cosmetic for now (for some reason I decided to have "github" lower case in the index, but it needs to be upper case for the manifest to match the 'Github enum), but I think there's some value in allowing them to be different. For example, if we add support for other forges then an old nickel version reading 'Codeberg from a manifest is allowed to error out immediately (because it won't know how to fetch that dependency), but an old nickel version shouldn't fail to deserialize an index that contains a package hosted on codeberg. (It can fail if the dependency tree contains such a package, though)

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.

2 participants