Metacello is a configuration management system that allows us to specify dependencies in Smalltalk projects systems. Like any similar system, the dependencies are defined at two levels:
Having a configuration makes many things cheaper. Here are two: you can easily automate the image creation and blend it in a continuous integration job; and you can have nesting of projects. The second point is particularly interesting. For example, in the case of Moose, we have 32 nested projects.
One way to work with Metacello is to define your baseline, and for each commit to update the current development version. This can work well in small projects, but as soon as you go into larger projects with distributed teams that work on different schedules, it can be less appropriate. The greatest challenge comes from synchronization needs when versions are too fine grained: Because there is no merging support of configuration specifications, if the configuration changes too often (due to versions update), it can become a pain to handle the modifications. This can be solved with appropriate tooling, but at present time, it poses a significant problem in large projects.
In the case of Moose, we work in a distributed fashion, and the development philosophy that matches best is to work on the very latest of package versions and integrate aggressively all the time. To match this mode of development, we have introduced the rule that all baselines in Moose-projects only depend on #development
versions of other the Moose-projects. Furthermore, the #development
version always points to a baseline.
For example, ConfigurationOfMoose looks like:
development: spec
<symbolicVersion: #'development'>
spec for: #'pharo2.x' version: '4.8-baseline'.
baseline48: spec
<version: '4.8-baseline'>
spec for: #common do: [
...
spec project: 'Glamour' with: [
spec
className: 'ConfigurationOfGlamour';
file: 'ConfigurationOfGlamour';
version: #development;
repository: 'http://www.smalltalkhub.com/mc/Moose/Glamour/main' ].
...
]
In this way, when we load the #development
version, we actually load the very latest of all projects. This works well when in development mode, but it is a pain at release time.
Theoretically, we would have to release all sub-projects at the same time and hard code all versions. Given that in Moose we have some 161 packages, it would be a very tedious job to do manually. We need a tool that provides some snapshotting capabilities.
Meet Snapshotcello, a little utility that Stéphane Ducasse and I worked on, and that enables you to freeze a snapshot of a given configuration based on what is already loaded in your current image.
The idea is simple. You develop against the latest versions of all packages, and commit your changes for each package. When you are ready for a release, you assemble your image, and construct a snapshot version that can be reloaded later.
As an example, suppose you want to take a snapshot of the current Moose baseline and publish it under 4.8-snapshot
. The magic incantation goes like:
Snapshotcello new
configurationClass: ConfigurationOfMoose;
configurationVersion: #development;
publishVersion: '4.8-snapshot'
After a couple of seconds, the result looks like this:
version48snapshot: spec
"generated by Snapshotcello"
<version: '4.8-snapshot'>
spec for: #common do: [
self populateSpec: spec with: self snapshot1 ]
snapshot1
"generated by Snapshotcello"
^ #(
#('ConfigurationOfFame-TudorGirba.19.mcz'
'http://www.smalltalkhub.com/mc/Moose/Fame/main/'
'ConfigurationOfFame' )
...
#('Glamour-Announcements-TudorGirba.7.mcz'
'http://www.smalltalkhub.com/mc/Moose/Glamour/main/'
'Glamour-Announcements' )
...
)
The snapshot1
method lists all packages that should be loaded from all included projects in the right order. version48snapshot:
provides a wrapper to make the code loadable through Metacello. Why not have the two in the same method? A technical limitation: we cannot have that many symbols in one Pharo method. To go around the problem, we create only one literal array with the data tuples and manipulate them through the populateSpec:with:
utility method. The good news is that it works.
Snapshotcello is light. In fact, it comes with 2 classes of which one is an Error specialization. But, I think it opens doors to less constrained development. To give it a shot, you can load the code via:
Gofer new
smalltalkhubUser: 'girba' project: 'Snapshotcello';
package: 'ConfigurationOfSnapshotcello';
load.
(Smalltalk globals at: #ConfigurationOfSnapshotcello) loadDevelopment