-
Notifications
You must be signed in to change notification settings - Fork 171
Proposal: package management #851
Comments
Generally like that Go's module system is the inspiration and that much can be copied over. However I have some concerns about reusing the
This seems like it will prevent many use cases for CUE + Go dependencies in the same project. If I have a repository which is both CUE and Go, and the set difference of their respective dependencies is non-empty, then wouldn't the respective Am I missing something here? |
The reasons are exclusively attributable to re-using the proxy.golang.org and sum.golang.org infrastructure. The answer to "Why have you chosen to use {proxy,sum}.golang.org?" has the rationale.
At least as I envisage it, Perhaps I should be more explicit about this above? I had hoped to stash this file out of the way in |
I can't say that I receive the idea of reusing the Go modules scheme with delight. In principle, MVS centralizes the control over the version of a package I'll use in my project; I've had a lot of success in lockfile-driven systems being able to exclude releases that were incompatible with my usage. MVS also assumes that package authors want to and and able to perfectly conform to semver; if the best intentioned authors sometimes break interfaces with a release. Finally, MVS makes strong assumptions about the quality of releases, but then defaults to a source-repo-as-package-repo approach. In practice, I haven't found that vgo has enabled large Go projects the way that it was intended to. More and more, I work with container orchestration, and both the Kubernetes and Docker projects are a challenge to incorporate via gomodules. Incorporating version resolution into every Go task leads to a lot of confusing and frustrating issues. My intuition is that only local changes should influence the outcome of a build, but post gomodules, I find that any of my dependencies can break things. Personally, I was having great success with the "official experiment" of godeps. It was a familiar and well-worn approach with version satisfaction and lockfiles. The process by which it was overturned felt (as a daily user of Go) authoritarian and ill-considered. It's possible that CUE avoids a number of these issues because it doesn't have a large legacy base to support, but I wanted to give voice to my trepidation. |
In order for CUE to maintain both, will it be reading the Go code and resolving used imports? Is that even something CUE should be doing? Are there examples of other languages where they are crossing dependency management or repurposing dependency files? Do we need a module proxy for a first module implementation? I'd argue that Go's ecosystem had been quite vibrant long before it had this. Having the sumdb for security & reproducibility is a lot easier. ( w.r.t. the current sumdb format, it's a line oriented file & parser and super simple, though having it specified in CUE would not impede reusing the existing code |
@nyarly I would say that MVS is a selection algorithm with deterministic results and that Go added some assumptions that align with the Go 1 compatibility guarantees. Go's module system does have exclude and retract clauses (https://golang.org/ref/mod#go-mod-file-exclude). I think the semver topic is orthogonal to the MVS vs Lockfile, as npm / yarn dependencies are often specified in semver format. There are authors in all languages that will break semver rules. I don't think the dependency selection method helps here. Anecdotally, as a daily Go user too, I welcomed the addition of the |
@myitcv in the section "Changes to cmd/cue" there are several references to a |
Thanks for that catch. The proposal is that CUE's vendor directory will be |
@verdverm Tempted though I am, I don't mean to re-litigate the go modules design here. With regards to CUE though, my impression is that adopting much of that design is settled. If that's so, I don't want to foster acrimony by questioning those decisions here. |
New If we just want to get the at least, we may need github will return
gitlab only handle
|
Another question Could we add non-cue requirement in
|
I believe Auto-detect -> gen would break when I have repos which are both CUE and Go modules (I have several already and know that the "Go" files found would not parse (they are actually It does seem nice to have a way to |
@verdverm. Thanks for your comments/feedback.
Yes, in the case of a Go + CUE module,
This is of course the fundamental question when it comes to this proposal. The arguments in favour and against are laid out in "Why have you chosen to use {proxy,sum}.golang.org?". If there are points you feel are missing, or need stressing more clearly please let me know.
This is not disputed. However, there are real benefits to using a proxy and sumdb. As I mention about though, it's whether those benefits outweigh the costs.
I'm not clear what point/position you are advancing here. sum.golang.org fulfils a specific role, namely to fill in the gaps if a module does not already have a
Again I'm not clear whether you're arguing in favour of the current proposal to use a |
@morlay - thanks for your comments and feedback.
I think you might have missed the bit in the proposal that talks about exactly this fallback:
So any existing code host that replies to The loader would also, like Hopefully that allays your concerns on this point?
I'm not sure this would help, because other package management systems would then have to learn to read Instead, per #646, The alternative to generating from Go/other source code (and the tooling dependencies that implies) is for those projects to pre-generate CUE, and for the dependency to be on the CUE module. In which case no dependency on tooling etc exists.
Apologies, I'm not entirely sure I follow what you are saying about extractors. I think (and please correct me if this is wrong) you are suggesting that steps could automatically be run when adding/loading a package dependency. If so, I don't think that's a direction we want to head in. The |
But in such a scenario of there being a specific CUE module (thereby obviating the need to generate from Go source) then there would be no requirement of the user of that module to have a |
When it comes down to it, I do not want CUE touching my Go, (
I fail to see how this statement aligns with the proposals idea for CUE modifying Go module files. Is this not what the In early conversations about CUE modules (iirc) @myitcv said that a first implementation would not have all of the bells and whistles. I think that any use of |
Some more questions on possible conflicts with CUE's use of
Given a dual module (both Go and CUE), I see some issues with supporting the following:
|
Another possible edge case:
Essentially there is a dependency which needs a |
Scenario:
But I don't see how the I suppose any CUE official k8s could have a different module path, but not necessarily required given replaces are possible I think there are more situations around replace than those I outlined above, outside of a "dual module", where both a CUE and Go module share the same path, and the Go module is only using CUE in CI, not for part of the code |
@myitcv Sorry. I missed
I understand this concern. In my hack, i use new attr
But this is not work for other non-vcs-based pkg managment, like npm. I agree, we prefer to use
I implement another extractor https://github.com/octohelm/cuemod/tree/main/pkg/extractor/golang to generated cue defs from go codes without using so Howerver, we need
Yes. it is. the example flow in my hack tool:
Howerver, I think generating & pushing somewhere, and importing generated code is better. @myitcv Thanks again. |
A potential embed functionality should not be allowed to embed cue.mod or any subdirectory that crosses a module boundary (like in Go. If I'm not mistaken, this edge case cannot occur with these restrictions. |
@verdverm If it is desirable for the CUE and Go to be versioned independently for external, it is generally a good idea to put it in separate repos. Note that tagging would otherwise get a bit messy, if nothing else. Also not that for importing only, that is if the CUE in a module is not to be shared, but if one still wants to include other modules, it is possible to use an "anonymous" module. Anonymous modules do not require a top-level |
@nyarly I'm not wedded to semver at all, and neither was MVS IIRC: if there are no backwards incompatible changes, having one version is really enough (see also https://www.youtube.com/watch?v=oyLBGkS5ICk). The reasons semver was chosen is because it was a de facto standard in the community and there needed to be something. With the special interpretation of major version |
@mpvl My understanding is that Personally I don't see this as "co-sharing", because Go has no say in this, it is a unidirectional CUE choice. I am unaware of any other situations like this. It is unusual and feels like an anti-pattern or bad hack. This is the opposite of my experience with other CUE decisions, which have taken a thoughtful and paced approach. Go took 10 years to include PROXY / SUMDB. Why are these services required for a first implementation? Generally, what I don't like as a language A user is some other language B messing with my lang A dependency files to merge in its own. Especially when it has its own already. Then to add an extra file to my project because it wants to do this, so that lang A toolchain doesn't undo lang B's abuse, this is not desirable. There are conditions where this will break and it forces lang B's choices for code organization. The proposal does not state where this extra file will live, but given that people have all sorts of ways they organize code, it's likely to make someone unhappy. Question wearing a security hat: Could an unneeded lang A dep (think malicious package) be more secretly included via a I find the proposed benefits / drawbacks incomplete and several complications have not been addressed. The claim that doing this Missing tradeoff points:
|
Of course if this is true, then the same issue exists with using any combination of repos. The thing is that for most package managers, reproducibility isn't really a feature anyway, so having a few more version skews seems like the lesser problem, or at least something that justifiably should be tracked manually. With MVS, we have the possibility of addressing this automatically. It seems to argue even that Go's MVS system should be pluggable.
We are looking at the problems at hand, rather than what is conventional. I can't say I'm thrilled with approaches taken by most other languages (there are some notable exceptions, like Nix). MVS seems to fit the constraints of configuration and CUE particularly quite well and piggybacking on Go not only is convenient, but also avoids some bad dependency skews that are otherwise impossible to solve. Given this reality, though, I do think the design needs to change a bit.
Those 10 years were not without battles and desperation of not having dependency management. There is no need to repeat that history if we have something good now.
There is no need for that. It can be accomplished by generating a Go file capturing cue dependencies hidden in the
The
I don't really don't see how this setup can influence the outcome of existing Go code other than forcing a newer version. |
I have a
Remote PROXY / SUMDB are not required for dependency management, they are optimizations for package cache and a "trusted" hash store. My understanding is that reusing these are the sole reason for using a
Are you saying CUE is now intending to use I have been using MVS dependency management with both CUE and Go in the same repos, successfully, without commingling. (hof has a reference implementation that uses MVS without commingling, that includes sumdb calculations, without the SUMDB remote, no PROXY needed) There are many organizations with polyglot repositories that get along just fine without mixing language dependencies and systems.
Besides increasing download and build times, forcing newer versions is an important point and a source of malicious dependencies. This is a security concern. By creating a dependency system that starts in Go, can then jump to processing CUE only dependencies (via a
I am also a fan of MVS and reusing this scheme in CUE. My disagreeability is specific to CUE using
This is in fact what |
RE: embeds Go allows embeds within a module, and then a Go binary that imports that module will have them as well. Trying to work through an example, my Go program has some dependency D. In the case that...
Now if the maintainer of D has not vendored their CUE code (including Does that sound correct? |
RE: language jumping via intermixed dependencies Maybe a more succinct way to describe my concern here is that two independent Go sources (Module A does not have any dependency path through Go code to Module B) can nonetheless have a version bumped through a CUE module to B, due to a common Go dependency and MVS. I believe only one dual module is required to link the dependency DAGs and force A to include B in its MVS calc. That dual module does not need to use CUE and Go together (CUE for CI only). Given enough widely used modules in each language start connecting, this could have a significant impact in the number of dependencies accumulated through an intermixed MVS and the subsequent versions. |
We're working on a new proposal that separates concerns a bit more and explains some of our thinking.
Overall I don't share that concern. In fact the opposite. If the bump occurs because Go and CUE are co-versioned, then there is, or at least may be, a dependency. But we're going in circles here.
Not saying that is not possible, but there are certainly cases where not commingling will result in badness. Do you have a documentation of how MVS in hof works? |
I can't find my good docs at the moment... I think they are somewhere in a git history The code is pretty much limited to this directory: https://github.com/hofstadter-io/hof/tree/_dev/lib/mod This was the original, separate program before merging into It should be possible to simulate the intermixed language MVS with a custom |
It's the case where there is not direct dependency, or thus an overlap between two MVS DAGs in GO, but only because CUE has bridged this gap by mixing the dependencies, that they become intertwined ( creates an MVS overlap ) A a common dependency at two versions (a leaf in the MVS DAG) must now be the same, for two independent DAGs (as far as MVS is concerned). The extra download is one outcome. Breaking builds (or requiring significant code change from a security update downstream dep) is another, because not every project adheres to SemVer. |
I understand the practical reasons for wanting to use a That is not necessarily a bad positioning - it can even be a smart way to piggy-back off the Go ecosystem to create a Go+Cue ecosystem. But it is quite different from the stated goal which is to make a universal data language. In short there are far-reaching implications in terms of the positioning of the project, and defining its indended audience. |
To echo @shykes's point, were I not already involved deeply with Go development, if I came to CUE freshly and saw this use of Go tooling, I would turn away. If, instead, I found CUE using, say, Node.js tooling, or Java tooling, I would turn away, thinking, "I came for the configuration and data manipulation, not for the integration with a single ecosystem that I don't want to use." |
@myitcv it doesn't seem like the proposal as written extends Go Modules to avoid the social behavior we see emerging in the Go ecosystem, in response to the limitations of Semantic Import Versioning and the constraints they put on Module authors. I'm talking specifically about the emerging behavior where more and more module authors are refusing to ever release a v1 of their module, due to challenges with tooling and not because they intend to always offer an unstable API. This pattern of behavior has emerged from those authors feeling like the SIV provides too much of barrier and support burden to use semantic versions, especially when moving past v1. Is that a behavioral pattern, and subsequently a sociotechnical risk, that we're comfortable also adopting here? |
Everyone: please note that this proposal DOES NOT require Go tooling to be installed. Not to say there are other points, which we are addressing in a new proposal. @theckman: as I said earlier, I'm not wedded to semver at all, and even don't like it all that much. But one needs to have something, no? Any alternatives that do not suffer from these issues? @verdverm is suggesting another approach using MVS, which I reckon in your opinion would suffer from the same issues? We need a hermitic reproducible way to manage modules. Also, it is imperative to unique packages versions across imports. Neither SAT solving nor a Nix-style approach seem to solve this. MVS is a good solution for this. I'm not aware of any other algorithm that achieves this. |
So in advance of the new/revised proposal, here is a description of the commingling issue. The large groups/yellow blocks represent VCS repositories. At the top is a repo ( Repo G isn't technically necessary here, but the graph renders more nicely with it. At the bottom is a repo ( Now This issue can be avoided by ensuring that there is only one version of repo To emphasize, this is not specifically a Go+CUE issues but really an issue for any language or system where CUE is included as a description of an API or other form of contract. The general solution seems ensuring that any resolution within one repo will select only one version of a repo across languages, satisfying all other constraints. There may be other ways to ensure that repos are unique across different languages. But co-resolving versions seems like a possibility. We have some ideas how to make this more pluggable and even allow resolving multiple languages at once (within limits). But we would love to hear about other solutions that can solve this issue. @verdverm: can you explain why you think this is not an issue and how you would solve this in your (independently resolved) MVS approach? |
@mpvl So the first thing I think I should call out is that Go Modules, as it exists today, marries you to semantic versioning, and makes it extremely difficult to use any other versioning scheme (including calendar based). I think we can address that issue, and the one I raised earlier around the risks of people not releasing v1 modules, by either making Semantic Import Versioning (SIV) optional or removing it entirely. This issue, which is a proposal for optional SIV in Go, may help provide more context: golang/go#44550 The idea would be to not include the major version in the name of the Module (e.g., |
I'd agree with @theckman, I'm not a fan of the required MVS could certainly support using SemVer or Calendar versions, per module, such that both are supported and a module is free to choose either. A module would not be able to use both or switch once it is chosen (I believe). @mpvl A picture is worth a 1000 words. This is definitely a problem, an in this case, commingling would force my upgrade to Go v1.1.0. Knowing that SemVer is not strictly adhered to, I could be forced into making changes to my own code. This would be more irritating when it happens in downstream dependencies and my Go only program is updating a Go only module, but some other dep breaks things, because it's dep chain was forced to bump versions due to a CUE only module. That being said, these situation look to only happen when choosing versions initially or updating direct dependency versions, and the downstream impacts therein. My issue is for Go only users (or other languages) who have no attachment to CUE. In this case my own app depends on a (series of) module(s) in some commingled MVS chain, and that a Go version is now required to increase because of a CUE only module, far removed. Supporting N languages together seems like it would result in compounding dependencies, as one multi-language module can force me to version align with and download a huge number of modules in N other languages which are irrelevant to my own application. I will make a picture as well, so that this is clearer. Generally I think mixing in more languages is going to make this problem even worse and that CUE should only worry about CUE and not N languages. Is it really in CUE's scope to solve dependency management issues across multiple languages? If there are compatibility issues, my opinion is the user should be left to deal with this. CUE forcibly injecting itself into other languages dependency systems is seen as not playing nice with other ecosystems. I think this will hurt adoption (as @seh related). Personally I will have to think about how I would proceed, with the current choices being maintaining a fork to disable this behavior or replacing with something else. I can say I will not use a CUE that has this approach to commingled dependency management. RE: Go tooling, I think we are talking more generally than in simply requiring the Go binary to be available. I consider using the Go libraries from a CUE binary to achieve the same thing using Go tooling ( reading/writing |
Leaving aside Flakes, a Nix style approach would require that every requirement of a package be made explicit. For instance, my current CUE-related workspace is built using an expression that includes: cue = { stdenv, buildGoModule, fetchFromGitHub, lib }:
buildGoModule rec {
pname = "cue";
version = "v0.3.0-beta.6";
src = fetchFromGitHub {
owner = "cuelang";
repo = "cue";
rev = version;
sha256 = "02fafsl5krvkxc9p5acz38hb9zbp1gz5zvz1cvgr7g5dd9h2j8fv";
};
vendorSha256 = "0drnmf8gfj3k74z5zh0032g5kg3nzji5ji1m3qbxfzisp5cvba7m";
#doCheck = false;
CGO_ENABLED=0;
buildFlagsArray = ["-ldflags='-s -w -X cuelang.org/go/cmd/cue/cmd.version=${version}'"];
}; (oops! I need to update to 0.3.0! 😄 ) On the one hand, I think Nix does provide hermetic, reproducible module collection. If not, maybe I don't understand what you're envisioning as that requirement. The irritation with Nix (and honestly, it's not a huge issue) is that collecting those digests is frustrating. The pragmatic approach, generally, is to put in a fake digest (nixpkgs includes a lib.fakeSha256 constant...) and then copy the real digests in from the build errors. It's a kind of poor man's TOFU system. The other thing about Nix is that dependencies tend to be resolved distro-wide. In some way's that's reasonable: no OS distribution really ships multiple versions of glibc, say. Nix is nice in that different versions of the distribution can co-exist, and different applications can use different glibcs if needed. For language-based distribution systems, the domain of version resolution is different. CUE, I think, wants an application domain, right? Everything is resolved together, and the way unification works puts me in mind of a hypothetical issue with old package.json style NPM resolution: if my app has an indirect dependency to another package via two paths, it's possible the code gets a value from one place (and therefore version X of my dep) and passes it to another that uses version Y. I think it's a subtle enough problem that it may have occurred and not been recognized. My point is that, a whole-application SAT solution (and here I think of Bundler from Ruby), can solve this issue well. People complain of "dependency hell" (and build NPM), but the upshot is that versions need to be resolved at the application domain in Ruby, and therefore you really can only have one version of any module. Bundler produces a lockfile with exact versions (which are held as source packages rather that VCS revisions), which also gets a lot of the way to reproducibility, and solves the fiddly and error prone process of resolving exact versions manually. |
With regards to semver: I think that's a really interesting point for CUE. As defined, semver talks about changes in interface, which I understand as being related to the functions a module exports, basically. CUE is a different kind of language than the ones semver was developed to describe, and I wonder if it really is appropriate to describe a CUE module in terms of its interface. |
@nyarly yes, nix is indeed hermetic, but it indeed doesn't solve the other problem you mention of enforcing only a single version different instances of the same import. Semver is still somewhat relevant to CUE, but as long as the import paths distinguish major versions, one could argue that only minor versions are relevant. Then again, the same could be argued for programming languages. I don't find SAT solvers give satisfactory results and dependency resolution at scale is a nightmare. CUE being different also opens up possible solutions. It requires all imports of the same package to be the same version, but only barely. For instance, in the above problem, we could expect users to manually "downcast" (#454) versions of there is a possibility for discrepancies. But this doesn't obviate the need to align versions used in one language with an import in CUE (although in this case it would be a simpler exercise). Also, the commingling issue is to some extent also something that is particular to CUE, or any language that is included alongside some system to describe its API for that matter. Some of the solutions may also come from guidelines and best practices. For instance, the above issue would more or less resolve itself if all CUE that depends on a commingled setup is itself managed within the same package management system. These could still depend on independent CUE repositories, but not vise versa. I'll try to write something up about this, but I believe with such restrictions it actually becomes doable to achieve consistency in a multi-lingual setup. @verdverm we certainly don't plan to offers integrations for all languages. But I think a solution should be pluggable enough to make such integrations relatively straightforward. @theckman Yes I know the Go modules depends on Semver. Although, I'm not a fan of semver, I am a fan of standardization, and ultimately it doesn't matter that much. The point of people sticking with v0.x is an interesting one, though. Hard to avoid if one allows for a standard of specifying a ramp-up of backwards incompatible changes, but probably having such a mechanism have more of a wall-of-shame feel to it would help. I think in the end eco-system incentives are a good way to go there, though: automatically detect that the semver guidelines are followed and demote packages that are not at v1 or break semver rules. I know this is not fool proof either. |
BTW, for those unfamiliar, Rich Hickey's Spec-ulate is an excellent talk about this topic: https://www.youtube.com/watch?v=oyLBGkS5ICk. An interesting thing to keep in mind when watching this is that CUE can actually be used as a "spec" definition, describing and validating backwards compatibility. This gives an idea how CUE could be different. |
This seem relevant and related: golang/go#36460 (cmd/go: lazy module loading, changes to how Go will process and manage transitive dependencies) Similar complaint about large numbers of modules being downloaded through dependencies: golang/go#29935 After reading https://go.googlesource.com/proposal/+/master/design/36460-lazy-module-loading.md I can't help but think that trying to maintain parity with Go across versions (and future changes) from CUE would take valuable time away from maintainers who could otherwise make CUE good at what CUE does best. Thoughts? Would having a command or function to check and warn (optionally error) about mismatched versions from the same repo be a sufficient solution to the problem described by @mpvl without the need to manage the |
This issue has been migrated to cue-lang/cue#851. For more details about CUE's migration to a new home, please see cue-lang/cue#1078. |
With extensive inputs from @mpvl.
Proposal summary
We propose adding package management to CUE, using an approach analogous to Go using Minimum Version Selection and semantic versioning. The changes are limited to
cmd/cue
and thecue/load
package, and hence have no bearing on CUE the language.As an interim measure, we propose using proxy.golang.org as a module mirror, and the checksum database sum.golang.org for authentication, until such time as the CUE project can host such services itself. Use of these services will be enabled by default for
cmd/cue
andcue/load
, but entirely configurable, just likecmd/go
.The proposed approach is broadly identical to the approach followed by Go modules. Indeed this proposal borrows heavily from Russ Cox's blog posts introducing
vgo
, the core Go modules documentation, and interactions with Bryan Mills and Jay Conrod from the Go team.For those who might be less familiar with Go, in particular Go modules, for the more significant parts of this proposal the relevant parts of the Go module reference have been copied and adapted for the CUE context to save jumping between multiple documents. For less significant parts of the proposal, or those for where the concept is truly identical between both approaches, then we generally choose to link to the relevant Go documentation.
I understand Go modules; what's the TL;DR?
Here is an abridged version for those who have a working knowledge of Go modules, presented from the user perspective with various implementation considerations thrown in where relevant.
What will be different?
cue get go
tocue import go
- see cmd/cue: rename 'get go' to 'import go' #646.cue.mod
directory; the root of a Go module is identified by ago.mod
file.go.mod
file that mirrors thecue.mod/module.cue
file.cue.mod
directory without amodule.cue
file); in Go this is not possible.cue.mod/module.cue
; this file is semantically equivalent togo.mod
, with all directives supported albeit via a different syntax.cue.mod/sum.cue
as its equivalent ofgo.sum
, but in a CUE format (for consistency withcue.mod/module.cue
).cue.mod/pkg
, as it is today.cue/load
package for loading CUE instances. Go has no equivalent, and instead relies ongolang.org/x/tools/go/packages
to wrapcmd/go
. Working with CUE modules therefore does not require the user to havecmd/cue
installed.cue/load.Config
configuration type is extended with the necessary fields for controlling module resolution and loading behaviour; these are essentially the backends to the frontend flags and environment variables available incmd/cue
.cue-import
<meta>
HTML tags with a fallback togo-import
.What is the same?
$HOME/cue/modcache
, and be configurable viaCUEMODCACHE
.cmd/cue
will have an analogous set of module-related commands ascmd/go
::cue get
cue list
(andcue list -m
)cue mod download
cue mod edit
cue mod graph
cue mod init
cue mod tidy
cue mod vendor
cue mod why
cue env
cmd/go
's module-aware command flags and environment variables:GO*
environment variables have equivalents of the formCUE*
,-mod
has a CUE equivalent--mod
,-modfile
has a CUE equivalent--modpath
.--mod=vendor
remains an option. The existence of a vendor directory (cue.mod/pkg
) implies--mod=vendor
. This approach represents a backwards compatibility mode for how things work today.@latest
et al).Background
The module and package concepts of CUE are directly inspired by their equivalent in Go. It is natural therefore to consider package versioning following the same model as Go.
One important and pleasant fact to note is that because CUE has already established the concept of a module, it does not have to manage a large legacy pre-modules world, unlike
GOPATH
in the Go project. This is reflected in some of the decisions in this proposal. As a result, we do not need to distinguish between module-aware mode non-module-aware mode: everycmd/cue
command is and will remain module-aware by definition, the same is true forcue/load
.The issues
As noted above, CUE has already established the concept of a module. The root of a module is denoted by a directory that itself contains a
cue.mod
directory. The contents of this directory are mostly managed by thecue
tool. In that sense,cue.mod
is analogous to the.git
directory marking the root directory of a repo, but where its contents are mostly managed by thegit
tool.Here is a minimal example that declares a CUE module
example.com/blah
, a packageblah
within the root of that module, such thatblah
imports a third-party CUE packageacme.com/quote
that is vendored withincue.mod/pkg
(written in thetxtar
format):From the root of the module we can
cue eval
:Because there are no arguments passed to
cue eval
, the implied argument is.
, the package in the current directory.cmd/cue
then hands off tocue/load
to resolve and load.
.In this example,
example.com/blah
is referred to as the main module. As we can see from the packageexample.com/blah
, it has a dependency onacme.com/quote
, a package that is not part of the main module.cue/load
currently uses the simple rule therefore of searching for all dependencies outside of the main module withincue.mod/{pkg,gen,usr}
directories, unifying the result.In this instance, the
acme.com/quote
package is not part of a module, but it could well be.So whilst
cmd/cue
automatically takes care of resolving imports or package paths for us (viacue/load
), everything else is left to the CUE developer. Vendoring of packages withincue.mod/pkg
has to be done by hand, there is no mechanism by which dependencies can be fetched from source code hosting sites or remote repositories.For an initial implementation of
cmd/cue
this was more than sufficient. Indeed, users have adapted shell scripts to help with creating minimal vendors, and thehof
tool has support for building such a vendor viahof mod vendor
.But such an approach will neither scale for a larger CUE user base, nor an ecosystem of tools built on top of/with CUE and the
cuelang.org/go/...
APIs. Specifically such an approach:Reimagining our example above, we want to:
acme.com/quote
, without needing to worry about how and where we fetch that code;example.com/blah
is reproducible;cmd/cue
to control the dependencies of the main module, including automatically fetching CUE modules when required;Requirements
The following requirements have driven our thoughts on why and how to add package versioning to CUE.
Package versioning in CUE must:
cmd/cue
orcue/load
: simplicity, speed, and understandability. In general, version management work must fade to the background, not be a day-to-day concern;For the remainder of this document we borrow slightly adjusted definitions of the following terms from the
vgo
proposal:One important difference from the Go modules implementation is that the CUE implementation should not live entirely in
cmd/cue
(as it does incmd/go
). Instead the bulk of the implementation will lie withincue/load
, with a change tocmd/cue
effectively being the means by which the main module and its dependencies are controlled from the command line. That way, existing users of thecuelang.org/go/...
APIs can continue to load and work with CUE instances, enjoying the same benefits that a seamless module experience will bring tocmd/cue
. For tool authors who use the API but also requirecmd/cue
-esque control over CUE modules and dependencies, thencuelang.org/go/cmd/cue/cmd
command instances can be created and run without requiring the user of their tool to have also installed CUE, as is the case today.A key building block in CUE is the Go compatibility promise:
Correspondingly, this proposal adopts exactly the same concept of semantic import versioning introduced with Go modules, and with it the import compatibility rule for CUE:
Detailed Proposal
This section gives a brief overview of the proposal. Details are presented in the next section.
Throughout the rest of the proposal, the term "the loader" generally refers to the loading of modules and packages that happens through the use of
cmd/cue
orcue/load
.Rename
cue get go
tocue import go
In preparation for full module support in CUE, we will need to repurpose an existing
cmd/cue
command, renamingcue get go
it tocue import go
. The detail of this is covered in #646.This change fits nicely with a recent change to the current semantics of
cue get go
. Prior to a616925,cue get go
attempted to resolve its package arguments that were not fully satisfied bygo.{mod,sum}
automatically, via the use ofgo/packages
(which itself usescmd/go
). This would result in changes togo.{mod,sum}
.With a616925 we have instead shifted to a model of requiring that a Go dependency of
cue get go
can be fully resolved viago.{mod,sum}
without requiring further changes to either. This aligns with the new Go 1.16 default ofcmd/go
build commands assuming a read-only default.This also aligns well with the new name
cue import go
: unlikecue get go
, the command does not imply any fetching or resolution.cue import go
will therefore fail in case any of its arguments cannot be fully resolved via the currentgo.{mod,sum}
, and the user will need to rungo get -d
(or equivalent) to ensure full resolution is possible.As part of this rename,
cue import go
will be changed to generate into thecue.mod/imp
hierarchy:cue get go
incorrectly generates files within thecue.mod/pkg
hierarchy.Declaring module dependencies
At the core of this proposal is the ability for a CUE module to declare dependencies on other CUE modules through versions. This will be done by an extended schema of
cue.mod/module.cue
files. Expanding our our example from earlier:Note:
example.com/blah/v2
. Like Go modules, CUE modules adopt the concept of semantic import versioning. In this case, our move to a new major version indicates that we have made breaking changes compared toexample.com/blah
, and this is reflected in the module path. A consumer of theblah
package at the root of this module would therefore writeimport "example.com/blah/v2"
.acme.com/quote
from thecue.mod/pkg
directory, and instead replaced it with arequire
field in our module definition. Cryptographic sums incue.mod/sum.cue
allow us to verify the contents of theacme.com/quote
module whenever it needs to be downloaded.go.mod
file that looks very similar to thecue.mod/module.cue
file now exists at the root of the CUE module. At least initially it is planned to host CUE modules on top of the existing Go modules infrastructure, specifically the module mirror at proxy.golang.org, and the checksum database at sum.golang.org. Declaring such a file is necessary for us to satisfy the GOPROXY protocol and existing checksumming mechanisms. The loader will automatically manage this file, effectively keeping it in sync withcue.mod/module.cue
. More on this belowUsing
cmd/cue
to control/list module dependenciesWhilst it would be possible to maintain
cue.mod/module.cue
,cue.mod/sum.cue
andgo.mod
by hand, it would be an incredibly frustrating and error-prone process. Instead,cmd/cue
will be modified to support controlling and listing module dependencies, and updating the files that declare those dependencies.For example, in the course of developing
example.com/blah/v2
we might well have run:This resolves
acme.com/quote
to the latest version of a module that provides the packageacme.com/quote
and modifiescue.mod/module.cue
,cue.mod/sum.cue
andgo.mod
accordingly to record the dependency.cue get
is therefore directly analogous togo get
in terms of its role in dependency management.cue get
will, likecmd/go
, support various version queries to control the dependencies being added, removed, upgraded or downgraded. For example@latest
is the version query implied when, as above, no@$version
is specified.The
cue list
command, directly analogous togo list
, will be added to provide information about CUE modules and packages. For example we could request information about theacme.com/quote
package in JSON format as follows:The
cue mod
command will be expanded with subcommands for more fine-grained control ofcue.mod/module.cue
,cue.mod/sum.cue
(andgo.mod
indirectly). For example:would add a
replace
"directive" tocue.mod/module.cue
, which tells the loader to load all versions ofacme.com/quote
from the directory/path/to/acme.com/quote
. The resultingcue.mod/module.cue
andgo.mod
would then look like this:The
--mod
flag will, for allcmd/cue
commands that load/resolve package import paths, control the behaviour of resolution. For examplecue list --mod=vendor $pkg
will limit the resolution of the package pattern$pkg
to the contents of thecue.mod/pkg
directory, matching the behaviour of today. The default will be--mod=readonly
, withcue get
being the exception because its whole purpose is to modify the main module's dependencies.Using
cue/load
and thecuelang.org/go/...
APIThe Go project entirely abstracts the loading of Go package and module information within
cmd/go
.go/packages
exists as a wrapper forcmd/go
to load Go packages for inspection and analysis.CUE has a dedicated package for loading CUE instances,
cue/load
. This package is used bycmd/cue
and users of thecuelang.org/go/...
API. As such, there is no need for ago/packages
equivalent, at least not equivalent functionality that wrapscmd/cue
.Users of the
cue/load
package will benefit from seamless module support, with thecue/load.Config
type being expanded with options relevant to controlling module resolution. The following example demonstrates how a custom proxy serving private modules would be set during the load process, with a specification that the sumdb should not be consulted for those private import paths:Use of a trusted module mirror and checksum database
In August 2019, the Go team at Google launched the Go module mirror and checksum database were launched. As part of the Go 1.13 launch,
cmd/go
used both by default. There are some important points to note about this setup:The use of a checksum database is a key component of verified and verifiable builds, or evaluations in CUE terms, and hence trusted reproducible builds (evaluations).
As an interim measure, we propose using proxy.golang.org as a module mirror, and the checksum database sum.golang.org for authentication, until such time as the CUE project can host such services itself. Use of these services will be enabled by default for
cmd/cue
andcue/load
, but entirely configurable, just likecmd/go
. We have permission from the Go team for this proposed use of these services.As covered above, this places one noticeable constraint on the CUE module implementation: that we must include a
go.mod
file at the root of a CUE module. This is required for our use of proxy.golang.org and sum.golang.org to satisfy theGOPROXY
and sumdb protocols, but also crucially to indicate to the proxy (which runscmd/go
) that a repository is Go module-aware, as opposed to not (remember, we don't have such a consideration in CUE). This last point is significant in the case of multi-module repositories, which we intend to support in CUE.Use of the proxy and checksum database will largely fade into the background for users of
cmd/cue
andcue/load
.For example, under a default configuration and assuming that
acme.com/quote
is public, the following command:would:
acme.com/quote
(which we know to be the module providing the package at the same import path);acme.com/quote
as the MVS algorithm proceedsUsing
cue env
to understand the currentcmd/cue
configurationThe
cue env
command will be added to show the current configuration to the user, for example:Much like
cmd/go
, the setting of an explicit environment variable will have the highest precedence, else if a variable is not set then a configuration default can be set in the file located atcue env CUEENV
using thecue env -w
command. See thecmd/go
environment variable documentation for more details.Detailed design
Finding a module for a module path
The details in this section largely follow the pattern established with Go module and package resolution.
When the loader needs to resolve an import path to a package and/or module (the concept of a module proxy is fully introduced below), they start by locating the repository that contains the module.
If the module path has a VCS qualifier (one of
.bzr
,.fossil
,.git
,.hg
,.svn
) at the end of a path component, the loader will use everything up to that path qualifier as the repository URL. For example, for the moduleexample.com/foo.git/bar
, the loader will download the repository atexample.com/foo.git
usinggit
, expecting to find the module in thebar
subdirectory. The loader will guess the protocol to use based on the protocols supported by the version control tool.If the module path does not have a qualifier, the loader sends an HTTP GET request to a URL derived from the module path with a
?cue-get=1
query string. For example, for the moduleacme.com/quote
, the loader will send the following request:The loader follows redirects but otherwise ignores response status codes, so the server may respond with a 404 or any other error status. CUE will not support an equivalent of
GOINSECURE
.The server must respond with an HTML document containing a
<meta>
tag in the document's<head>
. The<meta>
tag should appear early in the document to avoid confusing the loader's restricted parser. In particular, it should appear before any raw JavaScript or CSS. The<meta>
tag must have the form:root-path
is the repository root path, the portion of the module path that corresponds to the repository's root directory. It must be a prefix or an exact match of the requested module path. If it's not an exact match, another request is made for the prefix to verify the<meta>
tags match.vcs
is the version control system. It must be one ofbzr
,fossil
,git
,hg
,svn
,mod
. Themod
scheme instructs the loader to download the module from the given URL using the CUEPROXY protocol (see later). This allows developers to distribute modules without exposing source repositories.repo-url
is the repository's URL. If the URL does not include a scheme (either because the module path has a VCS qualifier or because the<meta>
tag lacks a scheme), the loader will try each protocol supported by the version control system. For example, with Git, the loader will tryhttps://
thengit+ssh://
. Insecure protocols (like http:// and git://) are not supported.As an example, consider
acme.com/quote
again. The loader sends a request to https://acme.com/quote?cue-get=1. The server responds with an HTML document containing the tag:From this response, the loader will use the Git repository at the remote URL https://github.com/acme.com/quote.
As the CUE modules implementation is based heavily on the Go modules implementation, if a server fails to respond with an appropriate
<meta>
tag using the query?cue-get=1
, the loader will then attempt to fallback via a query?go-get=1
. This means that any server that needs to distinguish the hosting information for Go and CUE modules can do so, whilst not placing an undue burden on existing infrastructure to add support for?cue-get=1
queries from day one (the assumption being that generally speaking hosting of CUE and Go modules will generally be aligned in terms of VCS systems). For example, GitHub and other popular hosting services respond to?go-get=1
queries for all repositories, so no server reconfiguration is necessary for CUE modules hosted at those sites. Over time it is envisaged that such code hosting sites would add support for?cue-get=1
queries.After the repository URL is found, the loader will clone the repository into the module cache. In general, the loader tries to avoid fetching unneeded data from a repository. However, the actual commands used vary by version control system and may change over time. For Git, the loader can list most available versions without downloading commits. It will usually fetch commits without downloading ancestor commits, but doing so is sometimes necessary.
Versions of modules
Much like Go, a version identifies an immutable snapshot of a module, which may be either a release or a pre-release. Each version starts with the letter
v
, followed by a semantic version. See the Go module reference for more detail, and Semantic Versioning 2.0.0 for details on how versions are formatted, interpreted, and compared.Therefore, as we have seen in
cue.mod/module.cue
examples above, a module path and version together form the basis of a declared dependency.Module authors explicitly release new versions by defining a semantic version tag within the repository that hosts the module, a tag that indicates which revision should be checked out for that version. For example, as the author of the
example.com/blah/v2
module, we would, in a local clone of the repository behindexample.com/blah/v2
, do something like:Another module looking to depend on
example.com/blah/v2
would then be able to run:and that would resolve to
v2.0.1
, specifically the revision 7c5d28e.CUE modules also adopt the concept of a pseudo-version. A pseudo-version is a specially formatted pre-release version that encodes information about a specific revision in a version control repository. For example,
v0.0.0-20191109021931-daa7c04131f5
is a pseudo-version. Pseudo versions are used when canonical semantic tagged versions are not available, for example the user wanting to depend on a recent fix pushed to themain
branch of a project. Pseudo-versions follow exactly the same model as implemented for Go modules: see the explanation of pseudo versions and details of how pseudo versions map to commits for more specific information.Declaring dependencies on other modules
The earlier example of the main module
example.com/blah/v2
showed a sketch of the schema ofcue.mod/module.cue
. This is now presented more fully:With the obvious exception that the representation is different (in CUE a module is defined in CUE itself, whereas in Go
go.mod
files have their own syntax), each of the directives supported ingo.mod
files have corresponding fields and meanings in acue.mod/module.cue
file:module
directivecue
directiverequire
directiveexclude
directivereplace
directiveretract
directiveBuilding on the short descriptions in the schema above, the links above cover in more detail what each means and when they should be used.
To support the
retract
directive we will provide a builtinsemver
package that allows for the specification of ranges:Similarly, the
cue.mod/sum.cue
file would have the following schema:Question: do we really need/want to have
cue.mod/sum.cue
in CUE format? Or would thego.sum
format suffice?The format of the
go.mod
file is described in the Go module reference documentation.Module proxy
As discussed above, as an interim measure we propose using proxy.golang.org as a module mirror, and the checksum database sum.golang.org for authentication, until such time as the CUE project can host such services itself. Use of these services will be enabled by default in the loader. The following CUE environment variables will control use of the proxy and checksum database (this follows almost identically from the Go module environment variables):
CUENOPROXY
Comma-separated list of glob patterns (in the syntax of Go's
path.Match
) of module path prefixes that should always be fetched directly from version control repositories, not from module proxies.If
CUENOPROXY
is not set, it defaults toCUEPRIVATE
.CUENOSUMDB
Comma-separated list of glob patterns (in the syntax of Go's
path.Match
) of module path prefixes for which the loader should not verify checksums using the checksum database.If
CUENOSUMDB
is not set, it defaults toCUEPRIVATE
.CUEPRIVATE
Comma-separated list of glob patterns (in the syntax of Go's
path.Match
) of module path prefixes that should be considered private.CUEPRIVATE
is a default value forCUENOPROXY
andCUENOSUMDB
.CUEPRIVATE
also determines whether a module is considered private forCUEVCS
(see below).CUEPROXY
List of module proxy URLs, separated by commas (
,
) or pipes (|
). When the loader looks up information about a module, it contacts each proxy in the list in sequence until it receives a successful response or a terminal error. A proxy may respond with a 404 (Not Found) or 410 (Gone) status to indicate the module is not available on that server.The loader's error fallback behaviour is determined by the separator characters between URLs. If a proxy URL is followed by a comma, the loader falls back to the next URL after a 404 or 410 error; all other errors are considered terminal. If the proxy URL is followed by a pipe, the loader falls back to the next source after any error, including non-HTTP errors like timeouts.
CUEPROXY
URLs may have the schemeshttps
orfile
. If a URL has no scheme,https
is assumed. A module cache may be used directly as a file proxy:Two keywords may be used in place of proxy URLs:
off
: disallows downloading modules from any source.direct
: download directly from version control repositories instead of using a module proxy.CUEPROXY
defaults tohttps://proxy.golang.org,direct
. Under that configuration, the loader first contacts the Go module mirror run by Google, then falls back to a direct connection if the mirror does not have the module. See https://proxy.golang.org/privacy for the mirror's privacy policy. TheCYEPRIVATE
andCUENOPROXY
environment variables may be set to prevent specific modules from being downloaded using proxies.CUESUMDB
Identifies the name of the checksum database to use and optionally its public key and URL. For example:
The loader knows the public key of
sum.golang.org
and also that the namesum.golang.google.cn
(available inside mainland China) connects to thesum.golang.org
database; use of any other database requires giving the public key explicitly. The URL defaults tohttps://
followed by the database name.CUESUMDB
defaults tosum.golang.org
, the Go checksum database run by Google. See https://sum.golang.org/privacy for the service's privacy policy.If
CUESUMDB
is set tooff
the checksum database is not consulted, and all unrecognised modules are accepted, at the cost of giving up the security guarantee of verified repeatable downloads for all modules. A better way to bypass the checksum database for specific modules is to use theCUEPRIVATE
orCUENOSUMDB
environment variables.Module versioning
(This follows directly from the Go module reference
Starting with major version 2, module paths must have a major version suffix like
/v2
that matches the major version. For example, if a module has the pathexample.com/mod
atv1.0.0
, it must have the pathexample.com/mod/v2
at versionv2.0.0
.Major version suffixes are not allowed at major versions
v0
orv1
. There is no need to change the module path betweenv0
andv1
becausev0
versions are unstable and have no compatibility guarantee. Additionally, for most modules,v1
is backwards compatible with the lastv0
version; av1
version acts as a commitment to compatibility, rather than an indication of incompatible changes compared withv0
.Minimal version selection
It is proposed that, like Go, minimal version selection (MVS) will be used as the algorithm to select a set of module versions to use when evaluating packages. MVS is described in detail in Minimal Version Selection by Russ Cox. The detail of the algorithm is not covered here.
MVS operates on a directed graph of modules, specified with go.mod files. Each vertex in the graph represents a module version. Each edge represents a minimum required version of a dependency, specified using a require directive. replace and exclude directives in the main module's go.mod file modify the graph.
Resolving a package to a module
When the loader loads a package using a package path, it needs to determine which module provides the package. CUE will follow exactly the same model for resolving a package to a module in this respect, with the obvious substitution replacing "the
go
command" with "the loader", andGO*
withCUE*
environment module-related environment variables. Whilst CUE does not have the equivalent ofGOOS
orGOARCH
, it will similarly ignore file level build constraints of the form@if
during this resolution.Changes to
cmd/cue
This section introduces changes that will be made to
cmd/cue
. Most of these changes are effectively a front-end to changes that will be made tocue/load
; those changes are discussed in the next section.All
cmd/cue
commands that load information about packages will become module-aware:cue cmd
cue def
cue eval
cue export
cue fix
cue fmt
cue get
cue import
cue trim
cue vet
The
--mod
flag is understood by module-aware commands and controls the resolution of packages in the following way (likecmd/go
):--mod=mod
tells the loader to ignore thecue.mod/pkg
vendor directory and to automatically updatecue.mod/module.cue
(andgo.mod
), for example, when an imported package is not provided by any known module.--mod=readonly
tells the loader to ignore thecue.mod/pkg
vendor directory and to report an error ifcue.mod/module.cue
(orgo.mod
) needs to be updated.--mod=vendor
tells the loader to use thecue.mod/pkg
vendor directory. In this mode, the loader will not use the network or the module cache.By default, if a
cue.mod/pkg
vendor directory is present at the module root, the loader acts as if--mod=vendor
were used. Otherwise, the loader acts as if--mod=readonly
were used.Module-aware commands will also understand
--modpath
as a means of specifying an alternative path at which acue.mod
directory can be found (and correspondingly readcue.mod/module.cue
and associated files from).Like
cmd/go
, the--modcacherw
flag instructs the loader to create new directories in the module cache with read-write permissions instead of making them read-only.We now move on to talk in more details about changes to
cmd/cue
commands. For users of thecuelang.org/go/...
APIs, programmatically creating and running command instances viacuelang.org/go/cmd/cue/cmd
will remain possible and will be fully module-aware.For any command that talks about modifying
cue.mod/module.cue
, it should be assumed an identical change will be made to the modulego.mod
file, unless specified otherwise. The same generally applies for any action of the loader that would modify any of these files.Much of the proposal regarding
cmd/cue
commands is, unsurprisingly, heavily based on thecmd/go
-equivalent commands.cue get
cue get
updates module dependencies in thecue.mod/module.cue
file for the main module.Unlike
cmd/go
we have no need to establish the-d
flag because there is no concept of building/installing the module/package we have just fetched. Nor do we have a need for-t
untilcue test
. Otherwise the command will behave likego get
:cue get
accepts a list of packages, package patterns, and module paths as arguments.cue get
updates the module that provides the package.all
or a path with a...
wildcard),cue get
expands the pattern to a set of packages, then updates the modules that provide the packagesacme.com/nopkg
has no package in its root directory),cue get
will update the modulecue get acme.com/quote@v1.1.0
. A version query suffix consists of an@
symbol followed by a version query, which may indicate a specific version (v1.1.0
), a version prefix (v1.1
), a branch or tag name (main
), a revision (1234abcd
), or one of the special querieslatest
,upgrade
,patch
, ornone
. If no version is given,cue get
uses the@upgrade
query.@none
. This is a special kind of downgrade. Modules that depend on the removed module will be downgraded or removed as needed. A module requirement may be removed even if one or more of its packages are imported by packages in the main module. In this case, the next build command may add a new module requirement.Once
cue get
has resolved its arguments to specific modules and versions,cue get
will add, change, or remove require directives in the main module'scue.mod/module.cue
file to ensure the modules remain at the desired versions in the future. Note that required versions incue.mod/module.cue
files are minimum versions and may be increased automatically as new dependencies are added. See Minimal version selection (MVS) for details on how versions are selected and conflicts are resolved by module-aware commands.cue get
then proceeds along the lines described in thego get
documentation.cue list
cue list
lists the named packages, one per line. The most commonly-used flags are-f
and-json
, which control the form of the output printed for each package. Other list flags, documented below, control more specific details.The default output shows the package import path:
The
--f
flag specifies an alternate format for the list, using the syntax of packagetext/template
. The default output is equivalent to--f '{{.ImportPath}}'
. The struct being passed to the template is:With error information defined as:
The
Module
struct type is defined as below forcue list -m
.cue list -m
cue list -m
lists information about CUE modules. The--m
flag lists information about modules and not packages.The
--json
flag prints JSON-encoded output according to the struct type:As an alternative to
--json
, the--f
flag specifies--u
adds information about available upgradesThe
--versions
flag causes list to set the module's Versions field to a list of all known versions of that module, ordered according to semantic versioning, lowest to highest.--retracted
flag instructs list to show retracted versions in the list printed with the -versions flag and to consider retracted versions when resolving version queriescue mod download
The
cue mod download
command downloads the named modules into the module cache. Arguments can be module paths or module patterns selecting dependencies of the main module or version queries of the formpath@version
. With no arguments, download applies to all dependencies of the main module.The loader will automatically download modules as needed during ordinary execution. The
cue mod download
command is useful mainly for pre-filling the module cache or for loading data to be served by a module proxy.By default,
download
writes nothing to standard output. It prints progress messages and errors to standard error.The
--json
flag causes download to print a sequence of JSON objects to standard output, describing each downloaded module (or failure), corresponding to this Go struct:The
--x
flag causes download to print the commands download executes to standard error.cue mod edit
Example:
The
cue mod edit
command provides a command-line interface for editing and formattingcue.mod/module.cue
files, for use primarily by tools and scripts.cue mod edit
reads only onecue.mod/module.cue
file; it does not look up information about other modules. By default,cue mod edit
reads and writes thecue.mod/module.cue
file of the main module, but a different target file can be specified after the editing flags. All changes tocue.mod/module.cue
files made viacue mod edit
will be reflected in a CUE module'sgo.mod
file.The editing flags specify a sequence of editing operations.
--module
flag changes the module's path (thecue.mod/modue.cue
file's module directive).--cue=version
flag sets the expected Go language version.--require=path@version
and--droprequire=path
flags add and drop a requirement on the given module path and version. Note that--require
overrides any existing requirements onpath
. These flags are mainly for tools that understand the module graph. Users should prefercue get path@version
orcue get path@none
, which make othergo.mod
adjustments as needed to satisfy constraints imposed by other modules--exclude=path@version
and--dropexclude=path@version
flags add and drop an exclusion for the given module path and version. Note that--exclude=path@version
is a no-op if that exclusion already exists.--replace=old[@v]=new[@v]
flag adds a replacement of the given module path and version pair. If the@v
inold@v
is omitted, a replacement without a version on the left side is added, which applies to all versions of the old module path. If the@v
innew@v
is omitted, the new path should be a local module root directory, not a module path. Note that--replace
overrides any redundant replacements forold[@v]
, so omitting@v
will drop replacements for specific versions.--dropreplace=old[@v]
flag drops a replacement of the given module path and version pair. If the@v
is provided, a replacement with the given version is dropped. An existing replacement without a version on the left side may still replace the module. If the@v
is omitted, a replacement without a version is dropped.--retract=version
and--dropretract=version
flags add and drop a retraction for the given version, which may be a single version (likev1.2.3
) or an interval (like[v1.1.0,v1.2.0]
). Note that the--retract
flag cannot add a rationale comment for theretract
directive. Rationale comments are recommended and may be shown bycue list -m -u
and other commands.The editing flags may be repeated. The changes are applied in the order given.
cue mod graph
The
cue mod graph
command prints the module requirement graph (with replacements applied) in text form. For example:Each vertex in the module graph represents a specific version of a module. Each edge in the graph represents a requirement on a minimum version of a dependency.
cue mod graph
prints the edges of the graph, one per line. Each line has two space-separated fields: a module version and one of its dependencies. Each module version is identified as a string of the formpath@version
. The main module has no@version
suffix, since it has no version.See Minimal version selection (MVS) for more information on how versions are chosen. See also
cue list -m
for printing selected versions andcue mod why
for understanding why a module is needed.cue mod init
The
cue mod init
command initialises and writes a newcue.mod/module.cue
file in the current directory, in effect creating a new module rooted at the current directory. Thecue.mod
directory must not already exist. Ago.mod
file will also be written to the current directory.Per the current module docs, the use of a module is optional, but required if one wants to import files. The module name is required if a package within the module needs to import another package within the main module.
cue mod tidy
cue mod tidy
ensures that thecue.mod/module.cue
file (and by extension thego.mod
file) matches the source code in the module. It adds any missing module requirements necessary to build the current module's packages and dependencies, and it removes requirements on modules that don't provide any relevant packages. It also adds any missing entries tocue.mod/sum.cue
and removes unnecessary entries.The
-e
flag causescue mod tidy
to attempt to proceed despite errors encountered while loading packages.The
-v
flag causescue mod tidy
to print information about removed modules to standard error.cue mod tidy
works by loading all of the packages in the main module and all of the packages they import, recursively.cue mod tidy
acts as if all build constraints are enabled, so it will consider@if
constrained files even if those source files wouldn't normally be evaluated.TODO: do we need the equivalent of the
ignore
build constraint exception?Like the
...
package pattern,cue mod tidy
will not consider packages in the main module in directories namedtestdata
or with names that start with.
or_
unless those packages are explicitly imported by other packages.Once
cue mod tidy
has loaded this set of packages, it ensures that each module that provides one or more packages either has arequire
directive in the main module'scue.mod/module.cue
file or is required by another required module.cue mod tidy
will add a requirement on the@latest
version on each missing module.cue mod tidy
will removerequire
directives for modules that don't provide any packages in the set described above.cue mod tidy
may also add or removeindirect
fields on#Require
directives. A#Require
directive withindirect: true
denotes a module that does not provide packages imported by packages in the main module. These requirements will be present if the module that imports packages in the indirect dependency has an incompletecue.mod/module.cue
file. They may also be present if the indirect dependency is required at a higher version than is implied by the module graph; this usually happens after running a command likecue get -u ./...
.cue mod vendor
The
cue mod vendor
command constructs a directory namedcue.mod/pkg
that contains copies of all packages needed to support evaluations of packages in the main module. As withcue mod tidy
and other module commands, build constraints (except forignore
???) are not considered when constructing thecue.mod/pkg
vendor directory.When vendoring is enabled, the loader will load packages from the
cue.mod/pkg
vendor directory instead of downloading modules from their sources into the module cache and using packages those downloaded copies.cue mod vendor
also creates the filevendor/modules.txt
that contains a list of vendored packages and the module versions they were copied from. When vendoring is enabled, this manifest is used as a source of module version information. When thecue
command readsvendor/modules.txt
, it checks that the module versions are consistent withcue.mod/module.cue
(andgo.mod
). If eithercue.mod/module.cue
orgo.mod
changed sincevendor/modules.txt
was generated,cue mod vendor
should be run again.Note that
cue mod vendor
removes thecue.mod/pkg
vendor directory if it exists before re-constructing it. Local changes should not be made to vendored packages. Thecue
command does not check that packages in thecue.mod/pkg
vendor directory have not been modified, but one can verify the integrity of thecue.mod/pkg
vendor directory by runningcue mod vendor
and checking that no changes were made.The
--e
flag causescue mod vendor
to attempt to proceed despite errors encountered while loading packages.The
--v
flag causescue mod vendor
to print the names of vendored modules and packages to standard error.cue mod verify
cue mod verify
checks that dependencies of the main module stored in the module cache have not been modified since they were downloaded. To perform this check,cue mod verify
hashes each downloaded module.zip
file and extracted directory, then compares those hashes with a hash recorded when the module was first downloaded.cue mod verify
checks each module in the evaluation list (which may be printed withcue list -m all
.If all the modules are unmodified,
cue mod verify
prints "all modules verified". Otherwise, it reports which modules have been changed and exits with a non-zero status.Note that all module-aware commands verify that hashes in the main module's
cue.mod/sum.cue
file match hashes recorded for modules downloaded into the module cache. If a hash is missing fromcue.mod/sum.cue
(for example, because the module is being used for the first time), the loader verifies its hash using the checksum database (unless the module path is matched byCUEPRIVATE
orCUENOSUMDB
).In contrast,
cue mod verify
checks that module.zip
files and their extracted directories have hashes that match hashes recorded in the module cache when they were first downloaded. This is useful for detecting changes to files in the module cache after a module has been downloaded and verified.cue mod verify
does not download content for modules not in the cache, and it does not usecue.mod/sum.cue
files to verify module content. However,cue mod verify
may downloadgo.mod
files in order to perform minimal version selection. It will usecue.mod/sum.cue
to verify those files, and it may addcue.mod/sum.cue
entries for missing hashes.cue mod why
cue mod why
shows a shortest path in the import graph from the main module to each of the listed packages.The output is a sequence of stanzas, one for each package or module named on the command line, separated by blank lines. Each stanza begins with a comment line starting with
#
giving the target package or module. Subsequent lines give a path through the import graph, one package per line. If the package or module is not referenced from the main module, the stanza will display a single parenthesised note indicating that fact.The
--m
flag causescue mod why
to treat its arguments as a list of modules.cue mod why
will print a path to any package in each of the modules. Note that even when--m
is used,cue mod why
queries the package graph, not the module graph printed bycue mod graph
.By default,
cue mod why
considers the graph of packages matched by theall
pattern, which is the same set of packages matched bygo mod vendor
.cue clean -modcache
The
--modcache
flag causescue clean
to remove the entire module cache, including unpacked source code of versioned dependencies.This is usually the best way to remove the module cache. By default, most files and directories in the module cache are read-only to prevent tests and editors from unintentionally changing files after they've been authenticated. Unfortunately, this causes commands like
rm -r
to fail, since files can't be removed without first making their parent directories writable.The
--modcacherw
flag (accepted by module-aware commands) causes new directories in the module cache to be writable. To pass--modcacherw
to all module-aware commands, add it to theGOFLAGS
variable.GOFLAGS
may be set in the environment or withcue env -w
.--modcacherw
should be used with caution; developers should be careful not to make changes to files in the module cache.cue mod verify
may be used to check that files in the cache match hashes in the main module'scue.mod/sum.cue
file.cue env
This is covered in the "Proposal" section above.
Required changes to
cue/load
The main changes required in
cue/load
are additions to thecue/load.Config
type. These are backwards compatible assuming that users of this type are, as advised bygo vet
, using keyed struct literals. All field additions below have corresponding "front end" flags/environment variables incmd/cue
.As is the case today,
cmd/cue
will usecue/load
to load CUE instances, defaulting the values of modules-relatedcue/load.Config
values from flags and environment variables (see "Environment variables").For users of
cue/load
who want to mimic the behaviour ofcmd/cue
, a utility function that sets the modules-related fields ofcue/load.Config
tocmd/cue
defaults (according to the values of environment variables and defaults) will be provided.Version queries
We re-use the same concept of version queries as Go: https://golang.org/ref/mod#version-queries
A version query may be one of the following:
v1.2.3
, which selects a specific version. See Versions for syntax.v1
orv1.2
, which selects the highest available version with that prefix.<v1.2.3
or>=v1.5.6
, which selects the nearest available version to the comparison target (the lowest version for>
and>=
, and the highest version for<
and<=
).v2
selects the latest version starting withv2
, not the branch namedv2
.latest
, which selects the highest available release version. If there are no release versions,latest
selects the highest pre-release version. If there no tagged versions,latest
selects a pseudo-version for the commit at the tip of the repository's default branch.upgrade
, which is likelatest
except that if the module is currently required at a higher version than the versionlatest
would select (for example, a pre-release),upgrade
will select the current version.patch
, which selects the latest available version with the same major and minor version numbers as the currently required version. If no version is currently required,patch
is equivalent tolatest
. Since Go 1.16,go get
requires a current version when usingpatch
(but the-u=patch
flag does not have this requirement).Release versions are preferred over pre-release versions. For example, if versions v1.2.2 and v1.2.3-pre are available, the latest query will select v1.2.2, even though v1.2.3-pre is higher. The <v1.2.4 query would also select v1.2.2, even though v1.2.3-pre is closer to v1.2.4. If no release or pre-release version is available, the latest, upgrade, and patch queries will select a pseudo-version for the commit at the tip of the repository's default branch. Other queries will report an error.
cmd/cue
outside of a module contextLike today,
cmd/cue
will continue operate outside of a module context, and only fail if its arguments require resolution of non-builtins.GOPROXY
protocolCUE module proxies will implement the
GOPROXY
protocol. Anyone looking to provide a CUE module proxy alternative to proxy.golang.org should consult theGOPROXY
reference.Version control systems
The loader may download module source code and metadata directly from a version control repository. Downloading a module from a proxy is usually faster, but connecting directly to a repository is necessary if a proxy is not available or if a module's repository is not accessible to a proxy (frequently true for private repositories). Git, Subversion, Mercurial, Bazaar, and Fossil are supported. A version control tool must be installed in a directory in
PATH
in order for the loader to use it.To download specific modules from source repositories instead of a proxy, set the
CUEPRIVATE
orCUENOPROXY
environment variables (or equivalent options incue/load.Config
). To configure the loader to download all modules directly from source repositories, setCUEPROXY
todirect
. See Environment variables for more information.See https://golang.org/ref/mod#vcs for more details on the specifics of:
go-import
<meta>
tagsControlling version control tools with
CUEVCS
The loader's ability to download modules with version control commands like
git
is critical to the decentralized package ecosystem, in which code can be imported from any server. It is also a potential security problem if a malicious server finds a way to cause the invoked version control command to run unintended code.The CUE module implementation will follow the same model of
GOVCS
to change the allowed version control systems for specific modules, via the variableCUEVCS
. For example:With this setting, code with a module or import path beginning with
github.com/
can only usegit
; paths onevil.com
cannot use any version control command, and all other paths (*
matches everything) can use onlygit
orhg
.See the
GOVCS
reference documentation for more details.Module zip files
Like Go, CUE module versions are distributed as
.zip
files. There is rarely any need to interact directly with these files, since the loader creates, downloads, and extracts them automatically from module proxies and version control repositories. However, it's still useful to know about these files to understand cross-platform compatibility constraints or when implementing a module proxy.The
cue mod download
command downloads zip files for one or more modules, then extracts those files into the module cache. Depending onCUEPROXY
and other environment variables, the loader may either download zip files from a proxy or clone source control repositories and create zip files from them. The--json
flag may be used to find the location of download zip files and their extracted contents in the module cache.CUE modules will be subject to the same constraints as Go modules with respect to file path and size constraints. See https://golang.org/ref/mod#zip-files for more details
Private modules
We establish the same support model for private modules as Go modules, with the following environment variables as substitutions:
CUEPROXY
— list of module proxy URLs. Thego
command will attempt to download modules from each server in sequence. The keyworddirect
instructs thego
command to download modules from version control repositories where they're developed instead of using a proxy.CUEPRIVATE
— list of glob patterns of module path prefixes that should be considered private. Acts as a default value forCUENOPROXY
andCUENOSUMDB
.CUENOPROXY
— list of glob patterns of module path prefixes that should not be downloaded from a proxy. Thego
command will download matching modules from version control repositories where they're developed, regardless ofCUEPROXY
.CUENOSUMDB
— list of glob patterns of module path prefixes that should not be checked using the public checksum database, sum.golang.org.CUEINSECURE
— list of glob patterns of module path prefixes that may be retrieved over HTTP and other insecure protocols.The Go module documentation provides a complete set of scenarios covering the various permutations of public/private modules:
It also provides a comprehensive explanation of how the loader (
cmd/go
in the case of the Go modules implementation) handles privacy concerns with respect to proxy requests. See the Go modules Privacy section for a comprehensive explanation.Modules cache
Like Go, CUE will establish and use a user module cache, a directory where the loader stores downloaded module files. The default location of the module cache is
$HOME/cue/modcache
. To use a different location, set theCUEMODCACHE
environment variable.The cache may be shared by multiple CUE projects developed on the same machine. The loader will use the same cache regardless of the location of the main module. Multiple instances of the loader may safely access the same module cache at the same time.
For more detail on the module cache implementation, see the Go module cache reference.
Authenticating modules
The approach and implementation of authenticating modules follows exactly from the
cmd/go
implementation, and uses the checksum databse sum.golang.org by default for public modules.When the loader downloads a module zip file or
go.mod
file (which is why this file is necessary for compatibility with the Go proxy and checksum models) into the module cache, it computes a cryptographic hash and compares it with a known value to verify the file hasn't changed since it was first downloaded. The loader reports a security error if a downloaded file does not have the correct hash.For
go.mod
files, the loader computes the hash from the file content. For module zip files, the loader computes the hash from the names and contents of files within the archive in a deterministic order. The hash is not affected by file order, compression, alignment, and other metadata. Seegolang.org/x/mod/sumdb/dirhash
for hash implementation details.The loader compares each hash with the corresponding entry in the main module's
cue.mod/sum.cue
file. If the hash is different from the hash incue.mod/sum.cue
, the loader reports a security error and deletes the downloaded file without adding it into the module cache.If the
cue.mod/sum.cue
file is not present, or if it doesn't contain a hash for the downloaded file, the loader may verify the hash using the checksum database, a global source of hashes for publicly available modules. Once the hash is verified, the loader adds it tocue.mod/sum.cue
and adds the downloaded file in the module cache. If a module is private (matched by theCUEPRIVATE
orCUENOSUMDB
environment variables) or if the checksum database is disabled (by settingCUESUMDB=off
), the loader accepts the hash and adds the file to the module cache without verifying it.The module cache is usually shared by all CUE projects on a system, and each module may have its own
cue.mod/sum.cue
file with potentially different hashes. To avoid the need to trust other modules, the loader verifies hashes using the main module'scue.mod/sum.cue
whenever it accesses a file in the module cache. Zip file hashes are expensive to compute, so the loader checks pre-computed hashes stored alongside zip files instead of re-hashing the files. Thecue mod verify
command may be used to check that zip files and extracted directories have not been modified since they were added to the module cache.The format of
cue.mod/sum.cue
files is described above, and follows directly from thego.sum
structure (albeit a different format). For details on thego.sum
for and checksum databases, see the corresponding sections in the Go modules reference.The
cue.mod
directoryCurrently (the CUE world prior to this proposal) the contents of the
cue.mod
directory have the following function/semantics:Given a non-main module import path
acme.com/quote
, the loader unifies the contents of the package valuescue.mod/{gen,pkg,usr}/acme.com/quote
.Under this proposal, the only change to these semantics is that
cue.mod/pkg
becomes the equivalent of Go modules'vendor
directory:If
cue.mod/pkg
exists, then the loader will expect to be able to load all non-main module imports from there - this also retains compatibility for the existing loading mechanism, i.e. when nogo.mod
file exists in the CUE module root. Given a non-main module import pathacme.com/quote
, the loader will unify the contents of the package valuescue.mod/{gen,pkg,usr}/acme.com/quote
as it does today.If
cue.mod/pkg
does not exist, then the loader will resolve and load package dependencies from the module cache. Given a non-main module import pathacme.com/quote
, the loader unifies the contents of the package valuescue.mod/{gen,usr}/acme.com/quote
with, for example,$(cue env CUEMODCACHE)/acme.com/quote@v1.1.0
.Environment variables
Explanation of the flags and environment variables (and config options in the case of
cue/load
) that control the loader's behaviour are covered elsewhere in this proposal. A full list is provided here for reference:--mod
- controls whether the loader can automatically updatecue.mod/module.cue
and associated files, or usecue.mod/pkg
--modpath
- an alternative path at which acue.mod
directory can be found (and correspondingly readcue.mod/module.cue
and associated files from)CUEMODCACHE
- the directory where the loader will store downloaded modules and related filesCUENOPROXY
- Comma-separated list of glob patterns (in the syntax of Go'spath.Match
) of module path prefixes that should always be fetched directly from version control repositories, not from module proxies. IfCUENOPROXY
is not set, it defaults toCUEPRIVATE
.CUENOSUMDB
- Comma-separated list of glob patterns (in the syntax of Go'spath.Match
) of module path prefixes for which the go should not verify checksums using the checksum database. IfCUENOSUMDB
is not set, it defaults toCUEPRIVATE
.CUEPRIVATE
- Comma-separated list of glob patterns (in the syntax of Go'spath.Match
of module path prefixes that should be considered private.CUEPRIVATE
is a default value forCUENOPROXY
andCUENOSUMDB
.CUEPRIVATE
also determines whether a module is considered private forCUEVCS
.CUEPROXY
- List of module proxy URLs, separated by commas (,
) or pipes (|
).CUESUMDB
- Identifies the name of the checksum database to use and optionally its public key and URLCUEVCS
- Controls the set of version control tools thego
command may use to download public and private modules (defined by whether their paths match a pattern inCUEPRIVATE
) or other modules matching a glob pattern.The mapping from these flags and environment variables to
cue/load.Config
options is covered in "Required changes tocue/load
".Go modules and CUE modules coexisting
It is not unreasonable to imagine a CUE module and Go modules co-existing at the same path, sharing the same VCS repository. Indeed, with the native support for exporting CUE to Go, and importing CUE from Go, it seems very likely that these situations will arise. This proposal supports such a setup.
The versioning of both modules would be intrinsically linked by virtue of each module system sharing the same tagging scheme in the same repository. However, assuming a Go module and CUE module exist at the same root, it would not be possible to version the two separately within the same repository. On the assumption that co-existence of CUE and Go code implies a strong relationship between the two, with breaking changes in one almost certainly corresponding to breaking changes in the other, we don't foresee this being a problem.
Versioning the two modules separately would still be possible, but only in separate VCS repositories. This would be achieved by having the
go-import
andcue-import
meta tags return different repository locations. See "Finding a module for a module path" for more information.Where a Go and CUE module coexist in the same repository, there would be some redundancy insofar as a CUE module would contain Go code, and vice versa. We don't foresee any specific problems beyond the limited inefficiency in CPU, memory and storage terms of this redundancy.
The post-
go.mod
futureAs indicated in the summary of this proposal, use of proxy.golang.org and sum.golang.org is intended as an interim measure until such time as the CUE project can host such services itself. The requirement for a
go.mod
file in a CUE module is tied to our use of those services.When the CUE project can host such services itself, we will need to develop a
CUEPROXY
protocol, similar to theGOPROXY
protocol, and hosting a service that speaks that protocol. We would then look to create a number of CUE releases wherecmd/cue
knows how to speak this protocol, at which point projects would be able to start the process of removinggo.mod
files from their CUE modules. The only challenge here being that a project would only be able to remove ago.mod
if it could be sure there are no consumers who rely on using acmd/cue
version that does not speak the new protocol. However, adopting something akin to the Go release policy would be sensible in this respect: we would only advisego.mod
files be removed when there are two major versions of CUE that support the newCUEPROXY
protocol.However, in the context of the previous section - "Go modules and CUE modules co-existing" - splitting out CUE dependencies from Go dependencies does appear to create an issue. Consider the following example, in a post-
go.mod
world, where CUE dependencies are not added to thego.mod
file, and ago.mod
file is not required as part of a CUE module:(we elide
go.sum
andcue.mod/sum.cue
files for simplicity).Notes:
go.mod
file exists because we have also declared a Go module; hence we have two main modules, one Go, one CUE, and both coincide (have the same module directory root)go.mod
world, thego.mod
file only reflects the dependencies of the Go module; the CUE dependencies are reflected incue.mod/module.cue
acme.com/quote
is both the path of a Go module and a CUE modulev1.2.0
in the source code repository that contains both creates versionsv1.2.0
of bothacme.com/quote
required by the Go module (v1.2.0
) is different that required by the CUE module (v1.1.0
)The problem is that the version of
acme.com/quote
resolved ingo.mod
is not guaranteed to be the same version ofacme.com/quote
resolved incue.mod/module.cue
. We essentially have two different instances of MVS running with different constraints.Much of the time this might be totally innocuous. But version skew like this will almost certainly lead to issues, for example in the case of additive changes to an API.
Nor is this problem unique to the combination of Go and CUE. It is envisaged that languages other than Go will be supported via
cue import
andcue export
. Each language will have its own (or indeed many) package versioning system equivalent. This scenario of a coincident Language X and CUE main module (or equivalent in Language X's terms) depending on coincident Language X and CUE modules is therefore more widespread: the version resolution algorithm of Language X is not guaranteed to arrive at the same revision as the CUE MVS algorithm.Notable details/exceptions
Here is a general list of points that don't naturally fit under any other heading:
gopkg.in
. However, such a position does not preclude doing so in the futureCUE & A
Why have you chosen to use {proxy,sum}.golang.org?
The Go module specification and implementation do not depend upon either proxy.golang.org or sum.golang.org. Indeed the
GOPROXY
and checksum protocols provide the necessary abstraction. Therefore the next phase of CUE module support does not need to be tied to either the module mirror at proxy.golang.org or checksum database at sum.golang.org. However, there are some significant advantages to doing so:cmd/go...
These seem to outweigh the disadvantages:
go.mod
file at the root of each CUE module (which will be done entirely automatically)The most crucial point however is that the default of using proxy.golang.org and sum.golang.org within the loader is just that: a default. It is entirely possible to turn off use of both via
CUEPROXY=off
andCUESUMDB=off
.What about
cue test
?As is covered elsewhere in this proposal, we have not concluded a design for
cue test
(this falls under #209). However, this proposal is entirely compatible with being extended to support_test.cue
files, again following the pattern of_test.go
files in Go.What about a
//go:embed
CUE equivalent?As of Go 1.16,
cmd/go
now supports including static files and file trees as part of the final executable, using the new//go:embed
directive. See the documentation for the newembed
package for details.We are working on a proposal for how to support the concept of embedding in CUE. Much like
cue test
, this proposal is entirely compatible with (and indeed depends upon) this proposal, the next phase of modules.What about a
gorelease
equivalent?gorelease
is an experimental tool that helps module authors avoid common problems before releasing a new version of a module.Examples:
gorelease
analyzes changes in the public API and dependencies of the main module. It compares a base version with the currently checked out revision. Given a proposed version to release,gorelease
reports whether the changes are consistent with semantic versioning.Given the very nature of the problems that CUE looks to solve, it will be entirely possible to provide such a command to help CUE module authors. Much like
gorelease
is intended to becomego release
, the equivalent in the CUE world would likely be spelledcue release
.Will CUE packages start to pollute pkg.go.dev?
Whilst we have permission to use proxy.golang.org and sum.golang.org from the Go team until such time as the CUE project starts to host instances of a module mirror and checksum database itself, we should look to limit any unintended side effects. One such side effect would that pkg.go.dev (a Go module and package documentation and discovery site) uses index.golang.org (an index which serves a feed of new module versions that become available at proxy.golang.org). CUE modules would therefore start to "leak" into pkg.go.dev results. We will work with the pkg.go.dev team to ensure that the relevant heuristics for determining CUE-only modules.
How does this proposal relate to
io/fs.FS
?Go 1.16 introduce a new
io/fs
package that defines thefs.FS
interface, an abstraction for read-only trees of files. This package largely exists to support the new//go:embed
feature, but does have other uses.#607 raises the question of how the existing
cue/load.Config.Overlay
field might be used to supply an entire file system as input tocue/load
. #607 (comment) clarifies that the currentOverlay
field exists to complement the the operating system file system, rather than replace it. As outlined in that comment, however, adding anio/fs.FS
field tocue/load.Config
would allow the intended semantics. This modules proposal is fully compatible with this proposal, but necessarily orthogonal to it.What about verifiable evaluations?
One of the main differences between Go and CUE from a "build" perspective is that Go has complete control over build artefacts. Specifically, binaries that represent the compilation result of a
main
package.cmd/go
includes sufficient module-related information (module path, version and checksum) in those binary artefacts so as to enable verifiable builds.runtime/debug.ReadBuildInfo()
gives runtime access to that information;go version -m /path/to/binary
allows it to be inspected. See Russ Cox's blog post for more information.CUE does not have such control over its many output formats (JSON, Yaml, JSONSchema etc). An immediate consideration here would be that comments be used to encode similar module-related information. However, JSON for one does not support comments.
The Required fields and related issues proposal includes a section on
cue export
, and how that would be repurposed to be the inverse ofcue import
. The example presented there is as follows.Given the CUE file:
cue export
would then produce the followingtxtar
output:One option therefore would be to make a step towards verifiable evaluations by including sufficient module-related information (like that included Go binaries) in the
txtar
output ofcue export
.Are there any alternatives to requiring a
go.mod
?If we want to utilise and leverage the existing module mirror and checksum database, we don't see a way around the requirement of declaring a
go.mod
file in the root of a CUE module. TheGOPROXY
protocol requires that a regulargo.mod
file (i.e. a symlink is not sufficient) denote the root of a Go module, and that that file declares the module's requirements, retractions etc.As noted above however, this is an interim measure until such time as the CUE project can host such services itself.
What about anonymous modules? Will they need a
go.mod
?Anonymous modules can be created today via:
This creates a
cue.mod/module.cue
file as follows:Anonymous modules are useful because as an end user, i.e. a situation where you know a package within the module will never be a dependency of another module, coming up with a module name is an annoying problem.
Go modules do not support anonymous modules: every module must have a path. Hence we simply could not maintain a parallel
go.mod
file, matching the requirements listed incue.mod/module.cue
.Therefore, for anonymous modules,
cmd/cue
andcue/load
will not create or maintain ago.mod
file.Why is the module cache in the user's home directory?
Russ Cox provided excellent motivation for this decision in a GitHub discussion about the
GOMODCACHE
environment variable:What is the timeline for CUE modules?
Once the CUE community has had a chance to consider and respond to this proposal, if there is broad agreement with the direction implementation would start immediately. As mentioned elsewhere in this proposal, we hope to reuse much of the
cmd/go/internal/...
implementation, as well as learning from and leveraging the vast experience of the Go team.Coming up with a rough timeline and priority ordered list of work will be the first thing we do when starting work on this next phase of CUE module support.
Are submodules and multi-module repositories supported?
Yes. Although the same advice regarding both will exist in the CUE world. As a starting point the following advice from Russ Cox will likely hold true for the vast majority:
For more details see https://github.com/golang/go/wiki/Modules#faqs--multi-module-repositories
What strategies exist for supporting multiple major versions of a module in parallel?
A corollary of the import compatibility rule:
is that any breaking changes in a module with major version number
>=1
must be accompanied by an increase in major version number. This raises the question of how to support users of the now old major version - is it possible to support both at the same time?Like with Go modules, developers will have two options when it comes to maintaining multiple major versions of a module in parallel:
See the Go modules wiki entry on the topic as well as a mention in the Go modules reference.
Should the CUE version tags be namespaced?
Some of the early discussions about
vgo
(the Go modules prototype) questioned whether Go should distinguish the VCS used tags used to indicated versions of Go modules. The thinking being thatv1.1.0
says nothing about the fact the tag corresponds to a version of a Go module. Alternatives included namespacing those tags, e.g.go:v1.1.0
.Link to those discussions
It is natural and appropriate to consider the same question in the context of designing CUE package versioning and modules.
However, if we choose to base our implementation on Go modules, using proxy.golang.org and sum.golang.org, then by definition we adopt the same approach to creating module versions: tagging with semantic versions, e.g.
v1.1.0
. Tags indicating Go versions and CUE versions will therefore be indistinguishable.But as we cover under "Go modules and CUE modules coexisting", we don't see a problem with this "conflict" - indeed it very much aligns with the intentions of the author.
What about CUE that coexists with non-Go module aware Go code?
The text was updated successfully, but these errors were encountered: