Index: app/doc/versioning.markdown |
diff --git a/app/doc/versioning.markdown b/app/doc/versioning.markdown |
deleted file mode 100644 |
index e845875d1a5db30fad849d0616bc972100543bb8..0000000000000000000000000000000000000000 |
--- a/app/doc/versioning.markdown |
+++ /dev/null |
@@ -1,379 +0,0 @@ |
---- |
-title: "Versioning Philosophy" |
---- |
- |
-1. [A name and a number](#a-name-and-a-number) |
-1. [Shared dependencies and unshared libraries](#shared-dependencies-and-unshared-libraries) |
-1. [Version lock](#version-lock) |
-1. [Version constraints](#version-constraints) |
-1. [Semantic versions](#semantic-versions) |
-1. [Constraint solving](#constraint-solving) |
-1. [Constraint context](#constraint-context) |
-1. [Lockfiles](#lockfiles) |
-1. [When things go wrong](#when-things-go-wrong) |
-1. [Summary](#summary) |
-{:.toc} |
- |
-One of pub's main jobs is helping you work with versioning. Here, I'll |
-explain a bit about the history of versioning and pub's approach to it. |
-Consider this to be advanced information. If you want a better picture of *why* |
-pub was designed the way it was, read on. If you just want to *use* pub, the |
-[other docs](index.html) will serve you better. |
- |
-Modern software development, especially web development, leans heavily on |
-reusing lots and lots of existing code. That includes code *you* wrote in the |
-past, but also stuff from third-parties, everything from big frameworks to tiny |
-little utility libraries. It's not uncommon for an application to depend on |
-dozens of different packages and libraries. |
- |
-It's hard to understate how awesome this is. When you see stories of tiny web |
-startups building a site in a few weeks that gets millions of users, the |
-only reason they can pull that off is because the open source community has |
-laid a feast of software at their feet. |
- |
-But there's still no such thing as a free lunch. There's a challenge to code |
-reuse, especially reusing code you don't maintain. When your app uses tons of |
-code being developed by other people, what happens when they change it? They |
-don't want to break your app, and you certainly don't either. |
- |
-## A name and a number |
- |
-We solve this by *versioning*. When you depend on some piece of outside code, |
-you don't just say "My app uses `widgets`." You say, "My app uses |
-`widgets 2.0.5`." That combination of name and version number uniquely |
-identifies an *immutable* chunk of code. The people hacking on `widgets` can |
-make all of the changes they want, but they promise to not touch any already |
-released versions. They can put out `2.0.6` or `3.0.0` and it won't affect you |
-one whit because the version you use is unchanged. |
- |
-When you *do* want to get those changes, you can always point your app to a |
-newer version of `widgets` and you don't have to coordinate with those |
-developers to do it. So, problem solved, right? |
- |
-## Shared dependencies and unshared libraries |
- |
-Well, no. Depending on specific versions works fine when your dependency *graph* |
-is really just a dependency *tree*. If your app depends on a bunch of stuff, and |
-those things in turn have their own dependencies and so on, that all works fine |
-as long as none of those dependencies *overlap*. |
- |
-But let's consider an example: |
- |
- myapp |
- / \ |
- / \ |
- widgets templates |
- \ / |
- \ / |
- collections |
- |
-So your app uses `widgets` and `templates`, and *both* of those use |
-`collections`. This is called a **shared dependency**. Now what happens when |
-`widgets` wants to use `collections 2.3.5` and `templates` wants |
-`collections 2.3.7`? What if they don't agree on a version? |
- |
-One option is to just let the app use both |
-versions of `collections`. It will have two copies of the library at different |
-versions and `widgets` and `templates` will each get the one they want. |
- |
-This is what [npm][] does for node.js. Would it work for Dart? Consider this |
-scenario: |
- |
- 1. `collections` defines some `Dictionary` class. |
- 2. `widgets` gets an instance of it from its copy of `collections` (`2.3.5`). |
- It then passes it up to `myapp`. |
- 3. `myapp` sends the dictionary over to `templates`. |
- 4. That in turn sends it down to *its* version of `collections` (`2.3.7`). |
- 5. The method that takes it has a `Dictionary` type annotation for that object. |
- |
-As far as Dart is concerned, `collections 2.3.5` and `collections 2.3.7` are |
-entirely unrelated libraries. If you take an instance of class `Dictionary` from |
-one and pass it to a method in the other, that's a completely different |
-`Dictionary` type. That means it will fail to match a `Dictionary` type |
-annotation in the receiving library. Oops. |
- |
-Because of this (and because of the headaches of trying to debug an app that |
-has multiple versions of things with the same name), we've decided npm's model |
-isn't a good fit. |
- |
-[npm]: https://npmjs.org/ |
- |
-## Version lock |
- |
-Instead, when you depend on a package, your app will only use a single copy of |
-that package. When you have a shared dependency, everything that depends on it |
-has to agree on which version to use. If they don't, you get an error. |
- |
-That doesn't actually solve your problem though. When you *do* get that error, |
-you need to be able to resolve it. So let's say you've gotten yourself into |
-that situation in the above example. You want to use `widgets` and `templates`, |
-but they are using different versions of `collections`. What do you do? |
- |
-The answer is to try to upgrade one of those. `templates` wants |
-`collections 2.3.7`. Is there a later version of `widgets` that you can upgrade |
-to that works with that version? |
- |
-In many cases, the answer will be "no". Look at it from the perspective of the |
-people developing `widgets`. They want to put out a new version with new changes |
-to *their* code, and they want as many people to be able to upgrade to it it as |
-possible. If they stick to their *current* version of `collections` then anyone |
-who is using the current version `widgets` will be able to drop in this new one |
-too. |
- |
-If they were to upgrade *their* dependency on `collections` then everyone who |
-upgrades `widgets` would have to as well, *whether they want to or not.* That's |
-painful, so you end up with a disincentive to upgrade dependencies. That's |
-called **version lock**: everyone wants to move their dependencies forward, but |
-no one can take the first step because it forces everyone else to as well. |
- |
-## Version constraints |
- |
-To solve version lock, we loosen the constraints that packages place on their |
-dependencies. If `widgets` and `templates` can both indicate a *range* of |
-versions for `collections` that they will work with, then that gives us enough |
-wiggle room to move our dependencies forward to newer versions. As long as there |
-is overlap in their ranges, we can still find a single version that makes them |
-both happy. |
- |
-This is the model that [bundler](http://gembundler.com/) follows, and is pub's |
-model too. When you add a dependency in your pubspec, you can specify a *range* |
-of versions that you can accept. If the pubspec for `widgets` looked like this: |
- |
-{% highlight yaml %} |
-dependencies: |
- collections: '>=2.3.5 <2.4.0' |
-{% endhighlight %} |
- |
-Then we could pick version `2.3.7` for `collections` and then both `widgets` |
-and `templates` have their constraints satisfied by a single concrete version. |
- |
-## Semantic versions |
- |
-When you add a dependency to your package, you'll sometimes want to specify a |
-range of versions to allow. How do you know what range to pick? You need to |
-forward compatible, so ideally the range encompasses future versions that |
-haven't been released yet. But how do you know your package is going to work |
-with some new version that doesn't even exist yet? |
- |
-To solve that, you need to agree on what a version number *means*. Imagine that |
-the developers of a package you depend on say, "If we make any backwards |
-incompatible change, then we promise to increment the major version number." |
-If you trust them, then if you know your package works with `2.5.7` of theirs, |
-you can rely on it working all the way up to `3.0.0`. So you can set your range |
-like: |
- |
-{% highlight yaml %} |
-dependencies: |
- collections: '>=2.3.5 <3.0.0' |
-{% endhighlight %} |
- |
-To make this work, then, we need to come up with that set of promises. |
-Fortunately, other smart people have done the work of figuring this all out and |
-named it [*semantic versioning*](http://semver.org/). |
- |
-That describes the format of a version number, and the exact API behavioral |
-differences when you increment to a later version number. Pub requires versions |
-to be formatted that way, and to play well with the pub community, your package |
-should follow the semantics it specifies. You should assume that the packages |
-you depend on also follow it. (And if you find out they don't, let their |
-authors know!) |
- |
-We've got almost all of the pieces we need to deal with versioning and API |
-evolution now. Let's see how they play together and what pub does. |
- |
-## Constraint solving |
- |
-When you define your package, you list its |
-[**immediate dependencies**](glossary.html#immediate-dependency)—the |
-packages it itself uses. For each one, you specify the range of versions it |
-allows. Each of those dependent packages may in turn have their own |
-dependencies (called |
-[**transitive dependencies**](glossary.html#transitive-dependency). Pub will |
-traverse these and build up the entire deep dependency graph for your app. |
- |
-For each package in the graph, pub looks at everything that depends on it. It |
-gathers together all of their version constraints and tries to simultaneously |
-solve them. (Basically, it intersects their ranges.) Then it looks at the |
-actual versions that have been released for that package and selects the best |
-(most recent) one that meets all of those constraints. |
- |
-For example, let's say our dependency graph contains `collections`, and three |
-packages depend on it. Their version constraints are: |
- |
- >=1.7.0 |
- >=1.4.0 <2.0.0 |
- <1.9.0 |
- |
-The developers of `collections` have released these versions of it: |
- |
- 1.7.0 |
- 1.7.1 |
- 1.8.0 |
- 1.8.1 |
- 1.8.2 |
- 1.9.0 |
- |
-The highest version number that fits in all of those ranges is `1.8.2`, so pub |
-picks that. That means your app *and every package your app uses* will all use |
-`collections 1.8.2`. |
- |
-## Constraint context |
- |
-The fact that selecting a package version takes into account *every* package |
-that depends on it has an important consequence: *the specific version that |
-will be selected for a package is a global property of the app using that |
-package.* |
- |
-I'll walk through an example so you can see what this means. Let's say we have |
-two apps. Here are their pubspecs: |
- |
-{% highlight yaml %} |
-name: my_app |
-dependencies: |
- widgets: |
-{% endhighlight %} |
- |
-{% highlight yaml %} |
-name: other_app |
-dependencies: |
- widgets: |
- collections: '<1.5.0' |
-{% endhighlight %} |
- |
-They both depend on `widgets`, whose pubspec is: |
- |
-{% highlight yaml %} |
-name: widgets |
-dependencies: |
- collections: '>=1.0.0 <2.0.0' |
-{% endhighlight %} |
- |
-The `other_app` package uses depends directly on `collections` itself. The |
-interesting part is that it happens to have a different version constraint on |
-it than `widgets` does. |
- |
-What this means is that you can't just look at the `widgets` package in |
-isolation to figure out what version of `collections` it will use. It depends |
-on the context. In `my_app`, `widgets` will be using `collections 1.9.9`. But |
-in `other_app`, `widgets` will get saddled with `collections 1.4.9` because of |
-the *other* constraint that `otherapp` places on it. |
- |
-This is why each app gets its own "packages" directory: The concrete version |
-selected for each package depends on the entire dependency graph of the |
-containing app. |
- |
-## Lockfiles |
- |
-So once pub has solved your app's version constraints, then what? The end |
-result is a complete list of every package that your app depends on either |
-directly or indirectly and the best version of that package that will work with |
-your app's constraints. |
- |
-Pub takes that and writes it out to a **lockfile** in your app's directory |
-called `pubspec.lock`. When pub builds the "packages" directory your app, it |
-uses the lockfile to know what versions of each package to pull in. (And if |
-you're curious to see what versions it selected, you can read the lockfile to |
-find out.) |
- |
-The next important thing pub does is it *stops touching the lockfile*. Once |
-you've got a lockfile for your app, pub won't mess with it until you tell it to. |
-This is important. It means you won't spontanteously start using new versions |
-of random packages in your app without intending to. Once your app is locked, |
-it stays locked until you manually tell it to update the lockfile. |
- |
-If your package is for an app, you take your lockfile *check that bad boy |
-into your source control system!* That way, everyone on your team will be using |
-the exact same versions of every dependency when they hack on your app. You'll |
-also use this when you deploy your app so you can ensure that your production |
-servers are using the exact same packages that you're developing with. |
- |
-## When things go wrong |
- |
-Of course, all of this presumes that your dependency graph is perfect and |
-flawless. Oh, to be so fortunate. Even with version ranges and pub's constraint |
-solving and semantic versioning, you can never be entirely spared from the |
-dangers of version hell. |
- |
-There are a couple of problems you can run into: |
- |
-### You can have disjoint constraints |
- |
-Lets say your app uses `widgets` and |
-`templates` and both use `collections`. But `widgets` asks for a version |
-of it between `1.0.0` and `2.0.0` and `templates` wants something |
-between `3.0.0` and `4.0.0`. Those ranges don't even overlap. There's no |
-possible version that would work. |
- |
-### You can have ranges that don't contain a released version |
- |
-Let's say after |
-putting all of the constraints on a shared dependency together, you're |
-left with the narrow range of `>=1.2.4 <1.2.6`. It's not an empty range. |
-If there was a version `1.2.4` of the dependency, you'd be golden. But maybe |
-they never released that and instead when straight from `1.2.3` to `1.3.0`. |
-You've got a range but nothing exists inside it. |
- |
-### You can have an unstable graph |
- |
-This is, by far, the hairiest part of |
-pub's version solving process. I've described the process as "build up the |
-dependency graph and then solve all of the constraints and pick versions". |
-But it doesn't actually work that way. How could you build up the *whole* |
-dependency graph before you've picked *any* versions? *The pubspecs |
-themselves are version-specific*. Different versions of the same package |
-may have different sets of dependencies. |
- |
-As you're selecting versions of packages, they are changing the shape of |
-the dependency graph itself. As the graph changes, that may change |
-constraints, which can cause you to select different versions, and then you |
-go right back around in a circle. |
- |
-Sometimes this process never settles down into a stable solution. Gaze into |
-the abyss: |
- |
-{% highlight yaml %} |
-name: my_app |
-version: 0.0.0 |
-dependencies: |
- yin: '>=1.0.0' |
-{% endhighlight %} |
- |
-{% highlight yaml %} |
-name: yin |
-version: 1.0.0 |
-dependencies: |
-{% endhighlight %} |
- |
-{% highlight yaml %} |
-name: yin |
-version: 2.0.0 |
-dependencies: |
- yang: '1.0.0' |
-{% endhighlight %} |
- |
-{% highlight yaml %} |
-name: yang |
-version: 1.0.0 |
-dependencies: |
- yin: '1.0.0' |
-{% endhighlight %} |
- |
-In all of these cases, there is no set of concrete versions that will work for |
-your app, and when this happens pub will report an error and tell you what's |
-going on. It definitely will not try to leave you in some weird state where you |
-think things can work but won't. |
- |
-## Summary |
- |
-Wow, that's a lot to get through. Here's the important bits: |
- |
- * Code reuse is great, but in order to let developers move quickly, packages |
- need to be able to evolve independently. |
- * Versioning is how you enable that. But depending on single concrete versions |
- is too precise and with shared dependencies leads to version lock. |
- * To cope with that, you depend on *ranges* of versions. Pub will then walk |
- your dependency graph and pick the best versions for you. If it can't, it |
- tells you. |
- * Once your app has a solid set of versions for its dependencies, that gets |
- pinned down in a *lockfile*. That ensures that every machine your app is |
- on is using the same versions of all of its dependencies. |