This is a breaking change to package-version-parser.
A PackageConstraint used to look like this:
```
{ name: String,
constraintString: String,
constraints: [{version: String|null,
type: String}]}
```
Now it looks like this:
```
{ name: String,
constraintString: String,
vConstraint: {
raw: String,
alternatives: [{versionString: String|null,
type: String}]}}
```
Where (vConstraint instanceof VersionConstraint) and
(vConstraint.raw === constraintString).
This achieves several desirable changes at once.
* `constraint.constraints` for the disjuncts in “1.0.0||2.0.0”
was confusing. `alternatives` is better.
* Having a class for VersionConstraint will come in handy because
we can add methods to it, and we can use it in the constraint
solver to represent the problem statement.
* The names “vConstraint” and “versionString” are a little verbose,
but there really shouldn’t be a lot of code that dives into this
structure, and I really wanted to avoid anyone ever writing:
`constraint.constraint.alternatives[0].version`, and then wondering
what sort of object that was (not a parsed PackageVersion! we could
parse eagerly but that might be slow).
Mostly I just made parseConstraint clearer and gave the return value a
prototype.
There are new unit tests, and the existing ones have been given much
better names.
PV.parseConstraint also now optionally takes two arguments, a package name and a version constraint. We can eventually propagate this feature to the tool and stop concatenating with “@” so much.
I’m referring to “foo@=1.0.0” as a PackageConstraint and “=1.0.0” as a version constraint. I’ll probably add a VersionConstraint class too.
It parses “=1.0.0” but not “=1.0.0 || 2.0.0”. It shouldn’t be a public thing (and is never used publicly).
There should be only two things: versions and constraints. A value that can legally be “=1.0.0” is not a version, it is a constraint (and yet we still call it versionString in a few places). A value that can be “=1.0.0” but not “1.0.0 || 2.0.0” is not a constraint; it is at best a rare creature called something like a “simple constraint.” We may expand the syntax of constraints again in the future, and we can’t have N things called a “constraint.”
It was only used in one place in resolver.js. If the semantics of constraints are that buildIDs are ignored, that should be implemented when interpreting a constraint, not parsing it. We don’t use buildIDs ourselves anymore, so it’s rather theoretical, but we’ve always stripped them out of constraint strings so it’s not even clear why we allow them at all — ie. why we are willing to parse “=1.0.0+foo” as a valid constraint and then throw away the “+foo”.
Bonus: One less place where we conflate two different options objects (utils.parseConstraint and PV.parseConstraint).
Despite the name “package-version-parser,” PVP didn’t really provide a way to parse a package version. With this change, if you want to get the minor version of “1.2.3_4”, for example, you can just write `PV.parse(“1.2.3_4”).minor`. This simplifies the internals of PVP as well.
When you parse a version, you now get an instance of PackageVersion with a documented set of fields.
Also, make PVP work on the client. When called directly from the tool, semver is loaded the same way as before. When PVP is used as a package, it uses a copy of semver v4.1.0 kept inside the package.
This change is intended to be 100% behavior-preserving.
Consider the following situation:
- app uses package P
- Every version of P contains `api.use('s', 'server')`
- Every version of S contains `api.use('c', 'client')`
- There's nothing else around using S or C
When we bundle our app, we will not end up putting any unibuilds from C
into the bundle. That's fine.
The previous version of the constraint solver understood this, and so C
wasn't even part of the constraint solver solution.
HOWEVER, even though C does not contribute any unibuilds to the app
bundle, we still need to compile C in order to compile S. That's because
our current implementation never compiles only part of an isopack, even
if only part of the isopack will be needed for the app.
The structured project initialization done via ProjectContext will thus
decide to not build or load C, which will make it fail to compile S when
it gets around to compiling the client unibuilds in S.
We could make the model more complex by making it possible to compile
only part of S.
Instead, we'll make the Meteor package model simpler. Constraints, as
far as the constraint solver is concerned, are now at a package level.
So in this case, "C" will actually be part of the project (will end up
in .meteor/versions, etc) even though it will continue to not provide
any part of any of the bundled client or server programs.
This means that nodes in the constraint solver's graph will now just be
package versions, not unibuild versions. That's already the language
that the constraint solver spoke in as far as its inputs, outputs, and
error messages were concerned!
An example of an app that couldn't be built on the isopack-cache branch
before this commit and can be now is
https://github.com/glasser/precise-constraints-example
(It can be built with 1.0, but only by compiling a version of `c` that
isn't part of .meteor/versions!)
Note that this also means that .meteor/versions expresses enough to
allow us to implement a simpler constraint check that doesn't need to do
the full tree walk of the constraint solver. Such a checker would be
implemented as:
- gather root dependencies and constraints (project constraints,
release constraints, etc)
- add the dependencies and constraints from all versions mentioned
in .meteor/versions
- see if the choices made in .meteor/versions satisfy these
dependencies and constraints
This algorithm did NOT work previously, because you couldn't just look
at the constraints coming from `s@0.0.0` and say "they're satifisfied"
because you had to know to "ignore" the constraint on c#web.browser
because s#web.browser is not part of the app, which is not a fact that
actually got recorded in .meteor/versions.
(This commit does not implement this simpler constraint check, though.)
This was an undocumented and entirely unused feature (only two dummy
packages on the package server have this set to a non-default value).
No attempt is being made to remove the field from existing isopacks or
catalog entries. To continue to support existing clients, the package
server has been modified to ignore any provided
earliestCompatibleVersion and instead always write the default ECV to
the catalog.
When publishing a core package, you’re allowed to omit version numbers in package.js. With this change, we determine the correct versions of all the dependent packages based on the current checkout, and we send them to troposphere (instead of core packages having “null” constraints on their dependencies).
We throw an error if you have any missing version constraints on your package dependencies and you are not using versionsFrom or publishing a core package from a checkout.
We’ve already fixed this (no constraints on core package deps) retroactively in troposphere.
It speeds up the constraint solver by orders of magnitude when publishing a package.
This allows us to later increase the pool for well-formed versions without requiring
moving the catalog's data.json into a new folder, which would require re-syncing
the entire catalog.
It's now back to returning 10000 for "1.0.0", as it was before
the recent changes to allow for wrapped package version numbers
and prerelease versions. Given that there are other constants
in the cost function used in the constraint solver, this gives
us much more confidence that we haven't fundamentally changed
the behavior of `meteor add` or `meteor update` in 0.9.3.
The semver library's compare functions already did the right thing,
but our `PackageVersion.versionMagnitude` did not. It now almost
correctly works for prerelease versions with a few caveats described
in a [* XXX!] comment.
While at it, add many tests which caught a separate bug due to
adding a number to a string for versions such as "1.2.3_50".
(That version would have been chosen over "5.0.0"!)
Drop the "at-least" constraint type entirely. It was not user-accessible
and was only used in the form ">=0.0.0" to represent a constraint with
no version constraint at all. This type of constraint is now called
"any-reasonable".
The definition of "any-reasonable" is:
- Any version that is not a pre-release (has no dash)
- Or a pre-release version that is explicitly mentioned in a TOP-LEVEL
constraint passed to the constraint solver
For example, constraints from .meteor/packages, constraints from the
release, and constraints from the command line of "meteor add" end up
being top-level.
Why only top-level-constrained pre-release versions, and not versions we
find explicitly desired by some other desired version while walking the
graph?
The constraint solver assumes that adding a constraint to the resolver
state can't make previously impossible choices now possible. If
pre-releases mentioned anywhere worked, then applying the constraints
"any reasonable" followed by "1.2.3-rc1" would result in "1.2.3-rc1"
ruled first impossible and then possible again. That's no good, so we
have to fix the meaning based on something at the start. (We could try
to apply our prerelease-avoidance tactics solely in the cost functions,
but then it becomes a much less strict rule.)
At the very least, this change should allow you to run meteor on a
preview branch like cordova-hcp without getting a conflict between the
prerelease package on the branch/release and the lack of an explicit
constraint in .meteor/packages on that package, because we are
reintepreting the .meteor/packages constraint as meaning "anything
reasonable" and the in-the-release version counts as reasonable.