06 July 2015

Pharo and Git(Hub) Versioning [Revision 2]

While Monticello and Metacello are still the default tools for versioning and dependency management in Pharo, one can benefit by using Git with modern Metacello support of baselines to improve development experience. In the past I've wrote a "Dead simple intro to Metacello" and "How to distribute your GitHub Pharo packages" and tried to explain how to use Metacello its support for GitHub projects. This blogpost provides a fine description of a development workflow and covers versioning, dependency definition and release process a project.

External tools: in this example we use Git and GitHub (easily replaceable by BitBucket). Definitely everything can be broken down to bare FileTree functionality, but as I am describing a workflow, I need to rely on concrete tools. 

Versioning

Versioning can be simply done by FileTree Monticello repository. But it's much easier to load GitFileTree for the configuration browser, and it will perform a git commit each time you perform a Monticello commit, additionally GitFileTree will show you a full version history in Pharo.
Together with that it's highly recommended to use GitFileTree-MergeDriver. If you are using Git, you most likely want to make use of lightweight branching techniques, or someone will simply contribute a patch and you will have to merge it. This driver will solve most of the merging problems that are caused by Monticello metadata.

Managing Dependencies

Dependencies are managed by a baseline class. This class should be subclassed from BaselineOf and named BaselineOf<YourPojectName>. Essentially baseline class behaves almost in the same way as a configuration one. You have to define only one baseline method:
baseline: spec
   <baseline>
   spec for: #common do: [
      spec
         package: 'QualityAssistantAnalysis' with: [ spec requires: 'Roassal2' ].
  
      spec
         project: 'Roassal2'
         with: [ 
            spec
               className: 'ConfigurationOfRoassal2';
               version: '1.11';
               repository:
                  'http://smalltalkhub.com/mc/ObjectProfile/Roassal2/main' ] ]
You can read more about configuring dependencies in my blogpost about "Dead simple intro to Metacello".
Similarly to the configuration a baseline class should be in a package with the same name. Then after creating a baseline class, you should store it in the same git repository where your project is located. This gives you a phenomenal agility compared to the classic Metacello configurations: you don't have to specify the versions of your packages. Each git commit contains packages that should work together and a baseline that tells on which versions of other projects it depends. This way you have to change your baseline only if you have a big changes in your packages (addition, removal or renaming) or if want to manage dependencies (e.g. add, remove, update version of the project that you depend on).

Loading a Project

Here is the easiest way to load your project:
Metacello new
  baseline: #<projectname>;
  repository: 'github://<username>/<projectname>';
  load
Just replace <projectname> and <username> with your values. Also note that baseline: #<projectname> will turn into BaselineOf<projectname> and the next occurrence of <projectname> refers to your GitHub project, they don't have to be the same, but it's a nice convention to follow. Also if your repository is not in the root of git repository, you have to add a path in the end of url.

Releasing

The repository url consists of the following parts:
github://<username>/<projectname>[:<branch>|<tag>|<SHA>][/<path>]
They provide you with an amazing versatility in defining what you want to load. You can specify a branch, tag, of even SHA id of a commit. You can use branches in order to load the last commit of a certain feature development. I recommend to use tags as version markers, this can be also used in combination with GitHub releases. For example you can use the following script to load the 0.5.1 version of QualityAssistant:
Metacello new
  baseline: #QualityAssistant;
  repository: 'github://Uko/QualityAssistant:v0.5.1';
  load
Also instead of github "scheme" you can use bitbucket if you host your project on bitbucket.org.

Depending on a Baseline

In case you are depending on a project that defines a baseline, you may decide to depend directly on it rather then on configuration. This can be done but the following configuration spec:
spec
   baseline: 'Renraku'
   with: [ spec repository: 'github://Uko/Renraku:v0.3.0' ]
And then baseline key can be used as a requirement:
spec package: 'QualityAssistant' with: [ spec requires: 'Renraku' ]


Releasing with configurations

If you want to make your project available in Configuration Browser you have to create a classic configuration and upload it to the meta repo which is a Monticello one. Having a baseline version saves you a lot.
First if all you have to subclass ConfigurationOf and name your class ConfigurationOf<YourPojectName>. Then I suggest a new semantic for methods. Currently version 0.4.1 method would be called #version041:. But if you have a method called #version2113: does this mean that it's for 2.11.3 or 2.1.13 or even maybe 21.13? Luckily last case is solved by the new ConfigurationOf class, as it allows only semantic versioning. But as for method names I suggest the next format: #v0_4_1:. You cannot confuse it with anything else and it recalls real version a bit. Now here is an example of a full version definition:
v0_4_1: spec
   <version: 'v0.4.1'>

   spec
      for: #'common'
      do: [ 
         spec
            baseline: 'QualityAssistant'
            with: [ spec repository: 'github://Uko/QualityAssistant:v0.4.1' ];
            import: 'QualityAssistant' ]
Also you can create pre-releases that will point to a branch like:
pre0_5_0: spec
   <version: 'v0.5.0-pre'>

   spec
      for: #'common'
      do: [ 
         spec
            baseline: 'QualityAssistant'
            with: [ spec repository: 'github://Uko/QualityAssistant:master' ];
            import: 'QualityAssistant' ]
Then you can point stable version to v0.4.1 and development to v0.5.0-pre. When you create a new release just update symbolic pointers and you're done.

Please note that 'v' prefixes are not defined as mandatory anywhere, but they look nice :)

Good luck on versioning your project. Code responsibly!


Big thanks to Dale Henrichs and Thierry Goubier for helping me out with understanding of Metacello and GitFileTree!

No comments:

Post a Comment