-
Notifications
You must be signed in to change notification settings - Fork 82
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
Unlocked deps #393
base: main
Are you sure you want to change the base?
Unlocked deps #393
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for starting this! I have some suggested changes to the examples, WDYT:
Co-authored-by: Luke Wagner <[email protected]>
Co-authored-by: Luke Wagner <[email protected]>
Co-authored-by: Luke Wagner <[email protected]>
Appreciate the review! Makes sense to me. |
|
||
```wit | ||
world w { | ||
unlocked-dep foo:bar@{>=x.x.x <y.y.y}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it intended to support foo:bar
here exactly? Or is a /interface
required as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that, when /interface
is absent, the expectation could be that a registry is used to resolve ns:package
to a component and the interface is the exports of that component (which have no name).
More hypothetically, if we get unnamed interfaces/worlds in WIT, /interface
could also be absent without relying on a registry by using a nested package of the form:
package foo:bar {
interface { ... }
}
world w {
unlocked-dep foo:bar@{...}
}
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But perhaps this is a good opportunity to make clear that the optional /interface
is possible as well, and maybe talk a bit about in which cases toolchains would create an unlocked-dep
with and without an interface projection? If we wanted to scope it in, folks could specify an interface when they add a dep and tools could only grab interfaces they need from the registry. And also the lock
or bundle
command could shake out unused interfaces in cases as well. May be more than we want for the initial discussion, but I was thinking that folks may find it confusing to only see a component foo:bar
when they're accustomed to depending on interfaces at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fundamentally, an unlocked-dep
(in the component importname
) names a component via package name and so, for a simple illustrative example like this, I think makes sense to similarly start with unlocked-dep
naming a package. (Yes, it's different that regular interface imports, but being different is the point.) The /interface
only shows up after inlining registry contents and is only necessary due to current expressive limitations of WIT; ideally it wouldn't ever be necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this spec out the interface { ... }
change as well in that case? That's not currently implemented or sketched out here, so I think that should be included too if that's the intention. (also could this update the ebnf for unlocked-dep
in worlds too?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be up for that; it would simplify things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great I should be able to add some of these updates soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an alternative to adding unnamed interfaces/worlds (which open up another set of design questions), we could instead add a bit of also-useful syntax that allows the WIT to capture precisely how the range query was resolved:
package gh:sqlite@1.1.1 {
interface exports { ... }
}
world w {
import dependency gh:sqlite@{>=1.0.0} = gh:sqlite/[email protected];
}
This = <pkgname>
right-hand-side of the import dependency
is necessary for advanced cases where the WIT needs to nest multiple versions of the same package such that the <pkgnamequery>
alone would be ambiguous. Importantly for our purposes here, though, none of the name/version info in the = <pkgname>
shows up in the import so our world w
above just contains (import "unlocked-dep=<gh:sqlite@{>=1.0.0}>" (instance ...))
(no /exports
, no 1.1.1
), and we didn't need anonymous interfaces to achieve that.
Not sure if this is the best place to add my opinion on this locked/unlocked syntax. Let me know if there would be a better place to express this. As a user of the component-model, as opposed to a core developer such as yourselves, the meaning of the Seems to me that if the WIT author is concerned with expressing the simple concept of a dependency on a specific component vs on any component that conforms to the specified interface, then perhaps In fact, couldn't it use a single keyword? Something like:
If needed, it seems a Just my two-cents! Thanks, |
) | ||
``` | ||
|
||
A wasm component that contains `unlocked-dep` imports is referred to as an "unlocked component". Unlocked components are what you normally would want to publish to a registry, since it allows users of the unlocked component to perform the final dependency solving across a DAG of components. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to my comment on this PR, but an "unlocked component" really doesn't seem self-explanatory to me. Language like, "coupled component" or "tightly coupled component" seem to more readily express the fact that the component depends on implementations rather than interfaces.
I think this is a great place for you to voice such an opinion @James-Mart, really appreciate the feedback!
Something that may impact your opinion... At the moment we don't really anticipate that developers would ever author this syntax in WIT themselves, or at least not often. More commonly, toolchains would deduce that this WIT should be used for bindings generation based on a dependency specification in a package file. As for the syntax suggestions, I don't feel super strongly one way or the other immediately. It's valuable to hear that some of the verbiage/syntax doesn't feel intuitive to you, but I also think we have yet to fully document the entire lifecycle of an unlocked dep/unlocked component. I was having a bit of difficulty deciding how much of this information belongs in this PR vs in other places, but I'll try and add some context that helps provide some clarity around some of the points you're raising. |
@James-Mart Even if mostly folks won't manually write their component dependencies in WIT often, I'm definitely happy to discuss what is the most intuitive syntax/naming that we can find for WIT (which I agree can be distinct from what goes into the raw WAT) since I think folks will at least occasionally read synthesized WITs that use this syntax. So thanks for bringing this up! One observation is that both world w {
dependency ns:foo@{>=1.0.0}; // => unlocked-dep=<ns:foo@{>=1.0.0}>
dependency ns:[email protected]; // => locked-dep=<ns:[email protected]>
} (We'd also need to invent some syntax for where to include the Separately, I've wondered whether there's value in having an explicit world w {
import dependency ns:foo@{>=1.0.0}; Syntactically, there's no need for As a last detail, I think that |
Ah, that does make sense for general use. Although I think in the context of my use-case for component development this may be the mechanism that developers are instructed to use pretty regularly, since they are building components that are always dynamically composed, and there can be multiple implementations of each interface, so developers need some way to indicate to the runtime which instance should be linked.
What about just
Definitely +1 for this
Without the explicit import, I think a reader is more likely to assume that |
That's a good thought, and it makes technical sense if you're familiar with how The nice thing about the word "dependency" is that, from what I've seen, the word "dependency" is used by package managers and their associated build-config files (e.g., Just to see an example of world w {
import wasi:keyvalue/store;
import wasi:http/handler;
import dependency gh:sqlite@{>=1.0.0};
} IMO, that seems decently unambiguous (and an improvement from the original proposed syntax). But that's just from my limited perspective; happy to discuss more! |
824fdc5
to
74bd278
Compare
This PR servers as an initial description of what the desired behavior of an
unlocked-dep
would include. As exemplified in the PR, most language toolchains would probably use it in combination with nested interfaces, which is currently in the process of being implemented.The primary motivation behind this syntax is enabling a way for registry tooling concerned with package resolution to resolve implementations of components (rather than wit). This should hopefully enable workflows where users can depend on specific components, rather than interfaces whose implementations need to be specified at a later time.