neovim / packspec

ALPHA package dependencies spec
http://packspec.org/
Apache License 2.0
215 stars 2 forks source link

Pivot to "federated package spec" (ecosystem-agnostic dependency spec) #41

Open justinmk opened 1 year ago

justinmk commented 1 year ago

(Note: all feedback is captured in this description + the first comment.)


To add some momentum to this effort, I suggest:

  1. Supporting all Lua-supporting versions of Vim/Nvim all "assets" or "artifacts" of any kind in the spec should be a requirement.
    • No need for Vim/Nvim to have a different package spec, plus this adds legitimacy and weight to the idea.
    • No need for this spec to be specific to vim or text editors. What's needed is simply a way to define artifacts that depend on other artifacts.
  2. Shrink the scope by at least 2x.
    • Only support one format, Lua (because it allows comments). Vim must be compiled with +lua or must have lua available on $PATH.
    • Only support one format, JSON. Because:
      • ubiquitous
      • "machine readable" (sandboxed by design): can recursively download an entire dependency tree before executing any code, including hooks. Aggregators such as https://neovimcraft.com/ can consume it.
      • Turing-complete formats invite endless special-case features (nvim-lspconfig is a living example).
    • Remove any fields that are not immediately, obviously needed.
      • Remove any fields that are provided by git. Git is a client requirement.
      • But keep anything that is needed to ensure forward-compatible enhancements.
  3. (optional) use identical field names from npm's package.json, where possible (assuming this reduces confusion rather than increasing it)
  4. Strict "no side-effects" requirement: pkg.json should be totally sandboxed (evaluating it must have no side-effects, only input and output). (Does #7 address this?)
  5. Transfer this repo to https://github.com/neovim/ ?

Client requirements

Package server requirements

Why NPM package.json?

NPM is special because it's ubiquitous--and lots of discussion hasn't yielded a strong case for a novel format. Using things that are ubiquitous means you gain their tooling, docs, validators (and possibly even... infrastructure).

node and NPM aren't perfect, but that doesn't matter. Choosing to be a subset of that ecosystem provides optionality: it's almost entirely upside with limited downside. We could also choose PHP's package format. The point is to surf on something massive and immortal.

What about LuaRocks?

I've advocated for LuaRocks as the Nvim plugin manager, but defining a "plugin spec" "federated package spec" also makes sense because:

References

justinmk commented 1 year ago

Revised, minimal package spec: pkg.json

{
  "name" : "lspconfig", // OPTIONAL cosmetic name, not used for resolution nor filesystem locations.
  "description" : "Quickstart configurations for the Nvim-lsp client", // OPTIONAL
  "engines": {
      "nvim": "^0.10.0",
      "vim": "^9.1.0"
  },
  "repository": { // REQUIRED
      "type": "git", // reserved for future use
      "url": "https://github.com/neovim/nvim-lspconfig"
  },
  "dependencies" : { // OPTIONAL
    "https://github.com/neovim/neovim" : "0.6.1",
    "https://github.com/lewis6991/gitsigns.nvim" : "0.3"
  },
}

Changes

Closed questions

Open questions

Strategy

Sanix-Darker commented 1 year ago

removed "version" : "0.1.2", because package version is provided by the .git repo info

based on tag/release ?

should name be removed? because repository.url already defines the "name" (which can be prettified in UIs)

I think the name attribute can stay IMO

famiu commented 1 year ago

Should we perhaps have a way of describing why a dependency exists? What about optional dependencies? Should that be within scope for this package spec, and if so, how would it work?

justinmk commented 1 year ago

Should we perhaps have a way of describing why a dependency exists?

You mean like pseudo "comments"? No, that doesn't sound like something needed in a minimal "P0" approach.

What about optional dependencies?

Why is that needed in the minimal, initial spec?

famiu commented 1 year ago

Should we perhaps have a way of describing why a dependency exists?

You mean like pseudo "comments"? No, that doesn't sound like something needed in a minimal "P0" approach.

Makes sense.

What about optional dependencies?

Why is that needed in the minimal, initial spec?

I don't think it is "needed", I was just asking if it should be included or not, just in case the thought of optional deps may have slipped your mind.

williamboman commented 1 year ago

Cool! I'll add some of my thoughts, hope you don't mind;

  • removed "version" : "0.1.2", because package version is provided by the .git repo info

Does this mean that a manifest file always tracks HEAD of the git repo? Given a manifest file as input, how would one know which version of the package it describes? I think the version number in the manifest file is very much needed, and should be the canonical version identifier. I also feel like the absence of the version number would violate the 4th principle?

  • renamed specification_version to specversion

I think excluding this field would help with overall ergonomics. If the spec starts out small with only fundamental requirements and additions are carefully considered I don't see the spec schema changing in such a way it'd require a spec version bump. Bumping the spec version should imo be a last resort. I think a strict non-breaking change policy would make sense. Consumers would simply check the presence of a field to enable that "feature".

  • changed dependencies shape to align with NPM. Except the keys are URLs.

    • Leaves the door open for non-URL keys in the future.

Should consumers of dependencies need to control how a dependency is resolved? I think it'd be nice if the spec enforces globally unique names instead which central registries would have to enforce. The dependency schema could then simply be:

{
  "dependencies": {
    "plenary.nvim": "1.0.0"
  }
}

As for describing version ranges, it looks like the suggested syntax employs npm's syntax, which I think is entirely proprietary to npm. I think cargo's approach is a bit simpler, where:

1.2.3  :=  >=1.2.3, <2.0.0
1.2    :=  >=1.2.0, <2.0.0
1      :=  >=1.0.0, <2.0.0
0.2.3  :=  >=0.2.3, <0.3.0
0.2    :=  >=0.2.0, <0.3.0
0.0.3  :=  >=0.0.3, <0.0.4
0.0    :=  >=0.0.0, <0.1.0
0      :=  >=0.0.0, <1.0.0

I feel like this alone is enough? In the future it could be extended with the additional modifiers ~, ^ and * (which have different meaning than in npm).

Dependencies aren't required to have a packspec.json file. That's only required for the "leaf nodes".

I think the entire dependency tree should be "packspec-enabled". Having plugin dependencies is in my experience pretty rare (maybe only a contemporary consequence of the lack of a manifest file), and those who do tend to depend on plugins that are intended to be dependent on (e.g. plenary) - it'd be in everyone's interest for them to provide a manifest. Having this requirement could also act as a motivator for authors to include one. It's also a hygiene and stability factor as it'd hinder "nonserious" plugins from making their way into the package ecosystem - it's a good proxy for signaling stability.

Finally I think it'd also be good to explicitly define which fields are required. I don't see it being mentioned here but I feel like there's an implication that some of them currently are?

justinmk commented 1 year ago

Great feedback!

removed "version" : "0.1.2",

Does this mean that a manifest file always tracks HEAD of the git repo?

(Added to "Closed questions") The dependents declare what version they need, which must be available as a git tag in the dependency. Thus there is no need for packspec.json to repeat that information. The reason that package.json and other package formats need a version field is because they don't require a .git repo to be present.

excluding specversion field would help with overall ergonomics. ... Bumping the spec version should be a last resort.

Agreed, updated spec. The absence of specversion means "spec version 1.0".

Should consumers of dependencies need to control how a dependency is resolved?

(Added to "Closed questions") repository.type is available for future use if we want to deal with that.

it'd be nice if the spec enforces globally unique names

(Added to "Closed questions") Requiring URIs achieves that.

globally unique names ... which central registries would have to enforce. The dependency schema could then simply be: { "dependencies": { "plenary.nvim": "1.0.0"

(Added to "Closed questions") That looks nice but it makes the protocol more complicated and less "distributed".

justinmk commented 1 year ago

As for describing version ranges, it looks like the suggested syntax employs npm's syntax, which I think is entirely proprietary to npm. I think cargo's approach is a bit simpler

Good idea, updated spec.

I think the entire dependency tree should be "packspec-enabled". Having plugin dependencies is in my experience pretty rare

It's also a hygiene and stability factor as it'd hinder "nonserious" plugins from making their way into the package ecosystem - it's a good proxy for signaling stability.

That just isn't a goal (and it's the "RMS" approach to software 😏: cathedral instead of crazy, infectious bazaar) . The original scope was to be able to declare and resolve dependencies. Not anything else.

folke commented 1 year ago

Those git urls git://github.com/neovim/neovim.git are not valid. Should be just https or any other git supported url format.

titaniumtraveler commented 1 year ago
  • scripts and "build-time" tasks (lifecycle)
    • Scripts must be array of strings (unlike npm package.json).
    • Scripts are run from the root of the package folder, regardless of what the current working directory is.
      • Predefined script names and lifecycle order:
      • These all run after fetching and writing the package contents to the engine-defined package path, in order.
      • preinstall
      • install
      • postinstall

On the topic on uninstall scripts: Is it sure that we don't want that? Clean uninstall would be a really cool feature.

For example for something like mason automatically removing all installed lsp-binaries would be nice.

I read this and am not sure, why it would be necessary to give context for uninstall. Like there isn't context given for install, so why should it be needed for uninstall.

I tried research on the exact reasons why it was removed for npm, but haven't really found anything.


  • Version specifiers in dependencies follow the NPM version range spec ~cargo spec~
  • Supported by Nvim vim.version.range().
  • Extensions to npm version spec:
    • "HEAD" means git HEAD. (npm version spec defines "" and "*" as latest stable version.)
  • ~Do NOT support "Combined ranges".~
  • Treat any string of length >=7 and lacking "." as a commit-id.
  • Only support commit-id, tags, and HEAD.
  • Tags must contain a non-alphanumeric char.

Wouldn't it be better if there was a more explicit way to define non-semver dependency versions? Like if it's just a string it is interpreted as either semver or HEAD. If there is an object it depends on the keys used.

{
  ...
  "dependencies" : { // OPTIONAL
    ...
    "<dependency-url>" : {
        "branch": "<branch-name>"
    },
    "<dependency-url>" : {
        "tag": "<tag-name>"
    },
    "<dependency-url>" : {
        "ref": "<commit-id>"
    },
    "<dependency-url>" : { // This is equivalent to the normal "<dependency>": "<semver>"
        "version": "<semver>"
    },
  },
}

Another idea would be to support optional dependencies that can be installed to support additional features.

{
  ...
  "dependencies" : { // OPTIONAL
    ...
    "<dependency-url>" : {
        "version": "<semver>",
        "optional": true,
        // OPTIONAL
        "description": "Optional textual description describing the features enabled by using the optional dependency"
    },
  },
}

Other ideas:

justinmk commented 1 year ago

On the topic on uninstall scripts: Is it sure that we don't want that?

Yes

Clean uninstall would be a really cool feature.

It would. And yet...

optional dependencies

Why do people keep mentioning this? What in the world is an "optional" dependency? Either it's a dependency or it isn't.

path dependencies? ... OS specific dependencies?

Out of scope. Out of scope. Can wait until v2. Can wait until v2.

titaniumtraveler commented 1 year ago

optional dependencies

Why do people keep mentioning this? What in the world is an "optional" dependency? Either it's a dependency or it isn't.

I mean lots of package managers support optional dependencies. In my experience it's often a way to make integrations with other packages more discoverable.

Like it works without the dependency, but has additional features if the dependency is added.

famiu commented 1 year ago

optional dependencies

Why do people keep mentioning this? What in the world is an "optional" dependency? Either it's a dependency or it isn't.

I mean lots of package managers support optional dependencies. In my experience it's often a way to make integrations with other packages more discoverable.

Like it works without the dependency, but has additional features if the dependency is added.

Not needed for v1 imo, could be looked into for v2 though.

justinmk commented 1 year ago

Via https://twitter.com/oilsforunix/status/1680957458431213569 :

I have been lightly working on almost exactly this, many notes on https://oilshell.zulipchat.com (please join). Working name is "Silo" for dumb artifacts; "medo"/meadow for git-versioned trees. It's a "meta" package manager because it invokes containerized apt, pip, etc.

titaniumtraveler commented 1 year ago

❓ metadata probably makes sense, NPM itself allows arbitrary fields in package.json

I think having a metadata field reserved for client defined data is a pretty good idea. Especially when considering that this spec is likely to change in the future and breaking old packages because they used fields that collide with fields that were added in new versions would be bad.

Also validation and deserialization is much easier if you know what to expect.

llllvvuu commented 11 months ago

whether the plugin needs/supports setup()

VSCode handles this in the package.json. actually we could rip off quite a few of their contribution points although the rest of them have less obvious value.

configuration and configurationDefaults maps most cleanly to vim.g instead of setup(). I guess setup() could be a bool on top of that.

Contribution points can be used for anything lua_ls annotations are currently used for (completions, hover, diagnostics, vimdoc) plus:

Technically lua_ls annotations could also be contorted to do some of the above (not sure about vim.g or vimscript support) although the annotated setup() "standard" is too tacit I think. But if contribution points were added to the spec then I imagine someone would make a generator. Similar to how you can go schema-first or code-first in GraphQL and OpenAPI. (But schema-first is the way 😤)