2016-11-20 18:22:29 +01:00
|
|
|
# Mu Dependencies
|
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
This is a short note describing how MuPackage dependencies are managed and resolved. This design has been inspired by
|
|
|
|
many existing package managers, and is a mashup of the approaches taken in Go, NPM/Yarn, and Docker.
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
## Packages
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
The unit of dependency in Mu is a [MuPackage](mupack.md).
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
Each has a `Mu.yaml` (or `.json`) manifest, which lists, among other things, that package's own set of dependencies.
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
Each may also carry arbitrary assets, such as a `Dockerfile` and associated source code; serverless source code
|
|
|
|
representing lambdas and API implementations; and so on.
|
|
|
|
|
|
|
|
The dependency management system is opinionated about how directories are laid out, however most MetaMus will project
|
|
|
|
MuPackage dependencies into the native package management system using proxy packages that the MetaMu compilers
|
|
|
|
understand how to recognize. The details of how a language does this is outside of the scope of this document.
|
|
|
|
|
|
|
|
## References
|
|
|
|
|
|
|
|
Each package is referred to using a URL-like scheme, facilitating multiple package management distribution schemes.
|
|
|
|
For example, the URL `https://hub.mu.com/aws/ec2#^1.0.6` references the `aws/ec2` package on MuHub's built-in package
|
|
|
|
manager, and askes specifically for version `1.0.6` or higher using semantic versioning resolution.
|
|
|
|
|
|
|
|
Specifically, the reference has up to four parts: a protocol, base URL, name, and version:
|
|
|
|
|
|
|
|
PackName = [ Protocol ] [ BaseURL ] NamePart [ Version ]
|
|
|
|
Protocol = "http://" | "https://" | "ssh://" | ...
|
|
|
|
BaseURL = URL* . (URL | .)* "/"
|
|
|
|
URL = (* valid URL characters *)
|
|
|
|
NamePart = (Identifier "/")* Identifier
|
|
|
|
Version = "#" (* valid version numbers *)
|
|
|
|
|
|
|
|
Although there are four parts, three of them are optional, because because Mu uses these defaults:
|
Articulate how dependency versioning works
I've gone backwards and forwards on the design for dependency version
management. However, I think what's written in this commit represents
a pretty sane "sweet spot" between all available options.
In a nutshell, anytime reference to a stack type in your Mufile is a
full-blown StackRef; in other words, it has a protocol (e.g., "https://"),
a base URL (e.g., "hub.mu.com/"), a name (e.g., "aws/s3/bucket"), and a
version ("@^1.0.6"). Each reference carries all of these components.
For convenience, you may omit the components. In that case, Mu chooses
reasonable defaults:
* "https://" as the default protocol (or "git://"; this is TBD).
* "hub.mu.com/" as the default base URL.
* "@latest" as the default version number.
Note that a version can be "latest" to mean "tip", a specific SHA hash
to pin to a precise version, or a semantic version number/range.
I had originally shied away from specifying versions inline as you reference
stacks in your Mufile, and was leaning towards an explicit dependencies
section, however I was swayed for two reasons:
1. It's very common to only consume a given stack once in a file. Needing
to specify it in two places each time is verbose and violates DRY.
2. We have decided that each Mufile stands on its own and forms the unit
of distribution. I had previously thought we might move dependencies
out of Mufiles into a "package manager" specification. Lacking that,
there is even less reason to call them out in a dedicated section.
Now, managing all these embedded version numbers across multiple stacks in
a single workspace would have been annoying. (Many edits for a single
version bump.) Instead, I've added provisions for storing this in your
workspace.yaml file. The way it works is if any StackRef lacks a version
number, before defaulting to "@latest" we check the workspace.yaml file and,
if a default is found in there, we will use it. For example:
dependencies:
aws/s3/bucket: ^1.0.6
The provision for pinning an entire namespace is also preserved. E.g.:
dependencies:
aws/...: ^1.0.6
2016-11-22 22:22:29 +01:00
|
|
|
|
|
|
|
* `https://` is the default protocol.
|
|
|
|
* `hub.mu.com/` is the default base URL.
|
2017-01-02 01:02:18 +01:00
|
|
|
* `latest` is the default version number (a.k.a., "tip").
|
|
|
|
|
|
|
|
Although we're concerned with package references right now, we'll see soon that the same reference scheme is also used
|
|
|
|
to address elements exported from a package, like a module, class, function, or variable. For example, to reference the
|
|
|
|
`VM` class from a MuIL token, we might say `https://hub.mu.com/aws/ec2/VM#^1.0.6`. Most likely, we would leave
|
|
|
|
off the protocol, base URL, and version in the token, and leave it to the MuPackage to bind to a specific version.
|
|
|
|
|
|
|
|
The way these URLs are resolved to physical MuPackages is discussed later on.
|
|
|
|
|
|
|
|
## Versions
|
|
|
|
|
|
|
|
Each physical incarnation of a MuPackage can be tagged with one or more versions. How this tagging process happens is
|
|
|
|
left to the specific package provider. Each version can either be a semantic version number or arbitrary string tag.
|
|
|
|
|
|
|
|
For example, the Git provider allows dependency on a specific Git SHA hash. For example,
|
|
|
|
`https://github.com/mu/aws/ec2#1895753f53a63c055e7cae81ebe4ea5d5805584f` depends on a MuPackage published in a GitHub
|
|
|
|
repo at commit `1895753`. Alternatively, Git tags can be used to give MuPackages friendly names. So, for example,
|
|
|
|
`https://github.com/mu/aws/ec2#beta1` uses on the arbitrary tag `beta1`; the same scheme can be used to denote semantic
|
|
|
|
versions simply by using Git tags, for instance `https://github.com/mu/aws/ec2#^1.0.6`.
|
|
|
|
|
|
|
|
If the reference uses a semantic version range, the toolchain is given some "wiggle room" in how it resolves the
|
|
|
|
reference (in [the usual ways](https://yarnpkg.com/en/docs/dependency-versions)). If the reference uses a non-range
|
|
|
|
semantic version, Git commit hash, or Git tag, the reference is said to be "pinned" to a specific version.
|
Articulate how dependency versioning works
I've gone backwards and forwards on the design for dependency version
management. However, I think what's written in this commit represents
a pretty sane "sweet spot" between all available options.
In a nutshell, anytime reference to a stack type in your Mufile is a
full-blown StackRef; in other words, it has a protocol (e.g., "https://"),
a base URL (e.g., "hub.mu.com/"), a name (e.g., "aws/s3/bucket"), and a
version ("@^1.0.6"). Each reference carries all of these components.
For convenience, you may omit the components. In that case, Mu chooses
reasonable defaults:
* "https://" as the default protocol (or "git://"; this is TBD).
* "hub.mu.com/" as the default base URL.
* "@latest" as the default version number.
Note that a version can be "latest" to mean "tip", a specific SHA hash
to pin to a precise version, or a semantic version number/range.
I had originally shied away from specifying versions inline as you reference
stacks in your Mufile, and was leaning towards an explicit dependencies
section, however I was swayed for two reasons:
1. It's very common to only consume a given stack once in a file. Needing
to specify it in two places each time is verbose and violates DRY.
2. We have decided that each Mufile stands on its own and forms the unit
of distribution. I had previously thought we might move dependencies
out of Mufiles into a "package manager" specification. Lacking that,
there is even less reason to call them out in a dedicated section.
Now, managing all these embedded version numbers across multiple stacks in
a single workspace would have been annoying. (Many edits for a single
version bump.) Instead, I've added provisions for storing this in your
workspace.yaml file. The way it works is if any StackRef lacks a version
number, before defaulting to "@latest" we check the workspace.yaml file and,
if a default is found in there, we will use it. For example:
dependencies:
aws/s3/bucket: ^1.0.6
The provision for pinning an entire namespace is also preserved. E.g.:
dependencies:
aws/...: ^1.0.6
2016-11-22 22:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
A compiled MuPackage always contains the set of specific versions it was compiled against and can be optionally pinned
|
|
|
|
by the author. Alternatively, the semantic version ranges can be left in, for more flexibility in dealing with diamond
|
|
|
|
dependencies in consumer code, at the risk of behavioral changes that are discovered only on the client machine.
|
Articulate how dependency versioning works
I've gone backwards and forwards on the design for dependency version
management. However, I think what's written in this commit represents
a pretty sane "sweet spot" between all available options.
In a nutshell, anytime reference to a stack type in your Mufile is a
full-blown StackRef; in other words, it has a protocol (e.g., "https://"),
a base URL (e.g., "hub.mu.com/"), a name (e.g., "aws/s3/bucket"), and a
version ("@^1.0.6"). Each reference carries all of these components.
For convenience, you may omit the components. In that case, Mu chooses
reasonable defaults:
* "https://" as the default protocol (or "git://"; this is TBD).
* "hub.mu.com/" as the default base URL.
* "@latest" as the default version number.
Note that a version can be "latest" to mean "tip", a specific SHA hash
to pin to a precise version, or a semantic version number/range.
I had originally shied away from specifying versions inline as you reference
stacks in your Mufile, and was leaning towards an explicit dependencies
section, however I was swayed for two reasons:
1. It's very common to only consume a given stack once in a file. Needing
to specify it in two places each time is verbose and violates DRY.
2. We have decided that each Mufile stands on its own and forms the unit
of distribution. I had previously thought we might move dependencies
out of Mufiles into a "package manager" specification. Lacking that,
there is even less reason to call them out in a dedicated section.
Now, managing all these embedded version numbers across multiple stacks in
a single workspace would have been annoying. (Many edits for a single
version bump.) Instead, I've added provisions for storing this in your
workspace.yaml file. The way it works is if any StackRef lacks a version
number, before defaulting to "@latest" we check the workspace.yaml file and,
if a default is found in there, we will use it. For example:
dependencies:
aws/s3/bucket: ^1.0.6
The provision for pinning an entire namespace is also preserved. E.g.:
dependencies:
aws/...: ^1.0.6
2016-11-22 22:22:29 +01:00
|
|
|
|
2017-01-03 12:41:41 +01:00
|
|
|
The recommended practice is for libraries to leave more flexible semantic version ranges, while individual blueprints
|
|
|
|
are pinned to specific versions. This pinning is important to ensure that deployments are repeatable, and is encouraged
|
|
|
|
by the command line tools; in particular, generating a plan automatically first generates a lockfile.
|
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
## Package Resolution
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
Now let us see how references are resolved to physical MuPackages.
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
MuPackages may be found in multiple places, and, as in most dependency management schemes, some locations are preferred
|
|
|
|
over others. This is to ease the task of local development while also providing rigorous dependency management.
|
2016-11-20 18:22:29 +01:00
|
|
|
|
|
|
|
Roughly speaking, these locations are are searched, in order:
|
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
1. The current workspace, for intra-workspace but inter-package dependencies.
|
|
|
|
2. The current workspace's `.mu/packs/` directory.
|
|
|
|
3. The global Workspace's `.mu/packs/` directory.
|
|
|
|
4. The Mu runtime libraries: `$MUROOT/lib/packs/` (default `/usr/local/mu/lib/packs`).
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
In each location, Mu prefers a fully qualified hit if it exists -- containing both the base of the reference plus the
|
|
|
|
name -- however, it also accept name-only hits. This allows developers to organize their workspace without worrying
|
|
|
|
about where their MuPackages will be published. Most of the Mu tools, however, prefer fully qualified paths.
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
To be more precise, given a reference `r` and a workspace root `w`, we look in these locations, in order:
|
2016-11-22 18:28:25 +01:00
|
|
|
|
|
|
|
1. `w/base(r)/name(r)`
|
|
|
|
2. `w/name(r)`
|
2017-01-02 01:02:18 +01:00
|
|
|
3. `w/.mu/packs/base(r)/name(r)`
|
|
|
|
4. `w/.mu/packs/name(r)`
|
|
|
|
5. `~/.mu/packs/base(r)/name(r)`
|
|
|
|
6. `~/.mu/packs/name(r)`
|
|
|
|
7. `$MUROOT/bin/packs/base(r)/name(r)`
|
|
|
|
8. `$MUROOT/bin/packs/name(r)`
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
To illustrate this process, let us imagine we are looking up the package `https://hub.mu.com/aws/ec2`.
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
In the illustration, let us imagine we're the author of the package, and so it is in our workspace. We have things
|
|
|
|
organized so that it can be easily found, eliminating the need for us to frequently publish changes during development:
|
2016-11-20 18:22:29 +01:00
|
|
|
|
|
|
|
<Workspace>
|
|
|
|
| .mu/
|
|
|
|
| | workspace.yaml
|
|
|
|
| aws/
|
2017-01-02 01:02:18 +01:00
|
|
|
| | ec2/
|
|
|
|
| | | Mu.yaml
|
|
|
|
| | | ...other assets...
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
The `workspace.yaml` file may optionally specify a "namespace" property, as in:
|
2016-11-20 18:22:29 +01:00
|
|
|
|
|
|
|
namespace: aws
|
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
In this case, the following simpler directory structure would also do the trick:
|
2016-11-20 18:22:29 +01:00
|
|
|
|
|
|
|
<Workspace>
|
|
|
|
| .mu/
|
|
|
|
| | workspace.yaml
|
2017-01-02 01:02:18 +01:00
|
|
|
| ec2/
|
|
|
|
| | Mu.yaml
|
|
|
|
| | ...other assets...
|
|
|
|
|
|
|
|
It is possible to simplify this even further by specifying the namespace as `aws/ec2`, leading to:
|
|
|
|
|
|
|
|
<Workspace>
|
|
|
|
| .mu/
|
|
|
|
| | workspace.yaml
|
|
|
|
| Mu.yaml
|
|
|
|
| ...other assets...
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
Notice that we didn't have to mention the `hub.mu.com/` part in our workspace, although we can if we choose to.
|
2016-11-22 18:28:25 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
In the second illustration, let us imagine we have used `mu get` to download the dependency from a package manager:
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
$ mu get https://hub.mu.com/aws/ec2
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
In this case, our local workspace's package directory will have been populated with a copy of `aws/ec2`:
|
2016-11-20 18:22:29 +01:00
|
|
|
|
|
|
|
<Workspace>
|
|
|
|
| .mu/
|
2017-01-02 01:02:18 +01:00
|
|
|
| | packs/
|
2016-11-20 18:22:29 +01:00
|
|
|
| | | hub.mu.com/
|
|
|
|
| | | | aws/
|
2017-01-02 01:02:18 +01:00
|
|
|
| | | | | ec2/
|
|
|
|
| | | | | | Mu.yaml
|
|
|
|
| | | | | | ...other assets...
|
|
|
|
|
|
|
|
Notice that in this case, the full base part `hub.mu.com/` is part of the path, since we downloaded it from that URL.
|
|
|
|
|
|
|
|
Now in the third and final illustration, let us imagine that we've installed a global copy of the package. This might
|
|
|
|
have been thanks to use using `mu get`'s `--global` (or `-g`) flag:
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
$ mu get --global https://hub.mu.com/aws/ec2
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
The directory structure will look identical to the second example, except that it is rooted in `~/`:
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
~
|
|
|
|
| .mu/
|
|
|
|
| | packs/
|
|
|
|
| | | hub.mu.com/
|
|
|
|
| | | | aws/
|
|
|
|
| | | | | ec2/
|
|
|
|
| | | | | | Mu.yaml
|
|
|
|
| | | | | | ...other assets...
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
## Fetching
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
TODO(joe): describe package fetching protocols.
|
2016-11-20 18:22:29 +01:00
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
TODO(joe): on-demand compilation (for easier Git fetching).
|
2016-11-20 18:22:29 +01:00
|
|
|
|
|
|
|
TODO(joe): describe how semantic versioning resolution works.
|
|
|
|
|
2017-01-02 01:02:18 +01:00
|
|
|
TODO(joe): describe how all of this interacts with Git repos (locally; e.g., git pull).
|
2016-11-20 18:22:29 +01:00
|
|
|
|