Tag Archives: PackageLoader

Building Pharo from PharoKernel in 10 seconds

Hi. Last week I was playing with Pharo kernel images and I thought it could be interesting to document here what we (I am not alone!) were doing. First, the context:

Context of Pharo, PharoCore and PharoKernel

In older versions of Pharo, there were always 2 distributions: Pharo (a.k.a PharoDev) and PharoCore. The latter was a small core with just the basic stuff and very few development tools. It was perfect for deployment purposes for example. Then, on top of such core, we build PharoDev which added several packages for development: OmniBrowser, code completion, Shout, refactorings, etc, etc, etc. Since Pharo 1.4 (and now in Pharo 2.0) we now have only one image which is more similar to what we know as PharoDev. Of course the image can also be shrinked and get the core.

As said, the PharoCore was great for deployment because it was small. However, Pavel Krivanek was not satisfied, and hence started to work on “KernelImage”. The image is now known as PharoKernel. Such image contains a real small core and it is only about 2 MB. The image has to run headless. Can you imagine Seaside running in 4.6 MB? Just try Seaside on PharoKernel.

The first challenge was to be able to really shrink a Pharo image to a Kernel image. This does not happen in one afternoon. Pavel has done *a lot* of work improving the modularization of the system to reduce the dependencies between packages (to be able to correctly unload packages). So PharoKernel is the result of a HUGE work.

The second step was how to take a PharoKernel and to reload and *re initialize* everything so that it works. You know…unloading Morphic (remember PharoKernel is headless) and correctly load back and initialize it is not easy. In summary, it is a really complex process.

So far, PharoKernel was working. But there was something that we didn’t like: we still needed the Compiler in PharoKernel since to load code back we needed the source and therefore the compiler.

FuelPackageLoader arrived

So…Pavel didn’t give up and continue his efforts. Fuel is a general-purpose binary serializer but in addition, we have packages like FuelPackageLoader that let us export classes and packages in a binary way. This is what I used in the post I show you how to export and import Seaside. For more details of Fuel, FuelMetalevel and FuelPackageLoader read this link. The idea is that know we can export in a binary way. So when we are serializing a class, we serialize the class itself with the method dictionary, compiled methods, subclasses, instance variables, etc.  And to import we do not need the Compiler! we just materialize 😉   In addition, the export and import is much faster.

Wanna try it yourself?

The following are the needed steps if you want to try yourself:

1) Create a working directory where you will place all your stuff.

2) Download a Pharo 1.4 image and a PharoKernel 1.4 image and move them to your working directory.

3) Download the scripts to do this experiment. The scripts are in GIT together with all the scripts used by the Pharo Jenkins. The easiest way is to clone the repository:

git clone https://git.gitorious.org/pharo-build/pharo-build.git

4) Move the files (export.st, load.st, initCore.st and load.sh) from /XXX/pharo-build/scripts/pharo/Kernel-2.0/FuelPackageLoader to your working directory.

If you follow the steps correctly you should have something like this:

😉 ls -la
total 93112
drwxr-xr-x  13 mariano  staff       442 Apr 27 23:07 .
drwxr-xr-x@ 17 mariano  staff       578 Apr 27 23:06 ..
-rw-r--r--@  1 mariano  staff  10772559 Apr 27 20:35 Pharo-1.4.changes
-rw-r--r--   1 mariano  staff  15863104 Apr 26 12:05 Pharo-1.4.image
-rw-r--r--@  1 mariano  staff   2488141 Apr 26 12:17 PharoKernel-1.4.changes
-rw-r--r--@  1 mariano  staff   2272532 Apr 26 12:17 PharoKernel-1.4.image
-rw-r--r--@  1 mariano  staff  16235372 Oct 19  2009 PharoV10.sources
-rw-r--r--   1 mariano  staff      4023 Apr 27 20:34 export.st
-rw-r--r--   1 mariano  staff      9215 Apr 27 20:29 initCore.st
-rwxr-xr-x   1 mariano  staff       151 Apr 27 20:29 load.sh
-rw-r--r--   1 mariano  staff      3548 Apr 27 20:29 load.st
drwxr-xr-x  14 mariano  staff       476 Apr 26 19:27 pharo-build

5) Now we take the Pharo image and we export almost all core (except what is already present in PharoKernel) using Fuel. The script first downloads Fuel. Then we use Fuel for both things: export some class variables and fonts, but also the code (packages). If you are lazy you can see the files online. So for example, these lines export fonts and a class variable:

FLSerializer serialize: (TextStyle named: 'Bitmap DejaVu Sans') toFileNamed: 'dejavu.fuel'.
FLSerializer serialize: (UCSTable classPool at: #JISX0208Table) toFileNamed: '#jisX0208Table.fuel'.

And this is how we export packages:

packageNames := 'Ring-Core-Containers
Ring-Core-Kernel' lines.

FileStream forceNewFileNamed: 'ring.fuel' do: [:aStream |
aStream binary.
FLPackageStore new storeOn: aStream packages: packageNames.].

In this example we are exporting two packages (Ring-Core-Containers and Ring-Core-Kernel) with the same stream (‘ring.fuel’ file). If you want to only export one package you can use instead the message #storeOn:packageNamed:.

So its time to take the Pharo image and export everything. To do that we need to run the image from command line and send the export.st file as argument.

/Users/mariano/Pharo/VM/Pharo.app/Contents/MacOS/Pharo /Users/mariano/PhD/Marea/Fuel/PharoKernelExperiments/blog/Pharo-1.4.image export.st

6) Once we have exported, we should have several .fuel files in our working directory. The biggest file is pharo-core.fuel and it is about 5 MB. The export should have taken approx. 10 seconds (considering also the time to download Fuel).

7) As you can see in load.st, the way to import a package with Fuel is:

FileStream readOnlyFileNamed: 'ring.fuel' do: [:aStream |
aStream binary.
FLPackageLoader new loadFrom: aStream contents readStream].

Now we take a PharoKernel image and we load the files.

/Users/mariano/Pharo/VM/Pharo.app/Contents/MacOS/Pharo /Users/mariano/PhD/Marea/Fuel/PharoKernelExperiments/blog/PharoKernel-1.4.image load.st

This step should also take less than 10 seconds. You can notice that now PharoKernel is not 2MB anymore but more about 13MB 😉

8) Once packages has been loaded, we have to correctly initialize the system. FuelPackageLoader has a setting to send class side #initialize or not. For most cases it works. But in other cases (like the case of PharoKernel or any Smalltalk boostrap), the initialize has to be done in a careful order. Therefore, the initialize is done manually in a script called initCore.st. That scripts not only sends the #initialize to classes but also performs all the necessary actions to get back a headfull (not headless) working Pharo image. So the step now is to run:

/Users/mariano/Pharo/VM/Pharo.app/Contents/MacOS/Pharo /Users/mariano/PhD/Marea/Fuel/PharoKernelExperiments/blog/PharoKernel-1.4.image initCore.st

9) Finally!!!! If everything was fine, we should now have a working Pharo image built from a PharoKernel. You can just open PharoKernel-1.4.image and give it a try 🙂

Conclusions

  • It is possible to have a minimal image without compiler and boostrap from there a bigger image.
  • The performance seems quite good so far (of course we still need to add lots of things)
  • FuelMetalevel (the package to serialize and materialize classes) is working really well since it could serialize and materialize almost all classes and traits from Pharo.

Known limitations

For this experiment of exporting and importing packages we are using FuePackageLoader. This is a prototype and we still have lots of missing features. In fact, that’s why Martin is now as a student in the GSoC project 😉  The current limitations are:

  • We are not exporting source code, timestamp, class comments, etc.
  • We are not doing all needed validations nor recompiling in those cases that may be necessary.
  • We are not updating instances if the classes already existed in the image.
  • and more…. (read here for more details).

So, that’s all.  I hope you had fun. See you

Advertisements

Importing and exporting packages with Fuel

Background

Hi guys. One month ago, I was running like crazy preparing my presentations for ESUG 2011. I had 3 talks and also the Fuel presentation in the awards. So…I was thinking about examples and demos to show with Fuel. I finally showed how to serialize a debugger and continue debugging it in another image, how to serialize a graph and store it in a Riak NOSQL database, how to use SandtoneDB with Fuel, etc. But I was not the only one running. Martin Dias, main author of Fuel, who was in the other part of the world, was also coding. He was trying to finish what I will show today: to be able to export and import packages using Fuel. Reality is different from movies. Hence, he could finally make it work, one day after the awards 😦

Since that day, we only focused in Fuel itself and we left the tool there. This week I went to work to Lille and Igor asked me to show him such tool. The tool at that moment was outdated (using an old version of Fuel) and quite buggy. Today, we have something better 🙂

UPDATE: As part of his excellent blog and screencasts, James Robertson has recorded a screencast where he follows the steps of this blog post. I recommend to take a look to it as well. Thanks a lot James!

What is the tool about?

As you may know, Fuel is a plain object graph serializer. No more than that. However, Fuel supports correct serialization of Class, Trait, MethodDictionary, CompiledMethod, BlockClosure, MethodContext, etc. Hence, creating a tool on top of that which allows one to export and import packages, is quite easy. In fact the tools is right now about 220 lines of code.

The tool is called Fuel Package Loader, but its name may change in the future. Basically what it does is to take a PackageInfo, extract the classes and extension methods, create a FLPackage instance and serialize that. During materialization (the import), we need to do extra tasks like registering classes in Smalltalk globals, in categories/organizations, notify about class creation, send class side #initialize, etc. In other words, this tool adds package semantics to a plain serialization.

Installing Fuel and Package Loader

As always, you have two options, pick up the latest bleeding edge build from our Jenkis job, or install with Metacello. For this post, use the Metacello way. The last stable version of Fuel is 1.6. But we needed to fix a couple of things on it in other to make FuelPackageLoader to work. However, Fuel right now is not “stable enough” to release 1.7 (we need to fix few things). Hence, I have created a temporal version 1.6.1 tagged as #development specially for this post. All this is because I want the post to be reproducible. So, pick up a Pharo 1.3 image (sorry, for older Pharo images we need to fix something very stupid but I don’t have time, but for 1.7 it will be fixed), and execute:

Gofer new
squeaksource: 'Fuel';
package: 'ConfigurationOfFuel';
load.
((Smalltalk at: #ConfigurationOfFuel) project version: '1.6.1') load: #('Core' 'PackageLoaderWithTests').

That will load Fuel, FuelPackageLoader and all their tests. Now, one of the best tests we can do for this project is to import and export Seaside web framework. Why? Because it is really big, it has a lot of packages, dependencies between them, a lot of important class side #initialize, etc. So…if we are able to import/export seaside, we should be able to import/export most of the existing projects.

Save this image as ‘Fuel.image’.

Exporting packages

The first basic and naive idea we had when dealing with packages was to map one to one between Monticello packages and Fuel packages. Knowing which packages of Seaside are needed, in which other, and all the dependencies it has, is quite complicated. So…we can use Metacello for that. In the following example, we will install Seaside using Metacello and the regular packages of Monticello. But, we will at the same time, creat an associated Fuel package for each Monticello package. So, execute the following and wait between 10 and 30 minutes (if you are lazy, go directly to the import step where I provide the binaries):

FLMetacelloStore default
capturePackagesDuring: [
Gofer new
squeaksource: 'MetacelloRepository';
package: 'ConfigurationOfSeaside30';
load.
((Smalltalk at: #ConfigurationOfSeaside30) project version: '3.0.6')
load: #('Core' 'Tests' 'Zinc-Seaside' )]
prefixed: 'seaside'.

In this case, we have loaded the group ‘Core’ of Seaside, together with its tests and the Zinc seaside adaptor (just because I like it). Once that finishes, you can execute:

(ZnZincServerAdaptor port: 4242)
codec: GRPharoUtf8Codec new;
start.

And now if you open the internet browser and go to http://localhost:4242/  you should see the nice Seaside welcome page.

Now, pay attention that a directory called ‘FuelMetacello’ was created in the same directory where the .image is. Inside that directory you can see all the generated fuel packages. Each of them are named with the pattern ‘prefix-packageName-fuel’. For example, ‘seaside-Scriptaculous-Core.fuel’, ‘seaside-Seaside-Flow.fuel’, etc. Those would be the equivalents to the Monticello .mcz and we will import them after.

Finally, save this image as ‘FuelSeasideExporter.image’

Importing packages

To import packages, all we need is to read the file and materialize them. But the problem is, in which order? mmmm Metacello used to know that, but not us. Well, this is why during export, we have also written a file that contains the script to import back. The file is called ‘prefix-load-script.txt’ (seaside-load-script.txt in this case) and it is also in the ‘FuelMetacello’ directory. If you open it, you can see its contents:

#('seaside-ConfigurationOfGrease.fuel' 'seaside-ConfigurationOfKomHttpServer.fuel' 'seaside-ConfigurationOfSPort2.fuel' 'seaside-ConfigurationOfSwazoo2.fuel' 'seaside-ConfigurationOfZincHTTPComponents.fuel' 'seaside-ConfigurationOfRefactoringBrowser.fuel' 'seaside-Grease-Core.fuel' 'seaside-Grease-Pharo-Core.fuel' 'seaside-Grease-Tests-Core.fuel' 'seaside-Grease-Tests-Pharo-Core.fuel' 'seaside-Sport.fuel' 'seaside-Swazoo.fuel' 'seaside-Zinc-HTTP.fuel' 'seaside-Zinc-Tests.fuel' 'seaside-Seaside-Core.fuel' 'seaside-Seaside-Pharo-Core.fuel' 'seaside-Seaside-Component.fuel' 'seaside-Seaside-Canvas.fuel' 'seaside-Seaside-Pharo-Canvas.fuel' 'seaside-RSS-Core.fuel' 'seaside-Javascript-Core.fuel' 'seaside-Javascript-Pharo-Core.fuel' 'seaside-Comet-Core.fuel' 'seaside-Prototype-Core.fuel' 'seaside-Scriptaculous-Core.fuel' 'seaside-JQuery-Core.fuel' 'seaside-JQuery-UI.fuel' 'seaside-Seaside-Email.fuel' 'seaside-Seaside-Pharo-Email.fuel' 'seaside-Seaside-HTML5.fuel' 'seaside-Seaside-InternetExplorer.fuel' 'seaside-Seaside-Session.fuel' 'seaside-Seaside-RenderLoop.fuel' 'seaside-Seaside-Tools-Core.fuel' 'seaside-Seaside-Flow.fuel' 'seaside-Seaside-Examples.fuel' 'seaside-RSS-Examples.fuel' 'seaside-Seaside-Widgets.fuel' 'seaside-Seaside-Tools-Web.fuel' 'seaside-Seaside-Pharo-Tools-Web.fuel' 'seaside-Seaside-Environment.fuel' 'seaside-Seaside-Pharo-Environment.fuel' 'seaside-Seaside-Development.fuel' 'seaside-Scriptaculous-Components.fuel' 'seaside-Seaside-Swazoo.fuel' 'seaside-Zinc-Seaside.fuel' 'seaside-Seaside-Adaptors-Swazoo.fuel' 'seaside-Seaside-Tests-Core.fuel' 'seaside-Seaside-Tests-Pharo-Core.fuel' 'seaside-Seaside-Tests-Session.fuel' 'seaside-Seaside-Tests-RenderLoop.fuel' 'seaside-Seaside-Tests-Component.fuel' 'seaside-Seaside-Tests-Canvas.fuel' 'seaside-Seaside-Tests-Environment.fuel' 'seaside-RSS-Tests-Core.fuel' 'seaside-Javascript-Tests-Core.fuel' 'seaside-Javascript-Tests-Pharo-Core.fuel' 'seaside-Seaside-Tests-Email.fuel' 'seaside-Seaside-Tests-Functional.fuel' 'seaside-Seaside-Tests-Pharo-Functional.fuel' 'seaside-Seaside-Tests-Flow.fuel' 'seaside-Seaside-Welcome.fuel' 'seaside-Seaside-Pharo-Welcome.fuel' 'seaside-Prototype-Tests-Core.fuel' 'seaside-Scriptaculous-Tests-Core.fuel' 'seaside-Scriptaculous-Tests-Components.fuel' 'seaside-JQuery-Tests-Core.fuel' 'seaside-JQuery-Tests-UI.fuel' 'seaside-Seaside-Tests-HTML5.fuel' 'seaside-Seaside-Tests-InternetExplorer.fuel' 'seaside-Seaside-Tests-Examples.fuel' 'seaside-Seaside-Tests-Tools-Web.fuel' 'seaside-Seaside-Tests-Development.fuel' 'seaside-Seaside-Tests-UTF8.fuel' 'seaside-Seaside-Tests-Welcome.fuel' 'seaside-DynamicBindings.fuel' 'seaside-KomServices.fuel' 'seaside-KomHttpServer.fuel' 'seaside-Seaside-Tools-OmniBrowser.fuel' 'seaside-Seaside-Pharo-Tools-OmniBrowser.fuel' 'seaside-Seaside-FileSystem.fuel' 'seaside-Seaside-Tests-FileSystem.fuel' 'seaside-Seaside-Pharo-Continuation.fuel' 'seaside-Seaside-Tests-Pharo-Continuation.fuel' 'seaside-Seaside-Pharo-Flow.fuel' 'seaside-Seaside-Pharo-Development.fuel' 'seaside-Seaside-Tests-Pharo-Development.fuel' 'seaside-Seaside-Adaptors-Comanche.fuel' 'seaside-Comet-Pharo-Core.fuel' 'seaside-Comet-Examples.fuel' 'seaside-Comet-Tests-Core.fuel' 'seaside-Seaside-Tests-Adaptors-Comanche.fuel' 'seaside-Grease-Slime.fuel' 'seaside-Grease-Tests-Slime.fuel' 'seaside-Seaside-Slime.fuel' 'seaside-Seaside-Tests-Slime.fuel')
do: [ :packageFileName |
(FileDirectory default directoryNamed: 'FuelMetacello')
oldFileNamed: packageFileName
do: [ :aStream | FLPackageLoader new loadFrom: aStream binary ]]
displayingProgress: [ :packageFileName | 'Fuel importing ', packageFileName ]

So…. if you have followed the previous steps, then just open ‘Fuel.image’ (a new Pharo 1.3 image where Fuel is loaded but seaside is not), paste the previous code into a workspace and execute it. Of course that it depends on the machine, but here (Mac Book Pro i5), it only takes 10 seconds.

If you were a lazy gut who didn’t export the package, ok, here you have a zip of FuelMetacello. Just extract it and let the directory next to the .image.

It seems it was too fast to be working. So, if you don’t believe me, execute again the Zinc code to start a server (if you let the FuelSeasideExporter.image open, take another port) and then go to the browser. You should see, again the welcome page:

Ok, you still don’t believe it? Open the TestRunner, select all the seaside related tests and run it. They will be all green, or at least it should be exactly the same results as when running the tests from the FuelSeasideExporter image (where seaside was loaded with Metacello).

Why we put each package in a separate file? First because it was easier, and second because we wanted to be as much similar as possible with Monticello packages. In addition, this way, you can choose which packages to load. However, that doesn’t mean we are not able to serialize all packages in the same file, say seaside.fuel hahah. That’s possible, and it is easy since you can just download one file and you are done.

What the tool does and does not

As you may imagine the idea is that maybe in the future we can replace Monticello’ mcz with Fuel packages. Of course, we really far from there, but this was the first step. We tool right now:

  • Sends class side #initialize to classes.
  • Add classes to superclass instVar ‘subclasses’.
  • Send notifications that a class was created

What it does not do so far (but we will try to do it in the future) is :

  • Validations: we do not do validations at all. For example, we do not check whether a class we are materializing is already present in the image or if the global name was already taken. So we do not do class migration and things like that. In addition, we don’t validate class pools uniqueness and so on. So…right now the tool only works if you load in a clean image where you know there cannot be problems.
  • Source code is not serialized. That means, when you import and browse the classes, you will see the decompiled source.
  • .changes is not modified at all when loading a package.
  • Traits are partially supported and not really tested (soon will be fixed).

Conclusions

This tool is similar to VisualWorks Parcels, but there is a big difference. Parcels was designed with the idea in mind of managing code. Hence, it is a little bit “coupled” with that. It is difficult to use Parcels as a plain serializer. Fuel is a general-purpose serializer and coupled with nothing. It is an infrastructure where we can then build tools on top of that. FuelPackageLoader is just an example of that. In 220 lines of code we where able to have something more or less working for importing and exporting packages. We can export seaside (96 packages!!!) in 12 seconds and import them in 10. Ok, we still need to add a lot of missing features that will probably have overhead. But so far, the results are promising.

Do not use this tool for production. It is just an experiment and it is really really in development. Just try it for fun. If you want to help us, try importing/exporting your own packages instead of seaside and let us know if you have problems!

Finally, a big clap clap to Martin who has been working a lot in Fuel and the package loader.