[swift-evolution] [Review] SE-0145: Package Manager Version Pinning (Revised)

Alex Blewitt alblue at apple.com
Thu Nov 24 06:54:24 CST 2016

> On 20 Nov 2016, at 05:48, Anders Bertelrud via swift-evolution <swift-evolution at swift.org> wrote:
> 	* What is your evaluation of the proposal?

A way of building against a specific set of dependencies, rather than a variable set (or master), is needed in Swift. The current build process seems opinionated (but not mandated) to check these explicit dependencies into version control. If dealing with builds-from-source (as currently stands in Swift) then building from a moving target makes sense, and is likely the only sane default, but when binary dependencies are generated the moving target makes less sense.

At the moment, the proposal suggests having a secondary 'pins' file, which exists to allow explicit dependencies to be checked in to version control. This can be done at the moment, using a Version(1,2,3) or range Version(1,2,3)...Version(1.2.3) in the constraints.

When dealing with unreleased software, there is no explicit version - and thus the resolution can't apply. In effect, a version control hash takes place of an explicit version for the purposes of the dependency resolution. Being able to override the version resolved with a hash, without making changes to a checked-in file, seems like a good idea.

What the proposal doesn't bring forward clearly is the versioning of transitive dependencies, and whether or not those dependencies violate the requirement constraints. For example, if A depends on B, and B depends on C version 1.2.3+, can or should A pin C version 0.9.9? What if A depends on B and D, both of which depend on different versions of C that may be semantically equivalent (e.g. C version 1.2.3 and C version 1.4.5)? This will come up more often than the 'fail to build' approach outlined in the proposal.

It would be useful if the tool to update a version dependency could allow for dependencies which used to be present but which have been since removed could be cleaned up, optionally with a warning.

For pin files that are version controlled, having each pinned dependency on a separate line (at least) will permit sensible version control introspection. Any start-of-list parenthesis or commas should be designed in such a way that removing the first pinned dependency or last pinned dependency results in an SCM diff which just has a single line change (so, for example, trailing commas would be useful in any list structures).

Another useful mode would be to re-write the version ranges to increase the lower limit of the dependency in the Package.swift file. If code has been built and tested against a library version 1.2.3, then it may not be appropriate for that build to be built against anything lower. Increasing the lower bound to match (or having a command to do the same) would be a way of keeping track of the moving dependencies for the package itself.

The proposal also doesn't make clear to me what the dependencies are named for. The expectation is that developers will name the target for the repository name that is referring to. Should that short name be used as a dependency, or the URL? What if the target's URL has been replaced with another (say, because you're working on a local fork of an existing project's component?)

> 	* Is the problem being addressed significant enough to warrant a change to Swift?

Yes. At the moment, it is not possible to use swift build to rebuild an old version of a product, without manually cloning many repositories and adjusting their hashes to match. Having the tool handle these dependencies automatically will manage this for the developer, and allow a step back in time to find out what the dependencies were.

> 	* Does this proposal fit well with the feel and direction of Swift?

Yes, this proposal is a natural extension to the way in which swift build works and will ensure repeatable builds.

> 	* If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Many languages who have such a dependency management mechanism do so by putting the versions next to the dependency in the format, and the dependencies are updated in the top level when they are needed. For example:

Maven: https://maven.apache.org/pom.html#Dependencies <https://maven.apache.org/pom.html#Dependencies> 
Gradle: https://docs.gradle.org/current/userguide/artifact_dependencies_tutorial.html#sec:declaring_your_dependencies <https://docs.gradle.org/current/userguide/artifact_dependencies_tutorial.html#sec:declaring_your_dependencies> 
Ivy: https://ant.apache.org/ivy/history/latest-milestone/ivyfile/dependency.html <https://ant.apache.org/ivy/history/latest-milestone/ivyfile/dependency.html> 

Although these tools allow specifying ranges of versions, this has been effectively deprecated over time as they don't provide for repeatable builds. Instead, the exact version (in the form of major/minor/micro) is encoded in the dependency itself, which allows the build to be repeated subsequently. (Dependencies in this world are identified with a fixed/immutable version number.)

One advantage of having the versions in the main dependency file is that it allows a view over what the history of those dependencies are simply by using the version control log. There are also other tools that can update the versions to the latest (or latest major, latest minor etc.) which update the dependencies in place.

http://www.mojohaus.org/versions-maven-plugin/ <http://www.mojohaus.org/versions-maven-plugin/> 

What these tools don't have is the ability to record a non-released version of a dependency; for example, if you know a but is fixed in a commit abc123 there isn't a way of depending on that.

Finally, one maven encodes the dependency information in the generated artefact. This allows the JAR to be unzipped and the dependencies investigated. For example, the dependency for the slf4j artefact (in the POM) is stored within the JAR in the META-INF/maven/org.slf4j/slf4j-log4j12/pom.xml of the generated code, along with a pinned stamp of the dependency used in the pom.properties file:

http://search.maven.org/remotecontent?filepath=org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.pom <http://search.maven.org/remotecontent?filepath=org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.pom>
http://search.maven.org/remotecontent?filepath=org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar <http://search.maven.org/remotecontent?filepath=org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar>

The pinning here takes the form of a major/minor/micro version, rather than a commit hash, but is similar in effect. (Unlike the constraints in Swift's package manager, a version in Maven dependencies is an exact version, not a range).

> 	* How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I spent some time going through the proposal and am familiar with other dependency management systems which have gone through a different evolution.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161124/9a16f02d/attachment.html>

More information about the swift-evolution mailing list