Pharo projects often require a configuration to declare how they should be loaded. This configuration is done via Baselines. A baseline defines the packages of the project, their dependencies to each other and to external projects and independent sub-groups that can be loaded.
Adding a baseline to a project has some advantages:
- It makes it easier to load the project
- It makes it easier for others to contribute to your project
- It allows the users of the project to be unaware of the project's dependencies
- It makes explicit the dependencies of the project
- It ensures that packages and dependencies of the project are loaded in the right order
This documentation explains how to write a baseline and how to load the project described by this baseline.
- Baselines
The first step to create a baseline is to create a new subclass of BaselineOf
. In the following example, MyProject
is to be substituted by the name of your project:
BaselineOf subclass: #BaselineOfMyProject
slots: { }
classVariables: { }
package: 'BaselineOfMyProject'
This class should be in a package separated from other packages' projects. The package holding the baseline must have the same name as the baseline. To summarize, BaselineOfMyProject
class is in the BaselineOfMyProject
package.
Then, create a method that defines the spec of the project for the commit it will be included in.
baseline: spec
<baseline>
spec
for: #common
do: [
"The main code of the baseline will go here"
]
The name of this method does not have to be
#baseline:
; however, that is the name that is commonly used. In fact, it is the<baseline>
pragma which specifies that the method defines the spec of the project.
If your project is stored using a metadataless format (Tonel or FileTree metadataless), which is the default since Pharo 6, you need to add this method to your baseline:
projectClass
^ MetacelloCypressBaselineProject
Or, if the project should be loadable in Pharo < 6.1, use this version:
projectClass
^ [ self class environment at: #MetacelloCypressBaselineProject ]
on: NotFound
do: [ super projectClass ]
The method is common to all projets using the metadataless format and the class return does not depend on the name of your baseline.
This will allow Metacello to be able to update your project and is needed because the default project class of Metacello used metadata to know if an update was needed.
To define the packages of the project, send the message #package:
to the spec with the name of the package as argument.
baseline: spec
<baseline>
spec
for: #common
do: [
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests';
package: 'MyProject-Gui';
package: 'MyProject-Gui-Tests';
package: 'MyProject-Examples' ]
Note: Packages are the most atomic entities managed by the baseline. It is not possible to declare entities at the package-tag granularity.
Defining packages is not enough to load them, because some of them might depend on other packages/projects. For example, MyProject-Tests
needs to be loaded after MyProject
.
To manage dependencies that are external to a project, see section Define external dependencies.
For dependencies between the packages of your project, you can use the message #package:with:
to give more information to the spec.
baseline: spec
<baseline>
spec
for: #common
do: [
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ]
The method #requires:
will define the list of dependencies of a specific package.
Another way to declare requirements is to use the method #includes:
. This method takes a collection of declarations as a parameter and will notify Metacello that all of them should include the package if they are loaded. This is helpful when defining platform-specific requirements, in case we want one of our packages to come with a platform-dependant package, which is depending on this package. See example in section Loads different packages depending on the Pharo version.
Defining external dependencies can be done in different ways depending on where the dependency is hosted.
To improve readability, I recommend extracting the definitions of dependencies into separate methods.
To depend on a git project, you can use the method #baseline:with:
.
spec
baseline: '{BaselineName}'
with: [ spec repository: '{prefix}://{url}:{owner}/{projectName}:{version}/{subfolder}' ]
This snippet should be configured with:
{BaselineName}
: The name of the baseline to load (e.g,'MaterialDesignLite'
to loadBaselineOfMaterialDesignLite
){prefix}
: This is host-specific:github
for githubbitbucket
for bitbucketgitlab
for gitlabgit
for others (and {url} is thus mandatory)
{url}
: Base url to the git host. Mandatory when prefixgit
is used, optional for other prefixes (can be useful for self hosted gitlab for example){owner}
: Name of the user or organization hosting the project{projectName}
: Name of the project{version}
: This parameter is optional (defaults to master). It can be the name of a branch, a tag like'v1.2.0'
or'v1.x.x'
, or a the SHA of a commit{subfolder}
: This parameter is optional in case the code is not at the root of the project. It should point to the sub-folder containing the code
Example:
spec
baseline: 'MaterialDesignLite'
with: [ spec repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src']
Some projects can defines groups
in their baselines. They are subsets of the project that can be loaded independently.
The previous snippet can also be customized to load only a specific group of the dependency like this:
spec
baseline: 'MaterialDesignLite'
with: [
spec
loads: #('Extensions');
repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src'
]
Once the dependency is defined, add BaselineName
to the list of the required dependencies of the package depending on it.
Example:
baseline: spec
<baseline>
spec
for: #common
do: [
"Dependencies"
self materialDesignLite: spec.
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject' 'MaterialDesignLite') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ].
materialDesignLite: spec
spec
baseline: 'MaterialDesignLite'
with: [
spec
loads: #('Extensions');
repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src'
]
In some cases your project might depend on an external project, but two packages of your project depend on different groups of this external project.
You can use the message #project:copyFrom:with:
to create a new dependency spec.
materialDesignLite: spec
spec
baseline: 'MaterialDesignLite' with: [ spec repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src' ];
project: 'MaterialDesignLiteExtensions' copyFrom: 'MaterialDesignLite' with: [ spec loads: #('Extensions') ]
Then you can use the new project name in the specification of dependencies.
Example:
baseline: spec
<baseline>
spec
for: #common
do: [
"Dependencies"
self materialDesignLite: spec.
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject' 'MaterialDesignLiteExtensions') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests' 'MaterialDesignLite' "We load the version containing MDL tests for our tests only") ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ].
materialDesignLite: spec
spec
baseline: 'MaterialDesignLite' with: [ spec repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src' ];
project: 'MaterialDesignLiteExtensions' copyFrom: 'MaterialDesignLite' with: [ spec loads: #('Extensions') ]
Sometimes we do not have access to a network, so we want to define dependencies to local git repositories.
This works like in the previous section, but with this repository format:
spec
baseline: 'MaterialDesignLite'
with: [ spec repository: 'gitlocal://full/path/to/repository' ]
Depending on a Smalltalkhub project is done via #project:with
.
spec
project: '{DependencyName}'
with: [ spec
className: #ConfigurationOf{ConfigurationName};
versionString: #'{Version}';
repository: 'http://smalltalkhub.com/mc/{owner}/{repositoryName}/main/' ]
The snippet should be configured with:
{DependencyName}
: It can be anything from your packages, groups and other dependencies names. It will be used to define dependency to this project in your packages/groups{ConfigurationName}
: It is the name of the configuration you wish to reference{Version}
: Name of the version you wish to reference. It can be something like'development'
,'stable'
,'release1'
,'1.2.6'
,'1.0-baseline'
, etc.{owner}
: Name of the team or user hosting the project{repositoryName}
: Name of the repository on SmalltalkHub
Example:
spec
project: 'Magritte3'
with: [ spec
className: #ConfigurationOfMagritte3;
versionString: #'release3';
repository: 'http://smalltalkhub.com/mc/Magritte/Magritte3/main/' ]
As for git-hosted repositories, you can ask for a specific group:
spec
project: 'Magritte3'
with: [ spec
className: #ConfigurationOfMagritte3;
versionString: #'release3';
loads: #('Seaside');
repository: 'http://smalltalkhub.com/mc/Magritte/Magritte3/main/' ]
You can now use the dependency names to add the project as a dependency of your packages.
baseline: spec
<baseline>
spec
for: #common
do: [
"Dependencies"
self magritte3: spec.
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject' 'Magritte3') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ].
magritte3: spec
spec
project: 'Magritte3'
with: [ spec
className: #ConfigurationOfMagritte3;
versionString: #'release3';
loads: #('Seaside');
repository: 'http://smalltalkhub.com/mc/Magritte/Magritte3/main/' ]
Sometimes we don't want to load the full project, but just a sub part, e.g.:
- Only the model of a project is needed without the UI (for example to build an alternative UI)
- Only the core of the project is needed without the tests and examples
- The project has some optional modules
- etc.
To manage such cases, baselines have the concept of a Group
. A group is a loadable spec containing only a sub part of the project.
They can be declared with the #group:with:
message. The second parameter defines the content of the group. The content can either be a package name, a dependency name, or even another group name.
Example:
baseline: spec
<baseline>
spec
for: #common
do: [
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ].
"Groups"
spec
group: 'Model' with: #('MyProject');
group: 'Tests' with: #('MyProject-Tests' 'MyProject-Gui-Tests');
group: 'Gui' with: #('MyProject-Gui');
group: 'Example' with: #('MyProject-Examples');
group: 'All' with: #('Model' 'Tests' 'Gui' 'Example')
To load a project with a given group, you can check the section loading groups.
Each baseline has a default group named 'default'
. This group includes all the packages and the dependencies declared in the baseline.
When using the message #load
with Metacello, or when not specifying the group of a dependency, it will load the "default" group.
This group can be redefined to change what will be loaded by default in a project.
Example:
baseline: spec
<baseline>
spec
for: #common
do: [
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ].
"Groups"
spec
group: 'default' with: #('Model' 'Gui');
group: 'Model' with: #('MyProject');
group: 'Tests' with: #('MyProject-Tests' 'MyProject-Gui-Tests');
group: 'Gui' with: #('MyProject-Gui');
group: 'Example' with: #('MyProject-Examples');
group: 'All' with: #('Model' 'Tests' 'Gui' 'Example')
Baselines provide some hooks to execute some code when loading a project.
Those hooks are:
#preLoadDoIt:
which is executed after the code and dependencies are resolved and fetched, but before the code is loaded.#postLoadDoIt:
which is executed when the project finishes loading.
Those methods take a symbol as parameter, which should be the name of a method of the baseline that should be executed by the hook.
Those methods can take two optional parameters:
- A Metacello loader containing information on the current project to load
- A Metacello spec containing information on the project spec
Example:
baseline: spec
<baseline>
spec
for: #common
do: [
spec preLoadDoIt: #'preload:package:'.
spec postLoadDoIt: #'postload:package:'.
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ]
preload: loader package: packageSpec
Transcript crLog: 'The fetch was finished. Now let''s load the project'
postload: loader package: packageSpec
Transcript crLog: 'Project loaded!'
It might be useful to load some packages in specific Pharo versions only. For example, if we have a compatibility package for Pharo 6, we do not want to load it in Pharo 7.
This is possible with the different spec attributes.
Up until now we defined everything in a spec for #common
, which applies to all versions of Pharo. But it's possible to define a spec for specific Pharo versions or even other Smalltalk environments.
We can add in the baseline a special #for:do:
command taking as parameter a specific attribute.
Every Pharo version contains some default attributes. For a Pharo version X.Y we have:
#pharo
#pharoX.x
#pharoX.Y.x
For example for Pharo 6.1:
#pharo
#pharo6.x
#pharo6.1.x
Those attributes can be used to define a spec that will be executed only in the images containing the corresponding tags.
Example:
baseline: spec
<baseline>
spec
for: #common
do: [
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ].
spec
for: #'pharo6.x'
do: [ spec
package: 'MyProject' with: [ spec requires: #('MyProject-Pharo6') ];
package: 'MyProject-Pharo6' ].
spec
for: #(#'pharo3.x' #'pharo4.x' #'pharo5.x' #'pharo6.x')
do: [ spec
package: 'MyProject' with: [ spec requires: #('MyProject-Pharo3To6') ];
package: 'MyProject-Pharo3To6' ] ]
The #includes:
method explained in section Define packages forming your project is often useful when dealing with platform-specific requirements. Imagine your package MyProject
will work in Pharo 6 only if MyProject-Pharo6
is present, but MyProject-Pharo6
depends on MyProject
. This can be resolved like this:
Example:
baseline: spec
<baseline>
spec
for: #common
do: [
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ].
spec
for: #'pharo6.x'
do: [ spec
package: 'MyProject' with: [ spec includes: #('MyProject-Pharo6') ];
package: 'MyProject-Pharo6' with: [ spec requires: #('MyProject') ] ].
spec
for: #(#'pharo3.x' #'pharo4.x' #'pharo5.x' #'pharo6.x')
do: [ spec
package: 'MyProject' with: [ spec requires: #('MyProject-Pharo3To6') ];
package: 'MyProject-Pharo3To6' ] ]
On top of attributes from Pharo, it's also possible to define our own attributes.
We override the method #customProjectAttributes
to return the custom attributes depending on the environment.
For example:
customProjectAttributes
Smalltalk os isMacOS ifTrue: [ ^ #(#MacOS) ].
Smalltalk os isUnix ifTrue: [ ^ #(#Unix) ].
Smalltalk os isWindows ifTrue: [ ^ #(#Windows) ]
Then they can be used in the baseline.
baseline: spec
<baseline>
spec
for: #common
do: [
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests') ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ] ].
spec
for: #(#'MacOS' #'Unix') do: [
self osSubprocess: spec.
spec package: 'MyProject' with: [ spec requires: #('OSSubprocess') ] ];
for: #'Windows' do: [
self processWrapper: spec.
spec package: 'MyProject' with: [ spec requires: #('ProcessWrapper') ] ]
osSubprocess: spec
spec
baseline: 'OSSubprocess'
with: [ spec repository: 'github://pharo-contributions/OSSubprocess:v1.0.1/repository' ]
processWrapper: spec
spec
configuration: 'ProcessWrapper'
with: [
spec
versionString: '1.2';
repository: 'http://smalltalkhub.com/mc/hernan/ProcessWrapper/main' ]
Baselines support different loading types. The loading types define how Metacello loads the project.
By default, a baseline uses linear loading, which means packages are loaded one by one with their requirements loaded before them.
This load type forces Metacello to load the full project in an atomic load. This is useful when a project has cyclic dependencies that cannot be resolved. For example it's useful to do an atomic load of Pharo's Kernel and Collections, since they depend on each other.
To define atomic loading, override the method #project
:
project
^ super project
loadType: #atomic;
yourself
Here is an example with all previous features illustrated:
"baseline"
baseline: spec
<baseline>
spec
for: #common
do: [
spec preLoadDoIt: #'preload:package:'.
spec postLoadDoIt: #'postload:package:'.
"Dependencies"
self materialDesignLite: spec.
"Packages"
spec
package: 'MyProject';
package: 'MyProject-Tests' with: [ spec requires: #('MyProject') ];
package: 'MyProject-Gui' with: [ spec requires: #('MyProject' 'MaterialDesignLiteExtensions' 'Magritte3') ];
package: 'MyProject-Gui-Tests' with: [ spec requires: #('MyProject-Tests' 'MaterialDesignLite' "We load the version containing MDL tests for our tests only") ];
package: 'MyProject-Examples' with: [ spec requires: #('MyProject-Gui') ].
"Groups"
spec
group: 'Model' with: #('MyProject');
group: 'Tests' with: #('MyProject-Tests' 'MyProject-Gui-Tests');
group: 'Gui' with: #('MyProject-Gui');
group: 'Example' with: #('MyProject-Examples');
group: 'All' with: #('Model' 'Tests' 'Gui' 'Example') ].
spec
for: #'pharo6.x'
do: [ spec
package: 'MyProject' with: [ spec includes: #('MyProject-Pharo6') ];
package: 'MyProject-Pharo6' with: [ spec requires: #('MyProject') ] ].
spec
for: #(#'pharo3.x' #'pharo4.x' #'pharo5.x' #'pharo6.x')
do: [ spec
package: 'MyProject' with: [ spec requires: #('MyProject-Pharo3To6') ];
package: 'MyProject-Pharo3To6' ] ].
spec
for: #(#'MacOS' #'Unix') do: [
self osSubprocess: spec.
spec package: 'MyProject' with: [ spec requires: #('OSSubprocess') ] ].
spec
for: #'Windows' do: [
self processWrapper: spec.
spec package: 'MyProject' with: [ spec requires: #('ProcessWrapper') ] ]
projectClass
^ MetacelloCypressBaselineProject
"dependencies"
materialDesignLite: spec
spec
baseline: 'MaterialDesignLite' with: [ spec repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src' ];
project: 'MaterialDesignLiteExtensions' copyFrom: 'MaterialDesignLite' with: [ spec loads: #('Extensions') ]
"dependencies"
magritte3: spec
spec
project: 'Magritte3'
with: [ spec
className: #ConfigurationOfMagritte3;
versionString: #'release3';
loads: #('Seaside');
repository: 'http://smalltalkhub.com/mc/Magritte/Magritte3/main/' ]
"dependencies"
osSubprocess: spec
spec
baseline: 'OSSubprocess'
with: [ spec repository: 'github://pharo-contributions/OSSubprocess:v1.0.1/repository' ]
"dependencies"
processWrapper: spec
spec
configuration: 'ProcessWrapper'
with: [
spec
versionString: '1.2';
repository: 'http://smalltalkhub.com/mc/hernan/ProcessWrapper/main' ]
"accessing"
customProjectAttributes
Smalltalk os isMacOS ifTrue: [ ^ #(#MacOS) ].
Smalltalk os isUnix ifTrue: [ ^ #(#Unix) ].
Smalltalk os isWindows ifTrue: [ ^ #(#Windows) ]
"actions"
preload: loader package: packageSpec
Transcript crLog: 'The fetch was finished. Now let''s load the project'
"actions"
postload: loader package: packageSpec
Transcript crLog: 'Project loaded!'
"accessing"
project
^ super project
loadType: #atomic;
yourself
When you have a project with a Baseline, it is possible to load it in a Pharo image if the project is compatible with the Pharo version.
Here we explain how to load a git project via its baseline.
The first way to load a project is to create a Metacello request programmatically and to execute it. This request looks like this:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src';
baseline: 'MaterialDesignLite';
load
Note the three steps:
- Create a new Metacello request
- Configure it (declare the repository of the project, specify the version, the baseline, optional options...)
- Launch the loading
To configure the request, some options are necessary and some are optional. We cover in the next two sub sections how to configure the loading of a project hosted via Monticello and git, and then we detail optional parameters.
To load a project from git you need to execute an expression like this:
Metacello new
repository: {repository};
baseline: {baselineName};
load
This command has two parameters:
repository
defining the location of the git project, the version of the project to load and the subdirectory in which the project is stored.baselineName
is the name of the baseline to load. For example to load theMaterialDesignLite
project, the baseline name isMaterialDesignLite
to load the project withBaselineOfMaterialDesignLite
.
The repository parameter is a string that can take different forms depending on if the project is local or hosted remotely.
The repository parameter to load a project from github/gitlab/bitbucket takes this form:
{prefix}://{optionalHostname}:{owner}/{projectName}:{version}/{subFolder}
This snippet should be configured with:
{prefix}
: This is host-specific:github
for githubbitbucket
for bitbucketgitlab
for gitlab
{optionalHostname}
: Optional server host, for private git servers{owner}
: Name of the user or organization hosting the project{projectName}
: Name of the project{version}
: This parameter is optional, and it defaults to master. It can be the name of a branch, a tag like'v1.2.0'
or'v1.x.x'
, or a the SHA of a commit{subfolder}
: This parameter is optional in case the code is at the root of the project. It should point to the sub-folder containing the code.
Example: loading from Github.
Metacello new
repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src';
baseline: 'MaterialDesignLite';
load
Example: loading from a private Gitlab host.
Metacello new
baseline: 'Ghost';
repository: 'gitlab://gitlab.inria.fr:RMOD/Ghost';
load
Metacello also comes with some syntactic sugar to define the repository to github or bitbucket:
- Github:
Metacello>>githubUser:project:commitish:path:
- Bitbucket:
Metacello>>bitbucketUser:project:commitish:path:
Example:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src';
baseline: 'MaterialDesignLite';
load
To load a project from a local repository you can use this form to declare the repository:
'{prefix}://{full/path/to/repository}/{subFolder}'
This snippet should be configured with:
{prefix}
: This is specific to the file format:filetree
for a Filetree projecttonel
for a Tonel project
{full/path/to/repository}
: is the path to the project on the file system{subfolder}
: This parameter is optional in case the code is at the root of the project. It should point to the subfolder containing the code.
Example:
Metacello new
repository: 'tonel://C:\Users\Cyril\GitRepositories\Native-Browser\src';
baseline: 'NativeBrowser';
load
To load a project from Smalltalkhub you need to execute an expression like this:
Metacello new
repository: 'http://smalltalkhub.com/mc/{owner}/{repositoryName}/main';
configuration: {configurationName};
version: {version};
load
This command has two parameters:
owner
: Name of the team or user hosting the projectrepositoryName
: Name of the repository on SmalltalkHubconfigurationName
is the name of the configuration to load. For example to load theMaterialDesignLite
project, the baseline name isMaterialDesignLite
to load the project withBaselineOfMaterialDesignLite
.{version}
: Name of the version you wish to reference. It can be something like'development'
,'stable'
,'release1'
,'1.2.6'
,'1.0-baseline'
, etc.
Example:
Metacello new
repository: 'http://smalltalkhub.com/mc/Seaside/Seaside31/main';
configuration: 'Seaside3';
version: #stable;
load
You can also use Metacello>>smalltalkhubUser:project:
:
Metacello new
smalltalkhubUser: 'Seaside' project: 'Seaside31';
configuration: 'Seaside3';
version: #stable;
load
It is possible to use Metacello without specifying any repository. This can be useful for defining all project dependencies in a baseline and then loading them with Metacello.
Metacello new
baseline: #TinyBlog;
load
Sometimes we want to load only specific groups of a project. This can be done be replacing the load
selector by load:
.
The load:
selector takes a string or a collection of strings as parameter. Each string represents a group name from the baseline.
Examples:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src';
baseline: 'MaterialDesignLite';
load: 'Extensions'
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src';
baseline: 'MaterialDesignLite';
load: #('Extensions' 'Widgets')
Sometimes there can be conflicts, updates or downgrades while loading a project.
For example, imagine in an image the project ProjA
at version v1.0.0. We want to load our project ProjB
that depends on ProjA
version v2.0.0., ProjC
version v1.0.0, and ProjD
that loads also ProjC
version v2.0.0.
If we load ProjB
in those conditions, we will have two problems:
- The update of
ProjA
from v1.0.0 to v2.0.0 - A conflict between
ProjC
v1.0.0 and v2.0.0
To manage conflicts we can use the options onConflict:
, onUpgrade:
and onDowngrade:
.
Example:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src';
baseline: 'MaterialDesignLite';
onConflict: [ :ex | ex useIncoming ];
onUpgrade: [ :ex | ex useIncoming ];
onDowngrade: [ :ex | ex useLoaded ];
load
A last conflicting situation happens if Pharo includes a project in the default distribution and you want to load a new version. To manage this case you have the ignoreImage
option.
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src';
baseline: 'MaterialDesignLite';
onConflict: [ :ex | ex useIncoming ];
onUpgrade: [ :ex | ex useIncoming ];
onDowngrade: [ :ex | ex useLoaded ];
ignoreImage;
load
Here is a last example of conflict management. The Pharo community was previously on a version control system called Monticello. Most of the community has now migrated to GitHub. Some of the projects exist on Smalltalkhub (managed with Monticello) and on GitHub. It's not unusual to have conflict between the two.
Here is a little script that loads the version managed with git when the project name is the same:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src';
baseline: 'MaterialDesignLite';
onConflict: [ :ex :a :b | a projectName = b projectName ifTrue: [ a projectSpec isBaselineOfProjectSpec ifTrue: [ ex useLoaded ] ifFalse: [ ex useIncoming ] ] ifFalse: [ ex resume ] ];
load
In some cases a project has problems during the loading, for example, if a package loaded is missing a dependency. When this happen, Metacello will raise a warning. Most of the time the projects can still work, at least partially. If you do not want Metacello to open a warning, you can log them instead. To enable this option you can use the onWarningLog
or onWarning:
options.
Examples:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src';
baseline: 'MaterialDesignLite';
onWarning: [ :ex | Transcript crShow: ex ];
load
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src';
baseline: 'MaterialDesignLite';
onWarningLog;
load
In Pharo 7 a new tool to manage git repositories was introduced: Iceberg. This tool allows the developer to load a project via a user interface.
The first step is to add your git project to Iceberg. Then right-click on the project name to access a Metacello
sub-menu to load the project.
This section covers other features of baselines and Metacello.
In the normal course of loading and upgrading, you want the correct version of dependent projects loaded. However, there are some special cases where automatic upgrading isn't desirable:
- You may be actively developing a particular version of a project and you don't want the project upgraded (or downgraded) out from under you.
- You may be working with a git checkout of a project and you want to continue using the git checkout.
- You may not want to have particular projects upgraded automatically.
The lock
command forces a particular version.
Example:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.1.0' path: 'src';
baseline: 'MaterialDesignLite';
lock
This example will lock the project MaterialDesignLite to version v1.1.0.
You can check the list of locked projects via those snippets:
Metacello registry locked. "Return the list of locked projects from the Metacello registry"
Metacello image locked. "Return the list of locked projects loaded in the image."
If you wish to unlock a project, you can use the unlock
message.
Example:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.1.0' path: 'src';
baseline: 'MaterialDesignLite';
unlock
During the loading of a project you can also do some specific actions when you encounter a lock. For this you can use the onLock:
message.
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.1.0' path: 'src';
baseline: 'MaterialDesignLite';
onLock: [ :ex :loaded :incoming | loaded baseName = 'myProject' ifTrue: [ ex break ] ifFalse: [ ex honor ] ];
load
Metacello includes a command to load the baseline of a project into the image. This is useful in two cases:
- You want to load only the baseline of the project
- You already have an obsolete baseline in your image and you want to update it before loading the project
To do that you can use the get
command.
Example:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src';
baseline: 'MaterialDesignLite';
get
The fetch command downloads all of the packages without loading them. This includes the packages of the project but also their dependencies.
Example:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src';
baseline: 'MaterialDesignLite';
fetch
You can also specify a group:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src';
baseline: 'MaterialDesignLite';
fetch: #('Extensions' 'Widgets')
The fetch command duplicates what the load command would do, which means if a package is already loaded in the image, it will not be fetched. To fetch packages regardless of what is loaded in the image, use the ignoreImage
option:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src';
baseline: 'MaterialDesignLite';
ignoreImage;
fetch
The record
command performs the same function as the fetch
command, without actually downloading any files. As a consequence, it can give you a quick report of what packages will be loaded into your image. The recording will be produced in the Transcript
(cmd + o + t).
Example:
Metacello new
githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src';
baseline: 'MaterialDesignLite';
record
The list
command lists projects in the image or Metacello registry:
Metacello image
baseline: [ :spec | true "spec name beginsWith: 'Seaside'" ];
list
Metacello registry
baseline: [ :spec | true "spec name beginsWith: 'Seaside'" ];
list
This command needs to be inspected to see the list.
- Deep into pharo: Chapter 9, Managing Projects with Metacello
- Metacello documentation