I actually released Metacello 1.0-beta.25 a couple of weeks ago as promised, but I’ve been side-tracked several times along the way to writing the promised documentation on the new 1.0-beta.25 features:
- Fetch and Record
- API Changes
- Metacello Tools Changes
- 1.0-beta.25 BugFixes [external link]
- Road to 1.0
Note: As usually happens while writing documentation, I discovered that the API needed to be extended beyond that defined in version 1.0-beta.25, so the API described in this post corresponds to Metacello version 1.0-beta.26.
- make a pass through the Issues and mark those to be fixed by the 1.0 release
- start testing the GLASS upgrade process using Metacello 1.0-beta.24
Unfortunately (or fortunately) by the end of March I ran into a snag upgrading from GLASS.230-dkh.177. The problem was that an HTTP request for an mcz file failed during the download. Miguel Cobá had identified just such a problem the week before (and proposed a solution), so I thought I might as well bite the bullet now and address the issue.
At the time Metacello was designed to downloaded the mcz file right before the mcz file was loaded. For projects involving lots of files and/or long compile times that meant that you’d have to keep an eye on the whole process just in case a network error caused the load to fail.
Miguel’s suggestion was to download (fetch) all of the files first, then compile/load each of the mcz files … That way once you saw that the downloads had completed successfully you could go off and relax, have a beer or maybe even shoe a horse.
I didn’t have to stretch my imagination very far to realize that if I am going to split the load into a a fetch phase and load phase, I might as well make the fetch phase accessible to the user rather than keep it hidden under the covers. The fetch phase by itself can be pretty useful:
- Using fetch, you can determine which mcz files will be loaded into your image.
- Using fetch, you can download the mcz files and metacello configurations into a local file-based repository and then using repositoryOverrides feature you can arrange to load from the local file-based repository.
Here is an example where I’ve printed out the result of doing a fetch on the latest version of Pier from a Pharo Dev image and you can seewhich mcz files will be loaded into the image:
ConfigurationOfPier project latestVersion fetch loadDirective. linear load : explicit load : 22.214.171.124 [ConfigurationOfPier] explicit load : 126.96.36.199 [ConfigurationOfPier] linear load : 188.8.131.52 [ConfigurationOfPier] linear load : 184.108.40.206 [ConfigurationOfMagritte] load : Magritte-Model-lr.367 explicit load : 220.127.116.11 [ConfigurationOfMagritte] linear load : 18.104.22.168 [ConfigurationOfMagritte] explicit load : 22.214.171.124 [ConfigurationOfSeaside28] linear load : 126.96.36.199 [ConfigurationOfSeaside28] linear load : 1.0 [ConfigurationOfKomHttpServer] load : DynamicBindings-lr.11 load : KomServices-gc.19 load : KomHttpServer-lr.51 load : Seaside2.8a1-pmm.596 load : RSRSS2-pmm.12 load : Scriptaculous-lr.250 load : Comet-lr.29 load : Magritte-Seaside-lr.316 load : Magritte-Tests-lr.159 load : Pier-Model-lr.351 load : Pier-Tests-lr.150 load : Pier-Seaside-lr.451 load : Pier-Security-lr.144 load : Pier-Blog-lr.134 load : Pier-Squeak-Persistency-kph.24
Of course, this kind of report is a lot more useful if you’ve already got Pier loaded and want to know what will be loaded if you do an upgrade. Here’s the report when run in an image into which Pier 1.2.1 has already been loaded:
ConfigurationOfPier project latestVersion fetch loadDirective. linear load : explicit load : 188.8.131.52 [ConfigurationOfPier] explicit load : 184.108.40.206 [ConfigurationOfPier] linear load : 220.127.116.11 [ConfigurationOfPier] explicit load : 18.104.22.168 [ConfigurationOfMagritte] linear load : 22.214.171.124 [ConfigurationOfMagritte] explicit load : 126.96.36.199 [ConfigurationOfSeaside28] load : Magritte-Model-lr.367 load : Magritte-Seaside-lr.316 linear load : 188.8.131.52 [ConfigurationOfMagritte] load : Pier-Model-lr.351 load : Pier-Tests-lr.150 load : Pier-Seaside-lr.451 load : Pier-Security-lr.144 load : Pier-Blog-lr.134 load : Pier-Squeak-Persistency-kph.24
The loadDirective is a nested structure that records the entities that may be loaded.The loadDirective is the product of the fetch pass and is used to drive the load pass. There is a loadDirective for each version, mcz package, preLoad doIt and postLoad doIt:
- a versionLoadDirective represents the load for a specific version of a project. The versionLoadDirective has a list of directives that are to be loaded. The versionLoadDirective represents an explicit, atomic or linear load. An explicit load indicates an mcz load that occurred during the fetch phase and will not be performed (again) during the load phase. The atomic and lnear load directives correspond to the loadType for a particular project.
- a packageLoadDirective represents the mcz file to be loaded.
- a preLoadDirective represents a pre load doit.
- a postLoadDirective represents a post load doit.
Given a loadDirective, one can:
- traverse all directives depth first using #directivesDo:
- traverse all versionLoadDirectives depth first using #versionDirectivesDo:
- traverse all packageLoadDirectives depth first using #packageDirectivesDo:
- traverse all preLoadDirectives and postLoadDirectives depth first using #prepostLoadDirectivesDo:
Given a versionDirective, one can:
- traverse versionLoadDirectives using #versionsDo: (direct children only).
- traverse packageLoadDirectives using #packagesDo: (direct children only).
- traverse preLoadDirectives and postLoadDirectives using #prepostLoadsDo: (direct children only).
Here’s an example where we split the fetch and load process so that we can use the loadDirective to determine whether we want to finish doing the load.
Assume that you’ve subclassed some of the classes in the Magritte-Seaside package and you are interested in being notified if a new version of Magritte-Seaside is going to be loaded. The following script uses #packageDirectivesDo: to scan the list of mcz files for a package named ‘Magritte-Seaside’ and throws a halt if one is found. If no Magritte-Seaside package is found, continue with the load using #doLoad:
| version loader | version := ConfigurationOfPier project latestVersion. loader := version fetch. "Check for Magritte-Seaside package in load directives" loader loadDirective packageDirectivesDo: [:directive | (directive spec name = 'Magritte-Seaside') ifTrue: [ "do something interesting" self halt: 'Magritte-Seaside package loading.' ]]. "No Magritte-Seaside loading, so finish the load" loader doLoad.
Here’s an example script showing how I maintain a backup repository for Metacello. In this script I’m interested in fetching ConfigurationOfGofer, ConfigurationOfMetacello and all of the mcz files directly referenced by those two configurations. The cache repository (target for fetches) is http:////www.squeaksource.com/metacello:
| cacheRepository version | cacheRepository := MCHttpRepository location: 'http://www.squeaksource.com/metacello' user: '' password: ''. version := ConfigurationOfMetacello project version: '1.0-beta.26'. (version record: 'ALL') loadDirective versionDirectivesDo: [:versionDirective | | p pClass | versionDirective spec ~~ nil ifTrue: [ p := versionDirective spec project. pClass := p configuration class. "save packages for Gofer and Metacello only" (pClass == ConfigurationOfGofer or: [ pClass == ConfigurationOfMetacello ]) ifTrue: [ | policy | policy := (MetacelloLoaderPolicy new) cacheRepository: cacheRepository; ignoreImage: true; yourself. "fetch Gofer or Metacello configuration" p fetchProject: policy. versionDirective packagesDo: [:packageDirective | "skip nested configurations" (packageDirective spec name beginsWith: 'ConfigurationOf') ifFalse: [ "fetch mcz file" packageDirective spec fetchPackage: policy ]]]]].
I maintain the secondary repository in case the GemSource repository is inaccessible. Use ConfigurationOfMetacello class>>alternateEnsureMetacello to load Metacello from the secondary repository.
If you want to create a local directory cache of the mcz files and metacello configurations that you use in your own project, then you can use fetch:
| localRepository version | "define local repository" localRepository := MCDirectoryRepository new directory: (FileDirectory on: '/home/dhenrich/monticello'). version := ConfigurationOfMagritte project version: '184.108.40.206'. "set cache repository" version cacheRepository: localRepository. "fetch mcz file even if image is up-to-date" version ignoreImage: true. "fetch mcz files into local repository" version fetch.
At a later date you use #repositoryOverrides: to force the mcz files and metacello configurations to be loaded from the local repository:
| localRepository version | localRepository := MCDirectoryRepository new directory: (FileDirectory on: '/home/dhenrich/monticello'). version := ConfigurationOfMagritte project version: '220.127.116.11'. "set the repositoryOverrides" version repositoryOverrides: (Array with: localRepository). "do the load" version load.
You see the pattern in the project spec below repeated over and over again:
spec project: 'Refactoring-Core' with: [ spec className: 'ConfigurationOfRefactoringBrowser'; loads: #('Refactoring-Core' ); file: 'ConfigurationOfRefactoringBrowser'; repository: 'http://www.squeaksource.com/MetacelloRepository' ]
The argument for the className: and file: selectors is redundant. In Metacello, there is no requirement that the name of the package and the name of the configuration class for a project be the same, however, we have defined a convention that they both be the same, making the file: specification redundant. In 1.0-beta.25, the file: spec is optional, so the following project spec is accepted:
spec project: 'Refactoring-Core' with: [ spec className: 'ConfigurationOfRefactoringBrowser'; loads: #('Refactoring-Core' ); repository: 'http://www.squeaksource.com/MetacelloRepository' ]
It probably makes sense to wait a bit before using this form in public configurations, because not everyone has updated to 1.0-beta.25, yet.
Set the repository where mcz files are stored during the load: or fetch:. By default an MCDictionaryRepository is used.
By default during the fetch phase, mcz files that are already loaded into the image are not fetched (or scheduled to be loaded). Sometimes, it is useful to ignore the mcz files that are loaded in the image and do a fetch on all of the mcz files (as in the example above). To do that you set #ignoreImage: to true.
Progress bars are really nice things to have when executing long running tasks in an interactive image. A quick glance at the progress bar can give you a pretty good idea of how far you’ve progressed when loading projects with lots of packages like Moose or Seaside30. However, if you want to use Metacello in scripting environment or to load code that tweaks the Morphic world then progress bars are less interesting, especially when they lead to quirks in the load process itself.
Here’s an example using silently: in a script:
| version | version := ConfigurationOfXXX version: '1.0'. version: silently: true. version load.
If you want to turn progress bars off permanently in your image you can do the following:
MetacelloPlatform current bypassProgressBars: true.
If you use the Save Packages command in the Metacello ToolSet, then you might like the new Save Packages (nested) command. For a simple project (no nested projects) you will notice that the commit comment for the packages gets reused. For nested projects you can save packages in multiple projects with the same command and carrying the commit comment across for each commit.
In truth, I think there are some rough edges in how the new command cancels the commit operation, but most of the time it works just fine. If you are annoyed by the new behavior, please submit a bug (don’t assume that what annoys you annoys me:) and then you can enable the old Save Packages command by editing OBCmdMetacelloSavePackages>>isActive (uncomment the ‘^super isActive‘ statement).
I am making progress (albeit slow) down the road to Metacello 1.0. I still plan to address these bugs and feature requests and revamp the way that Metacello handles version strings and version string parsing.