Pieter,
Thanks for taking the time to comment in great detail. This is good fodder for discussion ...
First some general comments and then I will embed specific comments/replies to your post.
I'd say that with Metacello, I've tried to supply some fairly general mechanisms that make it possible for developers to do what they need to do to specify the dependency structure of their project.
On the loading side, I'd say that Metacello provides a fair amount of flexibility for developers until it comes to dependency conflict resolution ... In the currently released version of Metacello (1.0-beta.31.1.6), Metacello always favors the latest version of a project: Metacello will automatically upgrade a loaded project, but it will never automatically downgrade a loaded project.
With the new Metacello Scripting API, the onConflict:, onUpgrade: and onDowngrade: family of messages provide a measure of load-time control over conflict management and I've eliminated the "automatic upgrade" policy ... Now the full control lies in the hands of the developer doing the load. Again, I am trying to provide some fairly general mechanisms.
I don't want Metacello to be in the enforcement business ... Metacello is intended to be used for cross-platform development and it is unreasonable to assume that the same development conventions will be followed on all platforms even though they may be sharing the same projects.
Just because Metacello isn't in the enforcement business doesn't mean that I don't think enforcement is necessary, I just think that enforcement should take place at a different level: tools or community.
I expect communities to evolve in terms of the conventions being followed and I don't want Metacello to be an impediment to such evolution ... it's why I try to use general mechanisms and balk at building conventions into Metacello itself. Symbolic versions has simply added a tag mechanism to Metacello. I chose to have an explicit tag (using Symbols) rather than implement a lookup order version strings. The use of #stable is a community convention and that convention can be changed without breaking Metacello.
With the Metacello Scripting API release, developers of individual projects will be able to choose whether or not they want to use Semantic Versioning. If (when) I add version range support to Metacello, then developers that depend upon a project that claims to adhere to Semantic Versioning will be able to leverage that and specify dependencies using version ranges ...
If a community decides to adopt Semantic Versioning (as the Ruby community has with their use of ruby-gems) then it is presumed that all projects will use Semantic Versioning, but if an odd project comes along that does not happen to use Semantic Versioning, at least Metacello won't prevent a development from specifying a dependency upon the project.
Further commentary embedded below...
Dale
----- Original Message -----
| From: "Pieter Nagel" <
pie...@nagel.co.za>
| To:
meta...@googlegroups.com
| Sent: Tuesday, April 23, 2013 1:04:05 PM
| Subject: Re: [Metacello] the future of #stable in Metacello
|
| I believe that if the Metacello world were to migrate to using Semantic
| Versioning, a lot of things would vastly improve.
I agree. Semantic Versioning is aimed at addressing the real world problem of "dependency hell" and to quote from the spec[1]:
In systems with many dependencies, releasing new package versions can
quickly become a nightmare. If the dependency specifications are too
tight, you are in danger of version lock (the inability to upgrade a
package without having to release new versions of every dependent
package). If dependencies are specified too loosely, you will
inevitably be bitten by version promiscuity (assuming compatibility
with more future versions than is reasonable). Dependency hell is
where you are when version lock and/or version promiscuity prevent
you from easily and safely moving your project forward.
Semantic Versioning is not necessarily the only solution, but adhering to any sort of convention for version naming would vastly improve things at this point:)
[1]
http://semver.org/
|
| I've been engaged at a client who uses both GemStone and Pharo to
| upgrade their dependencies on third party systems such as Seaside,
| Grease, Magritte, and so on, and I must say, down in the trenches, the
| state of the art in Smalltalk packaging seems to be "dependency hell".
|
| The norm seems to be for, say, Foo to depend on Bar-UI-xyz.101.mcz and
| Baz-Core-abc.102.mcz, while Baz-Core in turn depends on
| Bar-UI-xyz.102.mcz.
If you are using Metacello for this then I assume that you are referring to the fact that you have two external projects A and B that you depend upon and they both depend upon different versions of the same project C and one version of C uses Bar-UI-xyz.101.mcz and the other version of C uses Bar-UI-xyz.102.mcz.
Not a big deal, but Metacello only allows project dependencies to be specified upon versions of projects and not on versions of packages ... (just for clarity)
|
| Metacello will, heroically, apply some heuristics to try and get the
| resuling system loaded by for example first loading Bar-UI-xyz.101.mcz
| and then later loading Bar-UI-xyz.102.mcz as needed.
|
| And everytime I see situations like that I cross my fingers and think:
| "sheesh, I hope there were no backwards-incompatible changes between
| Bar-UI-xyz.101.mcz and Bar-UI-xyz.102.mcz, because if any slip through
| testing, the client is going to blame me".
But Semantic Versioning does not "solve" this problem ... it just changes your worry to "sheesh, I hope these guys were disciplined and didn't slip in an API change without bumping the major version that slips through my testing..." or to "sheesh, I hope the developers of project A and B didn't use a bit of private API that has been changed in an incompatible way in Bar-UI-xyz.102.mcz that slips through my testing...."
With that said, if someone claims to follow Semantic Versioning or another published version naming scheme, you can at least make the assumption you will be safer.
|
| But, frankly, this is wrong. Metacello's role should be to take
| packagers at their word and very strictly apply what they say. If the
| packagers "lie" and give a stricter dependency than what they intend to,
| my feeling is: tough cookies - let them learn to specify their
| dependencies correctly.
I basically agree with this sentiment ... as I implied in my general comments above, I really don't like the "automatic upgrade" feature and will remove it when the Metacello Scripting API is released.... Okay, "REMOVE" is too strong a word. When using the old-style load statements like:
(ConfigurationOfMyProject version: '1.0.0') load
Metacello will continue to do "automatic upgrades", but if one uses the style:
Metacello new
configuration: 'MyProject';
version: '1.0.0';
repository: '...';
load.
A load conflict error will be signalled when it is discovered that a different version of project C is required during the load.
When a load conflict occurs you can complain the developers of projects A and B and C and ask them to get their acts together and then patiently wait while some action is taken. But, while you wait for them to resolve the issue, you can continue with your own _development_ and use one the onUpgrade:, onDowngrade: or onConflict: family of messages to pick your own poison (deny or allow the upgrade/downgrade/conflict).
You may not want to ship under these conditions, but development is a different issue.
|
| And the only correct way to specify those dependencies would be for Foo
| to say "I depend on Bar-UI >= 10.3.1 and < 11", and for Baz to say "I
| depend on Bar-UI >= 10.3.2 < 11". That way, Monticello at least has a
| hope to determine that the entire collection of packages will indeed
| have their dependencies satisfied by Bar-UI 10.3.2, so I'll load that,
| and only that, once.
|
| I would like a world where:
|
| 1. Semantic versioning, in the sense of depending on range of versions
| that are intended to be compatible, becomes the norm, the easiest to
| express in Metacello's API, and the only thing one sees on "how to use
| Metacello" docs.
As I've stated in my comments above, I don't want Metacello to dictate a particular versioning scheme ... Metacello will provide the mechanism, but the community must supply the enforcement.
|
| 2. Depending on specific versions such as Bar-UI-xyz.101.mcz be
| absolutely disallowed by Metacello - with the only exception that
| ConfigurationOfBar is allowed to tie a specific version of the Bar
| project to specific mcz.that provide, say, Bar-UI and Bar-Core and the
| like.
Metacello already doesn't allow dependencies to be specified at the monticello file level, one can specify a dependency on a package name, but not on a specific version of a package. I think the problem that you are seeing is because the conflict lies at the project level and that Metacello does the "automatic upgrade" trick.
|
| 3. Before Metacello loads a set of dependencies, it walks the transitive
| closure of all dependencies and just flat our refuse to load in cases
| where one package needs Bar version X and another needs Bar version Y.
| This will place pressure on the authors of the respective
| ConfigurationOf... classes, to write their packages with sane
| dependencies on a range of versions . If you wrongly say that your
| package only works with Bar version 10.1.2 instead of saying it works
| with Bar version >= 10.1.2 and < 11, well, then that's your fault, not
| Metacello's fault.
With the Scripting API (`Metacello new....`) this will be the standard behavior.
|
| 4. Metacello itself seems to contain a lot of complexity that seems to
| be designed as a workaround for the fact that the community does not use
| semantic versioning. The new scripting API, onUpgrade: hooks and the
| like seem to be at least partially motivated by a desire for the user to
| be able to say "You know, that package over there wants A-12345.mcz, but
| I happen to hope that A-12346.mcz should work too, so let me add this
| hook to override what the packagers said". I think the desires and
| use-cases for feature like these would vastly reduce if, instead, the
| upstream packagers would just do sane, semantic, versioning!
The onUpgrade:, onDowngrade, and onConflict: methods were actually added to address the types of problems encountered when one decides to "fork a project" to fix bugs, add new features, etc. When one forks a project one must be able to declare "use my version of project X located over here" and that declaration has to be honored by Metacello when loading any other projects that may depend upon project X.
And just to reiterate, Metacello does not give developers control over package versions ... all of the decisions (like onUpgrade: and friends) are made relative to project versions, so you are making a decision between using version 3.1 and 3.2, not A-12345.mcz versus A-12346.mcz.
Like I said, I prefer to create general mechanisms that can be used to address a class of problems, rather than narrow features than knock off one problem at a time ... The fact that these general features can be used for evil as well as good is a side effect.
What's important to me about this is that the decision to resolve the conflicts is made by the load expression and not by changing a configuration somewhere so the fact that conflicts exist is very visible ...
Presumably the community can act to enforce their chosen conventions if there are too many published load scripts that are forced to use onUpgrade: and friends...
|
| 5. The actual version number of a package one is using and has loaded is
| "in your face:. I've spent the past few months downloading
| foo-dkh.651.mcz files and whatnot, but it is very rare when one can
| easily determine "oh, what i've got hold of here is version 3.0.4 of the
| foo project". My impresssion is that a lot of the projects don't really
| care what their version numbers are; and if they don't care, how can the
| users take care to know what version ranges they depend on?
I agree that a lot of the version numbers used by projects are pretty meaningless, I agree that a documented version numbering scheme is required and I agree that Semantic Versioning is a good example of a version numbering scheme goes a long way towards addressing the problem of "dependency hell" .... If the spec is adhered to...
... I am curious how Semantic Versioning works in practice, especially in the ruby community ... Do the Semantic Versioning police swoop down on a project and punish developers for changing the API without bumping the major version number or are developers more in the habit of simply forking in self defense those projects that can't be trusted to adhere to the Semantic Versioning spec or does everyone fanatically follow the Semantic Versioning spec?
Dale