Repository: pharo-open-documentation/pharo-wiki Branch: master Commit: 45f56f06b43e Files: 58 Total size: 358.7 KB Directory structure: gitextract_8rwhc7yu/ ├── .github/ │ └── workflows/ │ └── continuous.yml ├── .gitignore ├── CONTRIBUTION.md ├── ExternalProjects/ │ ├── DataStructures/ │ │ └── DataFrame.md │ └── Export/ │ ├── Arff.md │ ├── CSV.md │ ├── ESCP.md │ ├── HTML.md │ ├── JSON.md │ └── XML.md ├── ExternalResources/ │ └── Community.md ├── General/ │ ├── Baselines.md │ ├── CJKCharacter.md │ ├── CJKInputMethod.md │ ├── CodingConventions.md │ ├── CoolSnippets.md │ ├── DeployYourPharoApplication.md │ ├── Exceptions.md │ ├── ExportFormats.md │ ├── Extensions.md │ ├── Files.md │ ├── GithubActions.md │ ├── Glossary.md │ ├── HowToRunPharoFromCommandLine.md │ ├── IcebergOnWindows.md │ ├── ImageFileFormat.md │ ├── Inspector.md │ ├── InterestingsToKnowForBeginners.md │ ├── Layout.md │ ├── MenuBar.md │ ├── MustKnowForBeginners.md │ ├── Playground.md │ ├── Pragmas.md │ ├── Profiling.md │ ├── ProgressBar.md │ ├── Refactorings.md │ ├── SessionsManagement.md │ ├── SettingUpANewProject.md │ ├── Settings.md │ ├── SortingCollections.md │ ├── Traits.md │ ├── Travis.md │ ├── TweakingBigImages.md │ └── Windows10SubsystemForLinux.md ├── LICENSE ├── MANIFEST.md ├── Migration/ │ └── MigrationToPharo7.md ├── PharoProjects/ │ ├── Announcer.md │ ├── Cursor.md │ ├── DynamicVariables.md │ ├── Metalinks.md │ ├── Numbers.md │ ├── OS.md │ ├── ObjectsSerialization.md │ ├── RichText.md │ └── WebBrowser.md ├── PharoVirtualMachine/ │ └── pharo-vm-map.md └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/continuous.yml ================================================ name: Broken links env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the development branch on: push: branches: - 'master' pull_request: types: [assigned, opened, synchronize, reopened] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Defining a new Environment Variable run: | TEMPFILES=$(ls **/*.md) echo "FILES<> $GITHUB_ENV echo $TEMPFILES >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - uses: docker://dkhamsing/awesome_bot:latest with: args: --white-list travis-ci,127.0.0.1,smalltalkhub.com/mc,github.com/MY_USERNAME/MY_PROJET_NAME --allow 403 --allow-dupe --allow-redirect --skip-save-results ${{ env.FILES }} ================================================ FILE: .gitignore ================================================ **/.DS_STORE ================================================ FILE: CONTRIBUTION.md ================================================ # Contribution guidlines * [Suggest new pages](#suggest-new-pages) * [Entries structure](#entries-structure) * [Edit a page](#edit-a-page) * [Add a new page](#add-a-new-page) * [Deleting, Moving or Renaming a page](#deleting-moving-or-renaming-a-page) + [If the content is obsolete](#if-the-content-is-obsolete) + [If we want to move a page](#if-we-want-to-move-a-page) + [If we want to split a page](#if-we-want-to-split-a-page) + [If we want to rename a page](#if-we-want-to-rename-a-page) * [Review content](#review-content) ## Suggest new pages To suggest pages that could fit the wiki, the recommended way is to open an issue containing a summary of the page, the section in which it should be (General, Internal projects of Pharo or External projects of Pharo) and optionaly links to already existing documentation on the projects. On the language and environment part of the wiki we accept to document a part of the system that is already documented outside the wiki at the condition that we reference in a *See also* section those documentation. The goal is to have a centralized information and to provide alternative ways of explaining. If you wish to add the new entry yourself, see section [Add a new page](#add-a-new-page). ## Entries structure The entries structure is free but we still have some conventions. * Documentation on external projects should mostly contains small example, comparaisons between multiple projects, links to official documentations and repositories. We can accept documentation on the external projects in case there is none by the official maintainer. * Long entries should begin with a table of content. We recommand [https://ecotrust-canada.github.io/markdown-toc/](https://ecotrust-canada.github.io/markdown-toc/). * References to external/decentralized documentation should be in a *See also* section at the end of the entry. * Smalltalk code snippets should use *tabulation* for source code indentation. * Smalltalk code snippets should be highlighted using this format:
```Smalltalk
 Code
```
## Edit a page If you see some typo, wrong informations or missing informations you can report it in the issues or you can also edit the document yourself and create a pull request. Creating a PR is as easy as clicking on the Edit (pen) button, updating the document and propose the changes. ![Gif showing how to edit a page](Resources/EditPage.gif) ## Add a new page In order to add a page you can just go into the folder of the section where the page should be and use the "Create new file button". ![Gif showing how to add a new page](Resources/CreatePage.gif) If you need to integrate images in your entry, add them next to the Markdown file with an name following this pattern: `_Image_.extension`. For example for an image illustrating `Iceberg` in a page called `VCS.md`, the image should be named `VCS_Image_IcebergIllustration.png`. Once the page is created you need to add it to the [README.md](README.md) in the right section. > Note: If you have contents to start a page but don't have the time or the knowledge to finish it, it is advised to propose a pull request with the content you have already. Then, one can open issues with the missing sections pointing to the concerned page. An entry in the README can also have a one short sentense explaining the purpose of the page. The pages focus mainly on the latest stable Pharo version. This does not mean we refuse contribution on older or development version of Pharo, but those should be complement of the current stable version. ## Deleting, Moving or Renaming a page **/!\\ We never delete a page. /!\\** This is a rule in this wiki. What is more annoying than a 404 when we look for documentation? Instead we have different strategies depending on why we want to delete a page. ### If the content is obsolete If the content is obsolete, we do not delete the page but we add a note (using `> OBSOLETE : bla`) at the beginning explaining why the guide is obsolete. The guide can be removed from the README. ### If we want to move a page If we want to move a page, we can copy it to the new location and let the old page at the original location with an explanation and a link to the new page. ### If we want to split a page Sometimes a page is too big and would win to be splited. In that case the content can be extracted in multiple pages but the original page should stay and contains an index of the new pages. ### If we want to rename a page If we want to rename a page, we can copy it with the new name and let the old page at the original location with an explanation and a link to the new page. ## Review content It is really helpful to get reviews of the wiki's content. This can be achieved in multiple ways: - Review a Pull Request. Comments, approvals and changes request on pull request will help the intergation of new contents. - Open issues on existing entries. - Create a Pull Request to propose enhancements to an existing entry. ================================================ FILE: ExternalProjects/DataStructures/DataFrame.md ================================================ # DataFrame ================================================ FILE: ExternalProjects/Export/Arff.md ================================================ # Arff support in Pharo To be done, talk about the [Arff parser/generator](https://github.com/juliendelplanque/Arff) ================================================ FILE: ExternalProjects/Export/CSV.md ================================================ # CSV support in Pharo Currently, Pharo provides one main framework to handle the [CSV format](https://fr.wikipedia.org/wiki/Comma-separated_values): NeoCSV. ## NeoCSV NeoJSON is actually maintained by Sven Van Caekenberghe on [github](https://github.com/svenvc/NeoCSV). This section shows some quick examples but there is a great [documentation made by Sven](https://github.com/svenvc/docs/blob/master/neo/neo-csv-paper.md) and a chapter in [Enterprise Pharo (chapter 7)](http://books.pharo.org/enterprise-pharo/). > NeoCSV provides utilities to map Smalltalk objects to CSV lines, this page does not cover this topic to avoid duplication with the great documentation available in [Sven's documentation](https://github.com/svenvc/docs/blob/master/neo/neo-csv-paper.md) and [Enterprise Pharo (chapter 7)](http://books.pharo.org/enterprise-pharo/). Check it out if you need to use such feature! ### Install To install NeoCSV, simply execute the following code snippet in a playground: ```Smalltalk Metacello new repository: 'github://svenvc/NeoCSV/repository'; baseline: 'NeoCSV'; load ``` ### Parse CSV - From `String`: ```Smalltalk str := 'id, name 0, Julien 1, Cyril 2, Guillaume '. str readStreamDo: [ :s | (NeoCSVReader on: s) skipHeader; addIntegerField; addField; upToEnd ] ``` - From `Stream`: ```Smalltalk stream := 'id, name 0, Julien 1, Cyril 2, Guillaume ' readStream. (NeoCSVReader on: stream) skipHeader; addIntegerField; addField; upToEnd ``` - Read from CSV file: ```Smalltalk '/path/to/file.csv' asFileReference readStreamDo: [ :readStream | (NeoCSVReader on: readStream) skipHeader; addIntegerField; addField; upToEnd ] ``` ### Generate CSV Let `data` be defined as: ```Smalltalk data := #( (0 'Julien') (1 'Cyril') (2 'Guillaume')). ``` - To generate a CSV `String`: ```Smalltalk String streamContents: [ :writeStream | (NeoCSVWriter on: writeStream) writeHeader: #(id name); nextPutAll: data ] ``` - To generate CSV on a `Stream`: ```Smalltalk (NeoCSVWriter on: writeStream) writeHeader: #(id name); nextPutAll: data ``` - To write a CSV file: ```Smalltalk '/path/to/file.csv' asFileReference writeStreamDo: [ :writeStream | (NeoCSVWriter on: writeStream) writeHeader: #(id name); nextPutAll: data ] ``` ================================================ FILE: ExternalProjects/Export/ESCP.md ================================================ # ESCP support in Pharo To be done, talk about the [ESCP parser/generator](https://github.com/juliendelplanque/ESCP) ================================================ FILE: ExternalProjects/Export/HTML.md ================================================ # HTML support in Pharo To be done, talk about HTML parser(s) - XMLParser-HTML - Soup ? ================================================ FILE: ExternalProjects/Export/JSON.md ================================================ # JSON support in Pharo Currently, Pharo provides two main frameworks to handle the [JSON format](https://en.wikipedia.org/wiki/JSON). This page briefly present these two frameworks and expose their differences to help users to choose the one fitting their needs. - [STONJSON](#stonjson) * [Parse JSON](#parse-json) * [Generate JSON](#generate-json) - [NeoJSON](#neojson) * [Install](#install) * [Parse JSON](#parse-json-1) * [Generate JSON](#generate-json-1) - [STONJSON v.s. NeoJSON](#stonjson-vs-neojson) - [JSON Schema](#json-schema) ## STONJSON STONJSON is the built-in JSON parser available in default Pharo images. It is part of the STON package and its development takes place in Pharo's [github repository](https://github.com/pharo-project/pharo). ### Parse JSON - From `String`: ```Smalltalk STONJSON fromString: '{ "foo" : 42.0 }' "a Dictionary('foo'->42.0 )" ``` - From `Stream`: ```Smalltalk readStream := '{ "foo" : 42.0 }' readStream. STONJSON fromStream: readStream "a Dictionary('foo'->42.0 )" ``` - Read from JSON file: ```Smalltalk '/path/to/file.json' asFileReference readStreamDo: [ :readStream | STONJSON fromStream: readStream ] "a Dictionary('foo'->42.0 )" ``` ### Generate JSON - Let `jsonObject` be defined as: ```Smalltalk jsonObject := Dictionary new at: 'foo' put: 42.0; yourself. ``` - To generate a JSON `String`: ```Smalltalk STONJSON toString: jsonObject "'{""foo"":42.0}'" ``` - To generate JSON on a `Stream`: ```Smalltalk STONJSON put: jsonObject onStream: writeStream ``` - To write a JSON file: ```Smalltalk '/path/to/file.json' asFileReference writeStreamDo: [ :writeStream | STONJSON put: jsonObject onStream: writeStream ] ``` To pretty print JSON, either use `STONJSON>>#toStringPretty:` or `STONJSON>>#put:onStreamPretty:`. ## NeoJSON NeoJSON is actually maintained by Sven Van Caekenberghe on [github](https://github.com/svenvc/NeoJSON). This section shows some quick examples but there is a great [documentation made by Sven](https://github.com/svenvc/docs/blob/master/neo/neo-json-paper.md) and a chapter in [Enterprise Pharo (chapter 8)](http://books.pharo.org/enterprise-pharo/). ### Install To install NeoJSON, simply execute the following code snippet in a playground: ``` Metacello new repository: 'github://svenvc/NeoJSON/repository'; baseline: 'NeoJSON'; load ``` ### Parse JSON - From `String`: ```Smalltalk NeoJSONReader fromString: '{ "foo" : 42.0 }' "a Dictionary('foo'->42.0 )" ``` - From `Stream`: ```Smalltalk readStream := '{ "foo" : 42.0 }' readStream. (NeoJSONReader on: readStream) next ``` - Read from JSON file: ```Smalltalk '/path/to/file.json' asFileReference readStreamDo: [ :readStream | (NeoJSONReader on: readStream) next ] "a Dictionary('foo'->42.0 )" ``` ### Generate JSON Let `jsonObject` be defined as: ```Smalltalk jsonObject := Dictionary new at: 'foo' put: 42.0; yourself. ``` - To generate a JSON `String`: ```Smalltalk NeoJSONWriter toString: jsonObject "'{""foo"":42.0}'" ``` Pretty: ```Smalltalk NeoJSONWriter toStringPretty: jsonObject ``` - To generate JSON on a `Stream`: ```Smalltalk (NeoJSONWriter on: writeStream) nextPut: jsonObject ``` Pretty: ```Smalltalk (NeoJSONWriter on: writeStream) prettyPrint: true; nextPut: jsonObject. ``` - To write a JSON file: ```Smalltalk '/path/to/file.json' asFileReference writeStreamDo: [ :writeStream | (NeoJSONWriter on: writeStream) nextPut: jsonObject ] ``` Pretty: ```Smalltalk '/path/to/file.json' asFileReference writeStreamDo: [ :writeStream | (NeoJSONWriter on: writeStream) prettyPrint: true; nextPut: jsonObject ] ``` ## STONJSON v.s. NeoJSON |Property |STONJSON |NeoJSON | |------------------------------------------------|--------------------|----------------------| |Parse JSON | :white_check_mark: | :white_check_mark: | |Generate JSON | :white_check_mark: | :white_check_mark: | |Pretty-printing | :white_check_mark: | :white_check_mark: | |Built-in | :white_check_mark: | :x: | |Facilities to map JSON objects to Pharo objects | :x: | :white_check_mark: | |Facilities to query JSON objects | :x: | :white_check_mark: * | > *See [this post](http://forum.world.st/How-to-query-a-JSON-tp4948175p4948177.html) from Sven on the mailing list to know how to use this feature implemented in `NeoJSONObject`. ## JSON Schema JSON Schema allows to describes the structure of JSON objects. It is similar to [XML Schema](https://en.wikipedia.org/wiki/XML_Schema_(W3C)) but for JSON. A yet incomplete (but working in its scope) implementation of JSON Schema is provided by Zweidenker [on github](https://github.com/zweidenker/JSONSchema). ================================================ FILE: ExternalProjects/Export/XML.md ================================================ # XML support in Pharo To be done, talk about XML parsers, XML model, XPath implementation, etc... ================================================ FILE: ExternalResources/Community.md ================================================ # Community This page lists links to external resources created by members of Pharo (and sometimes other smalltalks) community. Links are ranked in the lexicographic order which means that the position of a link in the list is not representative of the quality of the content. This page exists for the sake of discoverability of Pharo community. **We do not take any responsibility for content published in these external resources as we have no control on it.** - [https://astares.blogspot.com](https://astares.blogspot.com) - Blog related to Pharo in general. - [https://blog.sebastiansastre.co](https://blog.sebastiansastre.co) - Blog related to Pharo in general. - [https://clementbera.wordpress.com](https://clementbera.wordpress.com) - Blog related to the Pharo virtual machine and its development. - [https://endormitoire.wordpress.com/2016/10/03/freewill-in-progress-4/](https://endormitoire.wordpress.com/2016/10/03/freewill-in-progress-4/) - Blog related to Pharo in general. - [https://fuhrmanator.github.io](https://fuhrmanator.github.io) - Blog related to Moose and Pharo in general but not only. - [https://masteringobjects.wordpress.com](https://masteringobjects.wordpress.com) - Blog related to Pharo development. - [https://medium.com/@i.oleks](https://medium.com/@i.oleks) - Blog related to Pharo, AI and machine learning. - [https://medium.com/concerning-pharo](https://medium.com/concerning-pharo) - Blog related to Pharo in general, multiple authors involved. - [https://medium.com/@juliendelplanque](https://medium.com/@juliendelplanque) - Blog related to Pharo in general. - [http://www.mirandabanda.org/cogblog/](http://www.mirandabanda.org/cogblog/) - Blog for open-smalltalk-vm. - [https://thiscontext.com](https://thiscontext.com) - Blog related to caffeine, a smalltalk vm in Javascript and other smalltalk topics. - [https://80738163270632.blogspot.com/] - Blog related to Pharo in general. ================================================ FILE: General/Baselines.md ================================================ # Baselines 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](#baselines) - [How to define Baselines](#how-to-define-baselines) - [Define packages forming your project](#define-packages-forming-your-project) - [Define external dependencies](#define-external-dependencies) - [To other remote git projects](#to-other-remote-git-projects) - [Depend on a subset of a git project](#depend-on-a-subset-of-a-git-project) - [Depends on the same project with different groups](#depends-on-the-same-project-with-different-groups) - [To a local git project](#to-a-local-git-project) - [To smalltalkhub projects](#to-smalltalkhub-projects) - [Groups](#groups) - [The default group](#the-default-group) - [Pre/post load actions](#prepost-load-actions) - [Loads different packages depending on the Pharo version](#loads-different-packages-depending-on-the-pharo-version) - [Define custom attributes](#define-custom-attributes) - [Loading types](#loading-types) - [Linear loading](#linear-loading) - [Atomic loading](#atomic-loading) - [Full example](#full-example) - [How to load a git project using its baseline](#how-to-load-a-git-project-using-its-baseline) - [From the playground](#from-the-playground) - [Project managed with Git](#project-managed-with-git) - [Project from github/gitlab/bitbucket](#project-from-githubgitlabbitbucket) - [Project from local repository](#project-from-local-repository) - [Project managed with Smalltalkhub](#project-managed-with-smalltalkhub) - [Project without repository](#project-without-repository) - [Loading groups](#loading-groups) - [Conflict, Upgrade and Downgrade resolution](#conflict-upgrade-and-downgrade-resolution) - [Manage warnings](#manage-warnings) - [From Iceberg](#from-iceberg) - [Other features](#other-features) - [Metacello lock feature](#metacello-lock-feature) - [Metacello get feature](#metacello-get-feature) - [Metacello fetch feature](#metacello-fetch-feature) - [Metacello record feature](#metacello-record-feature) - [Metacello listing feature](#metacello-listing-feature) - [See also](#see-also) ## How to define 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: ```Smalltalk 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. ```Smalltalk baseline: spec 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 `` 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: ```Smalltalk projectClass ^ MetacelloCypressBaselineProject ``` Or, if the project should be loadable in Pharo < 6.1, use this version: ```Smalltalk 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. ### Define packages forming your project To define the packages of the project, send the message `#package:` to the spec with the name of the package as argument. ```Smalltalk baseline: spec 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](#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. ```Smalltalk baseline: spec 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](#loads-different-packages-depending-on-the-pharo-version)*. ### Define external dependencies 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 other remote git projects To depend on a git project, you can use the method `#baseline:with:`. ```Smalltalk 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 load `BaselineOfMaterialDesignLite`) - `{prefix}`: This is host-specific: - `github` for github - `bitbucket` for bitbucket - `gitlab` for gitlab - `git` for others (and {url} is thus mandatory) - `{url}`: Base url to the git host. Mandatory when prefix `git` 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: ```Smalltalk spec baseline: 'MaterialDesignLite' with: [ spec repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src'] ``` ##### Depend on a subset of a git project 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: ```Smalltalk 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: ```Smalltalk baseline: spec 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') ] ]. ``` ```Smalltalk materialDesignLite: spec spec baseline: 'MaterialDesignLite' with: [ spec loads: #('Extensions'); repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src' ] ``` ##### Depends on the same project with different groups 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. ```Smalltalk 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: ```Smalltalk baseline: spec 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') ] ]. ``` ```Smalltalk materialDesignLite: spec spec baseline: 'MaterialDesignLite' with: [ spec repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src' ]; project: 'MaterialDesignLiteExtensions' copyFrom: 'MaterialDesignLite' with: [ spec loads: #('Extensions') ] ``` #### To a local git project 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: ```Smalltalk spec baseline: 'MaterialDesignLite' with: [ spec repository: 'gitlocal://full/path/to/repository' ] ``` #### To smalltalkhub projects Depending on a [Smalltalkhub](http://smalltalkhub.com) project is done via `#project:with`. ```Smalltalk 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: ```Smalltalk 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: ```Smalltalk 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. ```Smalltalk baseline: spec 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') ] ]. ``` ```Smalltalk magritte3: spec spec project: 'Magritte3' with: [ spec className: #ConfigurationOfMagritte3; versionString: #'release3'; loads: #('Seaside'); repository: 'http://smalltalkhub.com/mc/Magritte/Magritte3/main/' ] ``` ### Groups 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: ```Smalltalk baseline: spec 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](#loading-groups). #### The default group 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: ```Smalltalk baseline: spec 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') ``` ### Pre/post load actions 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: ```Smalltalk baseline: spec 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') ] ] ``` ```Smalltalk preload: loader package: packageSpec Transcript crLog: 'The fetch was finished. Now let''s load the project' ``` ```Smalltalk postload: loader package: packageSpec Transcript crLog: 'Project loaded!' ``` ### Loads different packages depending on the Pharo version 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: ```Smalltalk baseline: spec 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](#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: ```Smalltalk baseline: spec 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' ] ] ``` ### Define custom attributes 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: ```Smalltalk customProjectAttributes Smalltalk os isMacOS ifTrue: [ ^ #(#MacOS) ]. Smalltalk os isUnix ifTrue: [ ^ #(#Unix) ]. Smalltalk os isWindows ifTrue: [ ^ #(#Windows) ] ``` Then they can be used in the baseline. ```Smalltalk baseline: spec 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') ] ] ``` ```Smalltalk osSubprocess: spec spec baseline: 'OSSubprocess' with: [ spec repository: 'github://pharo-contributions/OSSubprocess:v1.0.1/repository' ] ``` ```Smalltalk processWrapper: spec spec configuration: 'ProcessWrapper' with: [ spec versionString: '1.2'; repository: 'http://smalltalkhub.com/mc/hernan/ProcessWrapper/main' ] ``` ### Loading types Baselines support different loading types. The loading types define how Metacello loads the project. #### Linear loading By default, a baseline uses linear loading, which means packages are loaded one by one with their requirements loaded before them. #### Atomic loading 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`: ```Smalltalk project ^ super project loadType: #atomic; yourself ``` ### Full example Here is an example with all previous features illustrated: ```Smalltalk "baseline" baseline: spec 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') ] ] ``` ```Smalltalk projectClass ^ MetacelloCypressBaselineProject ``` ```Smalltalk "dependencies" materialDesignLite: spec spec baseline: 'MaterialDesignLite' with: [ spec repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src' ]; project: 'MaterialDesignLiteExtensions' copyFrom: 'MaterialDesignLite' with: [ spec loads: #('Extensions') ] ``` ```Smalltalk "dependencies" magritte3: spec spec project: 'Magritte3' with: [ spec className: #ConfigurationOfMagritte3; versionString: #'release3'; loads: #('Seaside'); repository: 'http://smalltalkhub.com/mc/Magritte/Magritte3/main/' ] ``` ```Smalltalk "dependencies" osSubprocess: spec spec baseline: 'OSSubprocess' with: [ spec repository: 'github://pharo-contributions/OSSubprocess:v1.0.1/repository' ] ``` ```Smalltalk "dependencies" processWrapper: spec spec configuration: 'ProcessWrapper' with: [ spec versionString: '1.2'; repository: 'http://smalltalkhub.com/mc/hernan/ProcessWrapper/main' ] ``` ```Smalltalk "accessing" customProjectAttributes Smalltalk os isMacOS ifTrue: [ ^ #(#MacOS) ]. Smalltalk os isUnix ifTrue: [ ^ #(#Unix) ]. Smalltalk os isWindows ifTrue: [ ^ #(#Windows) ] ``` ```Smalltalk "actions" preload: loader package: packageSpec Transcript crLog: 'The fetch was finished. Now let''s load the project' ``` ```Smalltalk "actions" postload: loader package: packageSpec Transcript crLog: 'Project loaded!' ``` ```Smalltalk "accessing" project ^ super project loadType: #atomic; yourself ``` ## How to load a git project using its baseline 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. ### From the playground The first way to load a project is to create a *Metacello* request programmatically and to execute it. This request looks like this: ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src'; baseline: 'MaterialDesignLite'; load ``` Note the three steps: 1. Create a new Metacello request 2. Configure it (declare the repository of the project, specify the version, the baseline, optional options...) 3. 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. #### Project managed with Git To load a project from git you need to execute an expression like this: ```Smalltalk 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 the `MaterialDesignLite` project, the baseline name is `MaterialDesignLite` to load the project with `BaselineOfMaterialDesignLite`. The repository parameter is a string that can take different forms depending on if the project is local or hosted remotely. ##### Project from github/gitlab/bitbucket 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 github - `bitbucket` for bitbucket - `gitlab` 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. ```Smalltalk Metacello new repository: 'github://DuneSt/MaterialDesignLite:v1.x.x/src'; baseline: 'MaterialDesignLite'; load ``` Example: loading from a private Gitlab host. ```Smalltalk 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: ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src'; baseline: 'MaterialDesignLite'; load ``` ##### Project from local repository 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 project - `tonel` 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: ```Smalltalk Metacello new repository: 'tonel://C:\Users\Cyril\GitRepositories\Native-Browser\src'; baseline: 'NativeBrowser'; load ``` #### Project managed with Smalltalkhub To load a project from Smalltalkhub you need to execute an expression like this: ```Smalltalk 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 project - `repositoryName`: Name of the repository on SmalltalkHub - `configurationName` is the name of the configuration to load. For example to load the `MaterialDesignLite` project, the baseline name is `MaterialDesignLite` to load the project with `BaselineOfMaterialDesignLite`. - `{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: ```Smalltalk Metacello new repository: 'http://smalltalkhub.com/mc/Seaside/Seaside31/main'; configuration: 'Seaside3'; version: #stable; load ``` You can also use `Metacello>>smalltalkhubUser:project:`: ```Smalltalk Metacello new smalltalkhubUser: 'Seaside' project: 'Seaside31'; configuration: 'Seaside3'; version: #stable; load ``` #### Project without repository 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. ```Smalltalk Metacello new baseline: #TinyBlog; load ``` #### Loading groups 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: ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src'; baseline: 'MaterialDesignLite'; load: 'Extensions' ``` ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src'; baseline: 'MaterialDesignLite'; load: #('Extensions' 'Widgets') ``` #### Conflict, Upgrade and Downgrade resolution 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: ```Smalltalk 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. ```Smalltalk 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: ```Smalltalk 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 ``` #### Manage warnings 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: ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src'; baseline: 'MaterialDesignLite'; onWarning: [ :ex | Transcript crShow: ex ]; load ``` ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'master' path: 'src'; baseline: 'MaterialDesignLite'; onWarningLog; load ``` ### From Iceberg 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. ![Interface of Iceberg to load a project](Baselines_Image_LoadBaselineViaIceberg.png?raw=true "Interface of Iceberg to load a project") ## Other features This section covers other features of baselines and Metacello. ### Metacello lock feature 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: ```Smalltalk 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: ```Smalltalk 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: ```Smalltalk 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. ```Smalltalk 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 get feature 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: ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src'; baseline: 'MaterialDesignLite'; get ``` ### Metacello fetch feature The fetch command downloads all of the packages without loading them. This includes the packages of the project but also their dependencies. Example: ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src'; baseline: 'MaterialDesignLite'; fetch ``` You can also specify a group: ```Smalltalk 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: ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src'; baseline: 'MaterialDesignLite'; ignoreImage; fetch ``` ### Metacello record feature 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: ```Smalltalk Metacello new githubUser: 'DuneSt' project: 'MaterialDesignLite' commitish: 'v1.x.x' path: 'src'; baseline: 'MaterialDesignLite'; record ``` ### Metacello listing feature The `list` command lists projects in the image or Metacello registry: ```Smalltalk 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. ## See also - Deep into pharo: [Chapter 9, Managing Projects with Metacello](http://files.pharo.org/books-pdfs/deep-into-pharo/2013-DeepIntoPharo-EN.pdf) - [Metacello documentation](https://github.com/Metacello/metacello/tree/master/docs) ================================================ FILE: General/CJKCharacter.md ================================================ # Using CJK (Chinese, Japanese, and Korean) characters Pharo's default font settings for code (Source Code Pro) and UI (Source Sans Pro) are unable to handle [CJK characters](https://en.wikipedia.org/wiki/CJK_characters), this page explains how to display them properly in Pharo. ![Preview string for chinese characters under Source Code Pro](CJKCharacter_Screenshot_1.png) Operating systems may come with fonts that support those characters but they are usually not monospaced which is not good for displaying code. If one need to display CJK characters in Pharo, it is recommended to install a monospace font which also covers CJK characters. Some of them are [`Noto Sans Mono CJK`](https://www.google.com/get/noto/) , `Microsoft YaHei Mono`, [`WenQuanYi Zen Hei Mono`](http://wenq.org). Note one must enable the option `Update fonts at startup` first and restart Pharo to make the font selection dialog below show all available fonts: 1. Select menu `Pharo` -> `Settings` to show the `Settings Browser`. 2. Enable `Appearance` -> `Use Free type...` -> `Update fonts at startup`, this can be disabled later after one changes the fonts. ![Preview string for chinese characters under Microsoft YaHei Mono](CJKCharacter_Screenshot_2.png) Note that on Windows 10, one may need to manually copy font files to `C:\Windows\Fonts\`. Here is a code sample: ```Smalltalk StandardFonts defaultFont: (LogicalFont familyName: 'Noto Sans CJK SC' pointSize: 9); codeFont: (LogicalFont familyName: 'Microsoft YaHei Mono' pointSize: 10); listFont: (LogicalFont familyName: 'Noto Sans CJK SC' pointSize: 9); menuFont: (LogicalFont familyName: 'Noto Sans CJK SC' pointSize: 9); buttonFont: (LogicalFont familyName: 'Noto Sans CJK SC' pointSize: 9); windowTitleFont: (LogicalFont familyName: 'Noto Sans CJK SC' pointSize: 10); balloonFont: (LogicalFont familyName: 'Noto Sans CJK SC' pointSize: 8); haloFont: (LogicalFont familyName: 'Noto Sans CJK SC' pointSize: 8). ``` ## Related links: https://www.samadhiweb.com/blog/2019.02.22.windows.multilingual.html http://forum.world.st/Japanese-Fonts-td4928056.html http://forum.world.st/im-using-pharo-7-with-linux-env-but-cannot-input-korean-td5095342.html ================================================ FILE: General/CJKInputMethod.md ================================================ # Using CJK (Chinese, Japanese, and Korean) Input Method It's possible to use CJK input method in Pharo. Tested under Pharo8 on macOS, Linux and Windows. ## Linux On linux, you need add `-compositioninput` in VM args and make sure you are using Fcitx5. ================================================ FILE: General/CodingConventions.md ================================================ # Coding conventions Pharo is full of implicit coding conventions. This page aims to document all those conventions to help newcomers write Pharo code as per the community's idioms. ## Tests conventions Pharo has some conventions when it comes to tests. This section covers those. **Package name** It is preferred to store tests in a separate package rather than in the package of the tested classes. The name of this package should be `NameOfOriginalPackage-Tests`. For example, if one wants to add tests to `Material-Design-Lite-Core`, the tests should be in a package called `Material-Design-Lite-Core-Tests`. **Class name** Tests classes (subclasses of `TestCase`) should end with the suffix `Test` (and not `Tests` because a test class is a test case). In the case of unit tests, the name of the class should be `MyClassTest`. For example, if one wants to add unit tests of `MDLButton`, the test class will be named `MDLButtonTest`. **Method name** Test method names in Pharo need to be prefixed with `test`. This is the way the test framework recognizes them as test methods. In case of a unit test on a method of the tested class, the name should be prefixed by `test` followed by the name of the method to test. For example: - The unit test for `#click` will be named `#testClick` - The unit test for `#clickElement:` will be named `#testClickElement` - The unit test for `#clickElement:withShiftPressed:` will be named `#testClickElementWithShiftPressed` By doing that, a new icon next to your actual method name will appear in the system browser. This icon allows one to launch the unit test of the method via a click. ================================================ FILE: General/CoolSnippets.md ================================================ # Cool Snippets This file contains snippets of code that can be useful sometimes. - [Download a file with a progress bar](#download-a-file-with-a-progress-bar) - [Bench and profile a project from the tests](#bench-and-profile-a-project-from-the-tests) - [Automatic transformation of Pharo's methods source code](#automatic-transformation-of-pharo-s-methods-source-code) - [Browse all available icons](#browse-all-available-icons) - [Rename programatically methods](#rename-programatically-methods) - [Get all senders/implementors of a selector](#get-all-sendersimplementors-of-a-selector) - [Find dependencies on a package](#find-dependencies-on-a-package) - [Embed an image (picture) into Pharo](#embed-an-image-picture-into-pharo) ## Download a file with a progress bar ```Smalltalk | outputFileName url | outputFileName := './pharoLogo.png'. outputFileName asFileReference ensureDelete. url := 'http://files.pharo.org/media/logo/logo-flat.png' asZnUrl. [ :bar | bar title: 'Download: ' , url asString , ' to ' , outputFileName. [ ZnClient new url: url; signalProgress: true; downloadTo: outputFileName ] on: HTTPProgress do: [ :progress | progress isEmpty ifFalse: [ bar current: progress percentage ]. progress resume ] ] asJob run ``` ## Bench and profile a project from the tests ```Smalltalk packageSelectionBlock := [ :e | e name beginsWith: 'Spec' ]. testSuite := TestSuite new. ((RPackageOrganizer default packages select: packageSelectionBlock) flatCollect: #classes) select: [ :e | e inheritsFrom: TestCase ] thenDo: [ :e | e addToSuiteFromSelectors: testSuite ]. "Bench the test suite" [ testSuite run ] bench. "Profile the test suite" TimeProfiler spyOn: [ testSuite run ] ``` ## Automatic transformation of Pharo's methods source code During software maintenance, being able to transform source code automatically can be handy. It can be done easily in Pharo using the built-in code rewriting engine. For example, let's say that we want to replace the occurences of `#symbolToReplace` symbol with occurences of `#replacedSymbol` in the following method: ```Smalltalk ExampleCodeTransformation>>#methodToTransform 1 + 1 = 2 ifTrue: [ ^ #symbolToReplace ]. ^ #symbolToReplace , #symbolNotToReplace ``` It can be done easily using the following code snippet: ```Smalltalk compiledMethod := ExampleCodeTransformation >> #methodToTransform. ast := compiledMethod parseTree. "Here we select only nodes holding the target symbol and we replace it with a node representing `#replacedSymbol`." ast allChildren select: [ :n | n class = RBLiteralValueNode and: [ n value = #symbolToReplace ] ] thenDo: [ :node | rewriter := RBParseTreeRewriter new replaceTree: node withTree: (RBLiteralValueNode value: #replacedSymbol); yourself. (rewriter executeTree: ast) ifTrue: [ node replaceWith: rewriter tree ] ]. "Serialize the new AST as Pharo source code." rewrittenSourceCode := (BIConfigurableFormatter format: ast). "Recompile the method with transformed source code." ExampleCodeTransformation compile: rewrittenSourceCode ``` ## Browse all available icons The following code snippet opens an inspector in which the 'Icons' tab allows to browse all icons available in the image. ``` Smalltalk ui icons inspect ``` To get a specific icon, use `#iconNamed:` method as follow: ``` Smalltalk ui icons iconNamed: #arrowUp ``` ## Rename methods programatically In this section we will present a code snippet to rename all methods of a class by replacing one substring with another. First, we find all methods of a given class with names that contain a substring `A`, then we rename those methods to replace `A` with another substring `B`. ```Smalltalk "Class in which we want to rename the methods" class := EpApplyPreviewerTest. "Substring to replace" from := 'With'. "Substring to use" to := 'Without'. class methods select: [ :method | method selector includesSubstring: from ] thenDo: [ :method | | permutationMap | "We want to keep the arguments in the same order" permutationMap := method numArgs = 0 ifTrue: [ #() ] ifFalse: [ (1 to: method numArgs) asArray ]. (RBRenameMethodRefactoring renameMethod: method selector in: class to: (method selector copyReplaceAll: from with: to) permutation: permutationMap) execute ] ``` > Be careful, this will also rename the senders of those methods and if you have two methods with the same name in the image, it might badly rename some of them. Use this only on methods with unique names. ## Get all senders/implementors of a selector The selector of a method is kind of the equivalent of the signature of a method or function in other programming languages. However, since Pharo is dynamically typed, this selector is only the name of the method, without the parameters. For example, the selector of the method below is `#name:` ``` Example>>name: aString name := aString ``` For a given `CompiledMethod` in the system, its selector is accessible via `#selector` message. ``` (Object>>#yourself) selector. "#yourself" ``` ### Senders To get all the senders of a selector accross the image, simply call `#senders` on the selector: ``` #yourself senders ``` ### Implementors To get all `CompiledMethod`s implementing a method having a selector, simply call `#implementors` on the selector: ``` #yourself implementors ``` ## Find dependencies on a package In Pharo it is easy to find the dependencies of one package through the `Dependency Analyzer`, but there is no tool to browse the dependencies *on* a single package. It is possible to do it programatically via this snippet: ```Smalltalk report := DADependencyChecker new computeImageDependencies. "This might take some time but it will run in background" report knownDependantsOf: 'Epicea' "Replace Epicia by the name of your package." ``` ## Embed an image (picture) into Pharo If you want to add an image into Pharo, you will need to import it ```Smalltalk ImageReadWriter formFromFileNamed: 'test.png' ``` and you may store it into the image for further reuse. This is achieved by encoding the image into a base 64 String. Then, the String can be stored in a method. ```Smalltalk (Base64MimeConverter mimeEncode: 'test.png' asFileReference binaryReadStream) contents ``` Let's say we store the image base64 String in `Foo>>#image`. To materialize a Form from this image, you can do: ```Smalltalk Form fromBinaryStream: Foo image base64Decoded asByteArray readStream ``` Here is a shortcut available since Pharo 8.0: ```Smalltalk Form fromBase64String: Foo image ``` ================================================ FILE: General/DeployYourPharoApplication.md ================================================ # How to deploy a Pharo application *This guide has for purpose to help developpers deploying a Pharo application. If something is missing to you, do not hesitate to open an issue.* **This guide is first written for Pharo 7. Some parts will not work in Pharo 6 and earlier.** - [How to deploy a Pharo application](#how-to-deploy-a-pharo-application) * [Cruiser: A tool for app deployment?](#cruiser) * [Clean your image before deployment](#clean-your-image-before-deployment) * [Sources obfuscation](#sources-obfuscation) * [Change the logo and window title of the application](#change-the-logo-and-window-title-of-the-application) * [Deploy a Seaside application with Nginx](#deploy-a-seaside-application-with-nginx) ## Cruiser Cruiser is a tool to package Pharo applications. The idea is to quickly convert an application in a development environment to a production one. [https://github.com/VincentBlondeau/Cruiser](https://github.com/VincentBlondeau/Cruiser) ## Clean your image before deployment In this section we explain various ways to clean an image for deployment. All steps are not necessary and the benefits are given in each subsection. ### Clean up the system A first step is to launch a cleanup of the image. Pharo already contains a system to cleanup divers part of the system by calling the `#cleanUp(:)` method of every class implementing it. It can be done with this snippet: ```Smalltalk Smalltalk cleanUp: true except: {} confirming: false ``` The first parameter should be a boolean. If it's value is `true` it will launch a more aggresive cleanup and this will destroy resources, change sets, etc. The second parameter allows the developper to exclude some classes of the cleanup by passing an array containing all the classes that should not execute their cleanup. ### Ensure logins are removed from the image During the deployment of an application it might be a good idea to remove all login informations from the image. In order to do that you can execute this script: ```Smalltalk | store | "Remove all repositories from Monticello VCS" MCRepositoryGroup allSubInstancesDo: [ :group | group repositories do: [ :repo | group removeRepository: repo ] ]. "Remove projects and credentials from Git VCS (Tested in Pharo 7)" IceRepository registry removeAll. store := IceCredentialStore current. store allCredentials do: [ :each | each removeFrom: store ] ``` With this step done all the credentials should be removed from the image if you do not use an external project from Pharo that store some credentials. ### Close all windows While deploying an application you might want to ensure every Morph is closed (to remove the Pharo welcome window for example). This can directly be done via the command: ```Smalltalk World closeAllWindowsDiscardingChanges ``` ### Disable deprecation warning Pharo contains a deprecation warning system to help the developers to keep their code up to date but in productions those warnings should be removed so that the user will not be bothered by them in case a deprecated method is called. You can do that via: ```Smalltalk Deprecation raiseWarning: false; showWarning: false ``` ### Enable the run without sources and changes files It is possible to run a Pharo image without `.changes` or `.sources` files. To do that you can execute: ```Smalltalk NoChangesLog install. NoPharoFilesOpener install ``` Note that FFI needs the sources to work properly. In Pharo 7 a pluggin to remove this need was introduced. If your application uses FFI calls you will need to execute: ```Smalltalk FFICompilerPlugin install ``` ### Disable creation of STDio files on Windows On Windows there is two way to launch a Pharo image. The first uses the `Pharo.exe` executable and the second `PharoConsole.exe`. The difference between those two methods is that the former will be launched without a terminal and will create files to write STDio outputs. The second will open a terminal with Pharo to manage STDio. It is possible to disable the writing of the STDio files on Windows with this code: ```Smalltalk Stdio useNullStreams ``` ### Remove tests and example packages In production examples and tests packages are useless. You can find bellow a script to unload them. > Be careful, this script is based on a heuristic. If the naming convensions or dependencies are not well managed this might break your application. Please test you application after using such a script. ```Smalltalk | substrings | substrings := #('Test' 'Example' 'Mock' 'Demo'). RPackageOrganizer default packages select: [ :p | substrings anySatisfy: [ :aString | p name includesSubstring: aString ] ] thenDo: #removeFromSystem ``` ### Disable Monticello cache Monticello uses by default a cache when it is used. It is possible to disable this cache with this script: ```Smalltalk MCCacheRepository uniqueInstance disable ``` ### Disable Epicea Pharo contains a record system to recover changes: *Epicea*. This system log a lot of events on disk. It is possible to disable Epicea like this: ```Smalltalk EpMonitor reset ``` ### Garbage collect As last step of the deployment I would recommand the user to launch a full garbage collection of the system to clean all dead instances from the image: ```Smalltalk 5 timesRepeat: [ Smalltalk garbageCollect ] ``` ### Delete pharo-local folder Pharo works with a local folder containing caches. This folder is called `pharo-local` and is next to the image since Pharo 7. It is recommanded to delete this folder, or to not include this folder in the distribution. ## Sources obfuscation When deploying a commercial application on the customer's infrastructure, we might want to protect our code. This section will give some advice on how to protect the source code of your application. > WARNING: What will be describe here is not a perfect solution. We are aware it will still have some weakness, but at least it will make it much harder to get the source code of the application. This section works with the previous section aswell. We recommand to: * Run without the sources file * Run without the changes file * Disable Epicea * Remove the loggins of your image ### Force omission of startup preferences At launch Pharo try to load preferences. Since the user can execute Smalltalk code via those preferences, we recommand to disable the preference mechanism with this code: ```Smalltalk PharoCommandLineHandler forcePreferencesOmission: true ``` ### Protect command lines by a password Since the user can intereact with Pharo via command line, we recommand to protect command lines with a password. The password is not be saved in clear. It is hashed using pepper and iterations. If you wish to define *application* command lines who does not need a password protection, implement the method `requireDeploymentPassword` on the class side of your command lines to return `false`. ```Smalltalk "Enable password protection" CommandLinePasswordManager protectCommandLinesByPasswordWith: 'PharoPassword'. "You can also customize the pepper and number of iterations for the hashing of the password." CommandLinePasswordManager protectCommandLinesByPasswordWith: 'PharoPassword' pepper: 'SomePepper' numberOfHashIterations: 10. "Remove password protection" CommandLinePasswordManager removePasswordProtection. ``` ### Remove the decompiler Without the sources and changes files the user does not have the source code shipped with the application, but he still has the byte code of the application. To make it harder to exploit if he succeed to get a part of the byte code, you can unload the decompiler from the image with the piece of code: ```Smalltalk RPackageOrganizer default packages select: [ :p | p name includesSubstring: 'Flashback' ] thenDo: #removeFromSystem ``` ### Disable global shortcuts If the customer has access to the Pharo image it is recommanded to disable global shortcuts that can help to open tools. It is doable this way: ```Smalltalk (KMRepository default globalCategories flatCollect: [ :each | each allEntries keymaps ]) do: #disable ``` ### Disable WorldMenu, Menubar and Taskbar To block the access to the tools it is possible to disable the world menu, the taskbar and the menu bar from Pharo with this piece of code: ```Smalltalk "Disable world menu and menubar" WorldState desktopMenuPragmaKeyword: ''. "Disable Menubar only" MenubarMorph showMenubar: false. "Disable WorldMenu only" PasteUpMorph shouldShowWorldMenu: false. "Disable taskbar" TaskbarMorph showTaskbar: false. ``` ### Disable progress bar interrupt button If you show progresses in your application via a progress bar, the user can clic on the red cross to stop the action and open a debugger. It is possible to remove this possibility executing: ```Smalltalk JobProgressBarMorph isInterruptable: false ``` ### Disable process interruption button In Pharo, it is possible to disable the current process via the `cmd + .` shortcut. This feature can be disabled: ```Smalltalk UserInterruptHandler cmdDotEnabled: false ``` ### Disable drag and drop in Pharo It is possible to drop files in Pharo to install code in it. It is recommanded to disable this feature to block users to inject code into the application. Since there is no setting to do that, you can recompile a part of the Pharo image to block it this way: ```Smalltalk WorldMorph allowDropFiles: false ``` ### Disable Morph's Halos To remove the option to open Halos around the Morphs of Pharo you can execute: ```Smalltalk Morph halosEnabled: false ``` ### Disable Debugger #### Pharo 8 Since Pharo 8, Pharo comes with new debuggers you can choose from. When deploying a private application you don't want the user to get a bug and access to the code through it. For that you can use the `NoDebugger`: ```Smalltalk NoDebugger beCurrent ``` #### Pharo < 8 or specific debuggers In case you are using Pharo < 8 or you want a special handling of the bug you can create your own debugger. For that you need to create a object and implement a class side method called `#openOn:withFullView:andNotification:`. For example for a `NoDebugger`: ```Smalltalk NoDebugger class>>openOn: aDebugSession withFullView: aBool andNotification: aString "Do nothing" ``` For a debugger exporting in a file the error: ```Smalltalk openOn: aDebugSession withFullView: aBool andNotification: aString 'errors.log' ensureCreateFile; writeStreamDo: [ :s | s setToEnd; << 'ERROR. Here is the stack:'; << OSPlatform current lineEnding. aDebugSession interruptedContext shortDebugStackOn: s ] ``` You then need to register the debugger: ```Smalltalk Smalltalk tools register: NoDebugger as: #debugger ``` ### Open a morph in full screen When deploying the application, while there is no real headless mode in Pharo or when the application contains a user interface embedded we can open a Morph in full screen to ensure the user cannot access to content behind it. To do that we can create a Spec application (in case of headless application it can just contains a logo and a `quit` button) and open it in full screen with this command: ```Smalltalk MyPresenter new openWorldWithSpec ``` ## Change the logo and window title of the application In Windows it is possible to change the title and the logo of the Pharo application. To do that you can execute: ```Smalltalk DisplayScreen setWindowTitle: 'MyApplication'. DisplayScreen hostWindowIcon: (FileLocator imageDirectory / 'testLogo.ico') asFileReference fullName. ``` ## Deploy a Seaside application This section will cover a specific kind of deployment: the deployment of a web application with Seaside. ### Prepare the image It is recommanded to prepare the image for deployment. This section will cover some possible configurations you can apply to your image. > Note: This is section only contains suggestions and it might be missing interesting options. If you have an idea of missing section do not hesitate to open an issue. #### Set server mode When deploying an application as a server, it is possible to change a setting to slow down the rendering cycle of the image and increase performances. It can be done like this: ```Smalltalk WorldState serverMode: true ``` #### Unregister applications When building your seaside image it is possible that some Seaside application got registered (demos for examples). It is possible to unregister them like this: ```Smalltalk applicationsToUnregister := WAAdmin defaultDispatcher handlers keys \ #('myApplication' 'files' 'myHandler' 'config'). applicationsToUnregister do: [ :appName | WAAdmin defaultDispatcher unregister: (WAAdmin defaultDispatcher handlerAt: appName) ] ``` #### Set default application Applications must have a name in Seaside and the name should be in the URL. However, it is possible to define a default application which will be selected in case no application name is in the URL. ```Smalltalk WAAdmin defaultDispatcher defaultName: 'myApplicationName' ``` #### Disable development toolbar In case you load a version of Seaside containing development tools, your application will come with a development toolbar. It is possible to remove it by executing: ```Smalltalk WAAdmin applicationDefaults removeParent: WADevelopmentConfiguration instance ]. ``` Then you need to initialize you application. #### Protect configuration by a password In case you keep the configuration application available to the user, you might want to protect it by a password. It wan be done like this: ```Smalltalk | application | application := WAAdmin defaultDispatcher handlerAt: 'config'. application configuration addParent: WAAuthConfiguration instance. application preferenceAt: #login put: 'admin'; preferenceAt: #passwordHash put: (GRPlatform current secureHashFor: 'seasideiscool'). application addFilter: WAAuthenticationFilter new ``` ### Deploy with a Ngnix server This section will cover the configuration needed to deploy an image with a nginx server. #### Launch the image In general I (the author of this decumentation) use a Jenkins to generate my image and then I have a script to deploy the image. My script looks like this: ```bash #!/usr/bin/env bash set -vx # Where to deploy the application export DEST=/srv/app/mdl # Pharo version for the deployment export PHARO=61 # Location of the zip containing the archive export ARCHIVE_LOCATION=/var/lib/jenkins/workspace/MaterialDesignLite/PHARO/$PHARO/VERSION/master/ # To launch an image I uses a screen, else the image will close with my ssh session. Here I ensure the session used is closed. screen -S mdl -X quit # Remove the old version rm -rf $DEST/{MaterialDesignLite.*,pharo*,*.sources,Pharo*} # Copy the application cp ${ARCHIVE_LOCATION}MaterialDesignLite.zip $DEST/ cd $DEST # Get a VM and unzip the application wget --quiet -O - get.pharo.org/vm${PHARO} | bash unzip MaterialDesignLite.zip # Launch the application and initialize it on a free and open port ./pharo MaterialDesignLite.image eval --save " MDLDemo initializeAs: 'mdl'. WAAdmin defaultDispatcher defaultName: 'mdl'. ZnZincServerAdaptor startOn: 8088" #Launch the image in a screen screen -Sdm mdl ./pharo MaterialDesignLite.image --no-quit ``` #### HTTP deployment Now that the image is launched we need to dispatch it via nginx. In order to do that over HTTP I uses this configuration: ```nginx server { listen 80; #Since it's a web application, listen port 80 listen [::]:80; #Same for IPv6 server_name {Domaine name. Example mysite.com}; #Set your domaine name server_tokens off; #Do not display nginx version for security reasons access_log /var/log/nginx/{log name}.log; #loging error_log /var/log/nginx/{error log name}.log; #error loging root {Path to the root. For example /srv/myApp/}; location = / { try_files $uri $uri/index.html @proxy; } #use a proxy for your seaside application location @proxy { rewrite ^ /{Seaside application name. For example TelescopeDemo}$1 last; } location /{Seaside application name. For example TelescopeDemo} { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://127.0.0.1:{Port on which your ZincServerAdaptor listen. For example 8080}; } # This is for the file libraries location /files { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://127.0.0.1:{Port on which your ZincServerAdaptor listen. For example 8080}; } } ``` #### HTTPS deployment The previous option works but is not secured. It is recommanded to generate a TLS certificate (with `let's encrypt` via `certbot` for example) and to deploy over HTTPS. Then the configuration will look like this: ```nginx server { listen 80; #Since it's a web application, listen port 80 listen [::]:80; #Same for IPv6 server_name {Domaine name. Example mysite.com}; #Set your domaine name server_tokens off; #Do not display nginx version for security reasons return 301 https://$server_name$request_uri; #Redirect HTTP -> HTTPS } server { listen 443 ssl http2; #Listen to port 443 for HTTPS listen [::]:443 ssl http2; #Same for IPv6 server_name {Domaine name. Example mysite.com}; #Set your domaine name server_tokens off; #Do not display nginx version for security reasons ssl_certificate {path to your public certificate key}.pem; ssl_certificate_key {path to your private certificate key}.pem; access_log /var/log/nginx/{log name}.log; #loging error_log /var/log/nginx/{error log name}.log; #error loging root {Path to the root. For example /srv/myApp/}; location = / { try_files $uri $uri/index.html @proxy; } #use a proxy for your seaside application location @proxy { rewrite ^ /{Seaside application name. For example TelescopeDemo}$1 last; } location /{Seaside application name. For example TelescopeDemo} { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://127.0.0.1:{Port on which your ZincServerAdaptor listen. For example 8080}; } # This is for the file libraries location /files { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://127.0.0.1:{Port on which your ZincServerAdaptor listen. For example 8080}; } } ``` ================================================ FILE: General/Exceptions.md ================================================ # Exceptions All applications have to deal with exceptional situations. Arithmetic errors may occur (such as division by zero), unexpected situations may arise (file not found), or resources may be exhausted (network down, disk full, etc.). In languages like C, the solution is to have operations that fail return a special error code; this means that client code must check the return value of each operation, and take special action to handle errors. This leads to brittle code. Modern programming languages, including Pharo, offer a dedicated exception-handling mechanism that greatly simplifies the way in which exceptional situations are signaled and handled. This mechanism is the **Exception** mechanism. - [Exceptions](#exceptions) * [Basic Uses](#basic-uses) + [Signaling an exception](#signaling-an-exception) + [Handling exceptions](#handling-exceptions) + [Creating your own exceptions](#creating-your-own-exceptions) + [Ensure](#ensure) + [IfCurtailed](#ifcurtailed) * [Advanced Uses](#advanced-uses) + [Return](#return) + [Retry](#retry) + [Resume](#resume) + [Pass](#pass) + [Outer](#outer) + [Resignal](#resignal) * [A deeper look at the exception mechanism](#a-deeper-look-at-the-exception-mechanism) * [Credits](#credits) ## Basic Uses ### Signalling an exception **Syntax:** `anException signal` or: `anException signal: 'message with additional information'` Imagine you have a method `potentiallyProblematicMethod`, that sometimes sets a variable to a bad value. When this happens (line 3), you want to signal this problem by **signalling** an exception (in this example an `Error` exception). ```Smalltalk potentiallyProblematicMethod self doThings. (myVariable = badValue) ifTrue: [ ^ Error new signal: 'Oh my god no!']. ^ myVariable. ``` If you execute this code, the following window opens: ![Image](./Exceptions_Image_ExamplePreDebugger.png) And if you click on "Debug", a debugger opens to help you understand why your application signalled an exception. ![Image](./Exceptions_Image_ExampleDebugger.png) ### Handling exceptions **Syntax:** `protectedBlock on: anExceptionClass do: handlerBlock`. Handling multiple exception classes: `protectedBlock on: anExceptionClass, anotherExceptionClass, yetAnotherExceptionClass... do: handlerBlock`. Accessing the exception in the handler block: `protectedBlock on: anExceptionClass do: [:exceptionToHandle| yourCode]`. As seen in the previous paragraph, if an exception is signalled and nothing else is done, a debugger opens. It is useful when developing, but in the end you probably want your application to catch these exceptions and handle them itself if possible (for example by displaying a nice pop-up informing the user of the problem). This is done with exception **handlers**. In Pharo, an exception handler is of the form: `protectedBlock on: anExceptionClass do: handlerBlock`. Here's what happens (in English): *"Evaluate `protectedBlock`. If it signals an exception of the class `anExceptionClass`, catch this exception and evaluate `handlerBlock` instead"*. To continue the previous example, let's say that `potentiallyProblematicMethod` is called by `solidMethod`. Now `solidMethod` is solid, so it does not want debuggers to open when it is executed. Instead, if an exception is signalled by `potentiallyProblematicMethod`, it will handle it, display a pop-up informing the user, and return `42` instead of the result of `potentiallyProblematicMethod`. ```Smalltalk solidMethod ^ [self potentiallyProblematicMethod] on: Error do: [UIManager default inform: 'There was an issue, but it`s ok'. 42.] ``` Now when `solidMethod` is executed, it either returns the result of `self potentiallyProblematicMethod` if no `Error` is signalled, or it displays a pop-up and returns 42 if an `Error` is signalled. ![Image](./Exceptions_Image_InformationPopUp.png) When an exception is signalled, it walks the call stack (from the current method to its caller, then the caller of the caller, etc...) until it finds a handler that can handle it. If no handler is found, a debugger is opened. ### Creating your own exceptions `Error` is not the only kind of exception that can be signalled, there are a lot more! The following picture shows a few examples. ![Image](./Exceptions_Image_ExceptionSample.png) An issue with writing handlers for generic exception types like `Error` is that these handlers will not only handle errors your application signals (which is fine), but they will also handle errors signalled by the libraries you use, or code that your application uses but that is not a part of it. This is potentially dangerous, as your application may not be doing the right thing for these errors. To prevent this, you can define your own types of exceptions by creating subclasses of the `Exception` class, and signal and catch these. To continue the example, we could create an `Exception` subclass named `MyVariableHasABadValue`, and update `potentiallyProblematicMethod` and `solidMethod` as follows: ```Smalltalk potentiallyProblematicMethod self doThings. (myVariable = badValue) ifTrue: [ ^ MyVariableHasABadValue new signal: 'Oh my god no!']. ^ myVariable. ``` ```Smalltalk solidMethod ^ [self potentiallyProblematicMethod] on: MyVariableHasABadValue do: [UIManager default inform: 'There was an issue, but it`s ok'. 42.] ``` ### Ensure **Syntax:** `aBlock ensure: ensuredBlock`. The `ensure:` message can be sent to a block to make sure that, even if the block fails (e.g., signals an exception) the argument block will still be executed. This is useful to guarantee that `ensuredBlock` will be executed no matter what, for example to close files or clean up in case `aBlock` signals an exception, before the exception is transmitted. ### IfCurtailed **Syntax:** `aBlock ifCurtailed: curtailBlock` The `ifCurtailed:` message is similar to `ensure:`, but its argument block is evaluated **only** if the receiver block signals an exception. ## Advanced Uses After catching an exception in a handler, there are a number of advanced things you can do with the exception. ### Return **Syntax:** `protectedBlock on: anExceptionClass do: [:ex | aValue ]` or: `protectedBlock on: anExceptionClass do: [:ex | ex return ]` or: `protectedBlock on: anExceptionClass do: [:ex | ex return: aValue ]` **Synopsis:** This is the standard behavior of handler blocks. If an exception is caught, this handler returns an alternative value for the protected block (the execution continues as if the protected block has returned that value). Here is a picture of the context stack (a.k.a. call stack) explaining this operation. ![Image](Exceptions_Image_WhatCanYouDoWithAnExceptionYouCaught_fragment1_return.png) ### Retry **Syntax:** `protectedBlock on: anExceptionClass do: [:ex | ex retry ]` or: `protectedBlock on: anExceptionClass do: [:ex | ex retryUsing: aBlock ]` **Synopsis:** Re-execute the protected block (if using `#retry`), or replace the protected block with a different block and then re-execute it (if using `#retryUsing:`). Here is a picture of the context stack (a.k.a. call stack) explaining this operation. ![Image](Exceptions_Image_WhatCanYouDoWithAnExceptionYouCaught_fragment2_retry.png) ### Resume **Syntax:** `protectedBlock on: anExceptionClass do: [:ex | ex resume ]` or: `protectedBlock on: anExceptionClass do: [:ex | ex resume: aValue ]` **Synopsis:** Resume the execution of the protected block just after the call to #signal. The call to #signal is worth `nil` (if using `#resume`) or `aValue` (if using `#resume: aValue`). Here is a picture of the context stack (a.k.a. call stack) explaining this operation. ![Image](Exceptions_Image_WhatCanYouDoWithAnExceptionYouCaught_fragment3_resume.png) ### Pass **Syntax:** `protectedBlock on: anExceptionClass do: [:ex | ex pass ]` **Synopsis:** Skip the current handler and keep looking for another handler to handle this exception. ### Outer **Syntax:** `protectedBlock on: anExceptionClass do: [:ex | ex outer ]` **Synopsis:** Like `ex pass`, but if another handler later calls `#resume` on the exception, the execution resumes where `#outer` was called instead of where the exception was signalled. If multiple `#outer` are executed on the same exception, they will form a sort of stack of positions to go back to, in case `#resume` is called multiple times on the exception. Here is a picture of the context stack (a.k.a. call stack) explaining this operation. ![Image](Exceptions_Image_WhatCanYouDoWithAnExceptionYouCaught_fragment4_outer.png) ### Resignal **Syntax:** `protectedBlock on: anExceptionClass do: [:ex | ex resignalAs: anotherException ]` **Synopsis:** Signals `anotherException` as if it had been signalled at the same place the original exception (`ex`) was signalled. This can be used to catch generic exceptions and re-signal them as application-specific exceptions if they meet some criteria. ## A deeper look at the exception mechanism The following image gives a graphical view of what happens when an exception is signalled, and the different situations that can arise. ![Image](Exceptions_Image_WhatHappensWhenAnExceptionIsSignalled.png) ## Credits Parts of this page are based on the **Handling Exceptions** chapter of the [Deep Into Pharo book](http://files.pharo.org/books-pdfs/deep-into-pharo/2013-DeepIntoPharo-EN.pdf). Pictures by Thomas Dupriez. ================================================ FILE: General/ExportFormats.md ================================================ # Source Code Export formats Pharo stores its source code in memory (by default) or in a snapshot of the memory when we save a Pharo image. This makes it complicated to manage source code in the long term or to collaborate with others. In order to be able to save some source code in readable files, Pharo includes some textual export formats. This page will explain the main export formats used to save code in VCS such as git. This page will present formats from the most recommended for new projects to the less recommended. - [Comparisons](#comparisons) * [Overview](#overview) * [Supported Pharo versions](#supported-pharo-versions) - [Tonel](#tonel) * [Tonel Pros and Cons](#tonel-pros-and-cons) * [Tonel supported Pharo versions](#tonel-supported-pharo-versions) * [Tonel versions](#tonel-versions) - [FileTree metadata less](#filetree-metadata-less) * [FileTree metadata less Pros and Cons](#filetree-metadata-less-pros-and-cons) * [FileTree metadata less supported Pharo versions](#filetree-metadata-less-supported-pharo-versions) - [FileTree metadata full](#filetree-metadata-full) * [FileTree metadata full Pros and Cons](#filetree-metadata-full-pros-and-cons) * [FileTree metadata full supported Pharo versions](#filetree-metadata-full-supported-pharo-versions) ## Comparisons ### Overview || Tonel | FileTree metadata less | FileTree metadata full | | ------------- | ------------- |------------- |------------- | | Work out of the box since Pharo | 6.1 | 4 | 3 | | Older version supported after update | 4 | 4 | 3 | | Export granularity | Class | Method | Method | | Works well on windows | :white_check_mark: | :x: | :x: | | Easy to merge two branches | :white_check_mark: | :white_check_mark: | :x: | | Takes a reasonable space on file system | :white_check_mark: | :x: | :x: | | Works with Iceberg | :white_check_mark: | :white_check_mark: | :x: | | Works with Monticello | :white_check_mark: | :white_check_mark: | :white_check_mark: | ### Supported Pharo versions | Pharo Version | FileTree metadata full | FileTree metadata less | Tonel | | ------------- | ------------- |------------- |------------- | 3 | :white_check_mark: | :x: | :x: | 4 | :white_check_mark: | :white_check_mark: | Update Metacello + Install Tonel | 5 | :white_check_mark: | :white_check_mark: | Update Metacello + Install Tonel | 6.0 | :white_check_mark: | :white_check_mark: | Update Metacello + Install Tonel | 6.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | 7 | :white_check_mark: | :white_check_mark: | :white_check_mark: | 8 | :white_check_mark: | :white_check_mark: | :white_check_mark: | ## Tonel Tonel ([https://github.com/pharo-vcs/tonel](https://github.com/pharo-vcs/tonel)) is an export format introduced in Pharo 6.1. It creates one file by class or extended class and works well on Windows (compared to file tree). ### Tonel Pros and Cons Pros - Works well on Windows - Not much space lost because of small files - Export readable files - No problem during merge because of metadata Cons - Works out of the box since Pharo 6.1. - Since the format is more recent than the others, some edges cases might cause trouble. (It rarely happens) ### Tonel supported Pharo versions Tonel works out of the box in Pharo 6.1 and higher. But it is possible to make it work in Pharo 4 to 6.0 by updating Metacello and installing Tonel. ```Smalltalk Metacello new baseline: 'Metacello'; repository: 'github://Metacello/metacello:pharo-6.1_dev/repository'; get. Metacello new baseline: 'Metacello'; repository: 'github://Metacello/metacello:pharo-6.1_dev/repository'; onConflict: [:ex | ex allow]; load. Metacello new repository: 'github://pharo-vcs/tonel'; baseline: 'Tonel'; load. ``` ### Tonel versions Tonel got multiple versions over the years, each tweaking the export format: - version 1.0: original tonel export format - version 2.0: this version ensure that in the metadatas the keys are symbols and the values are strings. This change happened in order to be compatible with Gemstone. Note that this format was never used as a default export format of Pharo - version 3.0: this version has the changes of the 2.0 but it also adds the properties `package` and `tag` to replace the `category` property for class definitions. This is to remove same ambiguity in the class definitions. The `category` property is kept by default for backward compatibility, but the TonelWriter can be configured to not export this property. This format can also be improved to export more metadata. For example, it is planned to export a property `deprecatedAliases` to manage some class deprecations in the future. If you want to change your export format and convert all the files of a repository at once to avoid to have multiple PR with format changes you can use the following script. Take a Pharo 12 or Pharo 13 images and run the following script. It will convert all the loaded packages in your image so you may load extra packages to prepare such operation. ```st | projectName repository | projectName := 'ProjectNameInIceberg'. repository := IceRepository repositories detect: [ :repo | repo name = projectName ]. repository workingCopy packages do: [ :pkg | IceLibgitTonelWriter forInternalStoreFileOut: pkg latestVersion mcVersion on: repository ] ``` **NOTE: you must ensure all your project packages are loaded in the image (check in the Repository | Packages window, and right click load any that aren't).** Once you have run this script, use the "Repository, Project | Extra | Open in native file browser" menu option to open an OS terminal and then run: ``` git commit -a -m "Update tonel formal to V3" git push ``` Since Pharo 12, it is also possible to indicate in a Tonel project which version of Tonel to use to export some code. The file to update is the `.properties` file that is in the source folder and it should look like this: ``` { #format : #tonel, #version: #'1.0' //could be 2.0 or 3.0 } ``` This is useful for example if a project has contributors on P11 using v1 format and contributors in P12 using v3 format since P11 is unable to export Tonel v3 format. ## FileTree metadata less FileTree ([https://github.com/dalehenrich/filetree](https://github.com/dalehenrich/filetree)) is the first export format that was integrated in the goal to use Pharo with Git. The first version had a lot of metadata (see section Filetree metadata full), the second was a new version with metadata less format. This format exports one file per method. This can cause trouble with Windows because Windows has a maximum path and file name length of 255 characters. This limit can easily be reached with FileTree. Another problem of the FileTree format is that most Pharo's methods are short and creating one file by method waste a lot of space because the physical representation of files on a drive has a minimal size (4 KiB on NTFS file systems) that is reached by most of Pharo's method. Its advantage compared to FileTree metadata full is that there is no problem during the merge of the source code thanks to the absence of metadata. ### FileTree metadata less Pros and Cons Pros - Easy to browse the history of a method - Present since Pharo 4 out of the box - No problem during merge because of metadata Cons - Cause trouble on Windows - Waste space on the file system ### FileTree metadata less supported Pharo versions FileTree metadata less is supported out of the box since Pharo 4.0 and cannot easily be used in older Pharo versions. ## FileTree metadata full FileTree metadata full ([https://github.com/dalehenrich/filetree](https://github.com/dalehenrich/filetree) )is the ancestor of FileTree metadata less and works in the same way but also export a lot of metadata relating to the commits such has a commit message (different from the one of the VCS), the timestamp of the method compilation, etc. This simplifies the tooling because the tools can get some information from the metadata instead of the VCS but it also creates a lot of trouble while merging two branches using this format. Merges can be eased via this project: [https://github.com/ThierryGoubier/GitFileTree-MergeDriver](https://github.com/ThierryGoubier/GitFileTree-MergeDriver) This format can be used via Monticello but not via Iceberg. Iceberg will ignore the format and export in FileTree metadata less. ### FileTree metadata full Pros and Cons Pros - Easy to browse the history of a method - Present in most Pharo versions out of the box Cons - Cause trouble on Windows - Waste space on the file system - Hard to merge two branches because of metadata - Does not work with Iceberg, the default git tool since Pharo 7 ### FileTree metadata full supported Pharo versions This format works out of the box in most Pharo versions. ================================================ FILE: General/Extensions.md ================================================ # Extensions - [Extensions](#extensions) - [Use of Extension methods](#use-of-extension-methods) - [Add a new extension method](#add-a-new-extension-method) - [Define an extension method in versions prior to Pharo 7](#define-an-extension-method-in-versions-prior-to-pharo-7) - [Define an extension method since Pharo 7](#define-an-extension-method-since-pharo-7) - [Define an extension method programmatically](#define-an-extension-method-programmatically) - [Find methods currently extending a class](#find-methods-currently-extending-a-class) - [In versions prior to Pharo 7](#in-versions-prior-to-pharo-7) - [Since Pharo 7](#since-pharo-7) - [Find extensions programmatically](#find-extensions-programmatically) - [Package loading order is important](#package-loading-order-is-important) Pharo includes a system of extension methods. This feature allows the developer to add behavior (but not state) to existing objects in Pharo. > Note that it is only possible to add methods (behavior) as extension and not variables (state) There is no syntactic difference between calling an extension method and calling a method declared in the class. The main difference between methods and extension methods is that extension methods are stored in a different package than the class they are implemented on. ## Use of Extension methods Extension methods are useful when we want to use some behavior on an object, but it does not make sense to package it directly with the object. For example we might want to ask to a model object for information related to its display in a GUI, but to keep layers as cohesive as possible, we do not want display information stored in the code package of our application. In this case, we can add those methods to the model object but package those methods in a GUI package. Another case might be when you want to enhance the behavior of a package that is simply not your own. You could add the methods to the class in the different package, but committing those changes to source control is not practical (i.e., you don't want to make a branch of the package, and doing a pull request would take a long time, etc.). With extension methods, the changes you make to the foreign package are stored in your own package, so they belong to you. ## Add a new extension method Internally, an extension method is a method whose protocol has a pattern `*MyPackageExtendingMyClass`. Here is more detailed information about adding an extension method via Pharo's tooling. ### Define an extension method in versions prior to Pharo 7 In the Pharo versions based on Nautilus system browser, extension methods are created when we categorize methods on a protocol beginning by a `*`. ![Add an extension method via Nautilus 1](Extensions_Image_NautilusAddExtension1.png?raw=true "Add an extension method via Nautilus 1") ![Add an extension method via Nautilus 2](Extensions_Image_NautilusAddExtension2.png?raw=true "Add an extension method via Nautilus 2") ### Define an extension method since Pharo 7 Since Pharo 7 the default system browser is Calypso. To add an extension method in Calypso you need to use the "Move to package" menu entry. ![Add an extension method via Calypso](Extensions_Image_CalypsoAddExtension.png?raw=true "Add an extension method via Calypso") If you already have extensions in this package, you can also select the package in the list of extending packages in the protocol pane before adding a method. ### Define an extension method programmatically ```Smalltalk ExistingClass compile: 'myMethod ^ true' classified: '*MyPackage' ``` ## Find methods currently extending a class There are two ways to find which methods are extensions where they are located: - Browse a class and check the protocols pane. (See examples below) - Look at a package and check the bottom of the class pane. The last classes will appear in grey and will be the classes extended in that package. ### In versions prior to Pharo 7 ![See an extension method via Nautilus](Extensions_Image_NautilusSeeExtensions.png?raw=true "See an extension method via Nautilus") ### Since Pharo 7 ![See an extension method via Calypso](Extensions_Image_CalypsoSeeExtensions.png?raw=true "See an extension method via Calypso") ### Find extensions programmatically To find extension methods programmatically you can do something like: ```Smalltalk (Number methods select: #isExtension) groupedBy: #package ``` > **TODO: Add example of refactoring to an extension?** ## Package loading order is important One thing developers should take into account when using extensions is to have clean dependencies between its packages. A package extending classes from another package should be loaded *after* this package. For example, if my package `MyProject-GUI` extends `MyProject-Model`, `MyProject-Model` should always be loaded *before* `MyProject-GUI` is. For more details, see the dependencies sections in the [baseline guide](Baselines.md). ================================================ FILE: General/Files.md ================================================ # Files TODO: explain how to manipulate files, paths, file systems, etc... ================================================ FILE: General/GithubActions.md ================================================ # Setting up your continuous integration via GitHub Actions Previously, the Pharo community was heavily relying on Travis CI for the continuous integration of projects hosted on GitHub. But Travis becoming pay-to-use service, the community created some tooling to manage the CI via GitHub Actions. This guide will get you through the process to set up your own integration. > Note: In order to do anything for the CI of your project, you will need a `Baseline` to manage its dependencies. If it is not the case yet, you can check the [guide on baselines](Baselines.md). - [Setting up your continuous integration via Github Actions](#setting-up-your-continuous-integration-via-github-actions) * [Simple case: Run tests on master branch](#simple-case-run-tests-on-the-master-branch) * [SmalltalkCI options](#smalltalkci-options) + [Load spec options](#load-spec-options) + [Testing options](#testing-options) + [Code Coverage](#code-coverage) + [Using custom scripts](#using-custom-scripts) * [Testing multiple versions of Pharo at once](#testing-multiple-versions-of-pharo-at-once) * [Testing on multiple OS](#testing-on-multiple-os) * [Manage the workflow target (branchs, PR, ...)](#manage-the-workflow-target-branchs-pr-) + [Target branches](#target-branches) + [Target pull requests](#target-pull-requests) + [Target releases](#target-releases) + [Target scheduled workflow](#target-scheduled-workflow) + [Other targets](#other-targets) * [Managing multiple workflows and SmalltalkCI configurations](#managing-multiple-workflows-and-smalltalkci-configurations) * [Continuous releases](#continuous-releases) * [Save releases artifacts](#save-releases-artifacts) * [Depending on resources of your repository with GitBridge](#depending-on-the-resources-of-your-repository-with-gitbridge) * [Add your build artifacts to PharoLauncher](#add-your-build-artifacts-to-pharolauncher) * [External ressources](#external-ressources) ## Simple case: Run tests on the master branch Let's start simple! This section will explain how to set up a workflow to launch the tests of the project on every commit done on the master branch. To load our Pharo project in the CI and execute the tests, we'll use [SmalltalkCI](https://github.com/hpi-swa/smalltalkCI). This project needs a configuration file to fetch information on your project. This file should be at the root of your project under the name of `.smalltalk.ston`. Here is the most simple configuration: ```ston SmalltalkCISpec { #loading : [ SCIMetacelloLoadSpec { #baseline : 'MyProject', #directory : 'src' } ] } ``` This configuration tells smalltalkCI two things: - The baseline to use to load the project is BaselineOf`MyProject` - The sources of the project are in /src Now that smalltalkCI configuration file is created, we just need to define your GitHub workflow file. This file should be located in `.github/workflows/` folder. Let's call ours `testing.yml`. ```yml name: CI on: push: branches: - 'master' jobs: build: runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: Pharo64-10 - run: smalltalkci -s ${{ matrix.smalltalk }} shell: bash timeout-minutes: 15 ``` Let's see a little about what is happening here. The #name parameter allows one to give a name to the workflow. This is useful because you might have more than one workflow for your project. For example: ![Example of different workflows](GitHubActions_workflows.png) In this image, we can see a CI that has 3 different workflows: - CI - continuous - Release Then we have: ```yml on: push: branches: - 'master' ``` This is used to define when the workflow should enter into action. In our case, we want it when we `push` on `master` branch. Then we have: ```yml runs-on: ubuntu-latest ``` With this, the workflow will run in the latest version of Ubuntu. ```yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` This step adds en environment variable needed by smalltalkCI. Last but not least, we have the actions to execute: ```yml steps: - uses: actions/checkout@v3 ``` Using the "checkout" action to checkout the project on the CI worker. ```yml - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: Pharo64-10 ``` Using the "setup-SmalltalkCI" action to prepare the setup. ```yml - run: smalltalkci -s ${{ matrix.smalltalk }} shell: bash timeout-minutes: 15 ``` Loads the project and executes the tests of the project with a 15min timeout. This timeout can be increased in case your project tests are longer. Once you commit these two files, your project will have a working CI ! Each time you commit to master, a new build should happen. You will be able to see the resulting build in the `Actions` tab in your project. ## SmalltalkCI options We have seen in the previous section that [SmalltalkCI](https://github.com/hpi-swa/smalltalkCI) relies on a configuration file. We did a simple one to start, but there are more options available. We will cover them in this section. The full and up-to-date list is available in the [SmalltalkCI's README file](https://github.com/hpi-swa/smalltalkCI/blob/master/README.md). ### Load spec options In the previous example, we declared a loading spec with a baseline and a code directory. But it is also possible to add more options: ```ston SCIMetacelloLoadSpec { #baseline : 'MyProject', // Define MC Baseline #directory : 'src', // Path to packages, if packages are on root do not use #directory : '', just don't add this option #failOn : [ #OCUndeclaredVariableWarning ], // Fail build on provided list of Warnings #ignoreImage : true, // If true, Metacello will force a load of a package, overriding a previously existing one #load : [ 'default' ], // Define some specific groups to load #onConflict : #useIncoming, // When there is a conflict between loaded sources and incoming sources (can be #useIncoming|#useLoaded) #onUpgrade : #useIncoming, // When loaded sources are an older version than incoming sources (can be #useIncoming|#useLoaded) #onWarningLog : true, // Log Warning messages to Transcript #platforms : [ #squeak, #pharo, #gemstone ], // Define compatible platforms #usernameEnvVar : 'GITHUB_USER', // Environment variable containing the username used for authentication #passwordEnvVar : 'GITHUB_TOKEN', // Environment variable containing the password used for authentication #registerInIceberg : true // Pharo Only | Register the tested repository in Iceberg (false by default) } ``` ### Testing options It is also possible to customize the testing of the project: ```ston SmalltalkCISpec { ... #testing : { // Include specific TestCases #include : { #classes : [ #AnotherProjectTestCase ], #categories : [ 'AnotherProject-Tests' ], #packages : [ 'AnotherProject.*' ], #projects : [ 'BaselineOfMyProject' ] }, // Exclude specific TestCases from testing #exclude : { #classes : [ #AnotherProjectTestCase ], #categories : [ 'AnotherProject-Tests' ], #packages : [ 'AnotherProject.*' ], #projects : [ 'ConfigurationOfMyOtherProject' ] }, #allTestCases : true, // Run all TestCases in image // Define TestCases explicitly #classes : [ #MyProjectTestCase ], #categories : [ 'MyProject-*' ], #packages : [ 'MyProject.*' ], #projects : [ 'BaselineOfMyProject' ], // Other options #defaultTimeout : 30, // In seconds (Squeak, and Pharo 6 or later) #failOnSCIDeprecationWarnings : false, // Fail if a deprecated smalltalkCI API is used #failOnZeroTests : false, // Pass builds that did not run any tests #hidePassingTests : true, // Hide passing tests when printing to stdout #serializeError: false // (default: true) Disable serialization of failing test case (e.g. with Fuel in Pharo) } } ``` ### Code Coverage It is possible to see the coverage of our project via SmalltalkCI with [Coveralls](https://coveralls.io/). To enable this, you need to add a "coverage" section in your configuration: ```ston SmalltalkCISpec { ... #testing : { ... #coverage : { #packages : [ 'SomePackage', 'SomePack*' ], #classes : [ #ClassToCover ], #categories : [ 'SomeClassCategory', 'SomeClassCat*' ], #format : #coveralls } } } ``` For example our project could look like: ```ston SmalltalkCISpec { #loading : [ SCIMetacelloLoadSpec { #baseline : 'Myproject', #directory : 'src' } ], #testing : { #coverage : { #packages : [ 'MyProject-*' ] } } } ``` This configuration will execute all the tests loaded by the `BaselineOfMyProject` and build the coverage of all packages starting by `MyProject-` for [Coveralls](https://coveralls.io/). If you enabled your repository in coveralls ([https://coveralls.io/repos/new](https://coveralls.io/repos/new)), the results would be uploaded automatically. ![Coveralls screenshot](GithubActions_coveralls.png) For more information, check: [SmalltalkCI guide on coverage](https://github.com/hpi-swa/smalltalkCI/blob/master/docs/COVERAGE.md). ### Using custom scripts SmalltalkCI offers some hooks to be able to run some Pharo scripts at different moments of the project loading and testing. Four hooks are available: - `preLoading` : Executed before loading the project - `postLoading` : Executed after loading the project - `preTesting` : Executed before testing the project - `postTesting` : Executed after testing the project Those parameters can take 3 different parameters. The first one is a single script: ```ston SmalltalkCISpec { #preLoading : 'scripts/preLoading.st', #loading : [ SCIMetacelloLoadSpec { #baseline : 'Myproject', #directory : 'src' } ] } ``` The second one is a collection of scripts: ```ston SmalltalkCISpec { #loading : [ SCIMetacelloLoadSpec { #baseline : 'Myproject', #directory : 'src' } ], #postLoading : [ 'scripts/postLoading1.st', 'scripts/postLoading2.st' ] } ``` And the third one is an instance of SCICustomScript that allows running script only on certain platforms: ```ston SmalltalkCISpec { #preLoading : 'scripts/preLoading.st', #loading : [ SCIMetacelloLoadSpec { #baseline : 'Myproject', #directory : 'src' } ], #postLoading : [ SCICustomScript { #path : 'scripts/postLoadingSqueak.st', #platforms : [ #squeak ] }, SCICustomScript { #path : 'scripts/postLoadingPharo.st', #platforms : [ #pharo ] } ] } ``` Here is a full example: ```ston SmalltalkCISpec { #preLoading : 'scripts/preLoading.st', #loading : [ SCIMetacelloLoadSpec { #baseline : 'Myproject', #directory : 'src' } ], #postLoading : [ 'scripts/postLoading1.st', 'scripts/postLoading2.st' ], #preTesting : SCICustomScript { #path : 'scripts/preTesting.st', #platforms : [ #squeak, #pharo, #gemstone ] }, #testing : ..., #postTesting : [ SCICustomScript { #path : 'scripts/postTestingSqueak.st', #platforms : [ #squeak ] }, SCICustomScript { #path : 'scripts/postTestingPharo.st', #platforms : [ #pharo ] } ] } ``` For more informations on SmalltalkCI in general check: [SmalltalkCI documentation](https://github.com/hpi-swa/smalltalkCI). ## Testing multiple versions of Pharo at once It is rare to have a project working only on one version of Pharo. Thus, it is often useful to have our workflows run on multiple versions. This can be achieved this way: ```yml name: CI on: push: branches: - 'master' jobs: build: runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} strategy: matrix: smalltalk: [ Pharo64-9.0, Pharo64-10, Pharo64-11 ] name: ${{ matrix.smalltalk }} steps: - uses: actions/checkout@v3 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: ${{ matrix.smalltalk }} - run: smalltalkci -s ${{ matrix.smalltalk }} shell: bash timeout-minutes: 15 ``` Here we declared a matrix with multiple versions of Pharo as axes: ```yml strategy: matrix: smalltalk: [ Pharo64-9.0, Pharo64-10, Pharo64-11 ] ``` Each axe will use the name of the Pharo version as name: ```yml name: ${{ matrix.smalltalk }} ``` But this could also be another name, such as: ```yml name: MyProject-${{ matrix.smalltalk }} ``` Then the builds would be: `MyProject-Pharo64-9.0`, `MyProject-Pharo64-10` and `MyProject-Pharo64-11`. The names will then be used to display the different builds on Github: ![Screenshot of matrix](GithubActions_matrix.png) The list of possible Pharo versions are: | 64bits | 32bits | | ---------------- | ---------------- | | `Pharo64-alpha` | `Pharo32-alpha` | | `Pharo64-stable` | `Pharo32-stable` | | `Pharo64-11` | `Pharo32-11` | | `Pharo64-10` | `Pharo32-10` | | `Pharo64-9.0` | `Pharo32-9.0` | | `Pharo64-8.0` | `Pharo32-8.0` | | `Pharo64-7.0` | `Pharo32-7.0` | | `Pharo64-6.1` | `Pharo32-6.1` | | `Pharo64-6.0` | `Pharo32-6.0` | | | `Pharo32-5.0` | | | `Pharo32-4.0` | | | `Pharo32-3.0` | > Note: This list is from February 2023. More versions will be added in the future ## Testing on multiple OS Usually, our Pharo projects are not influenced by the OS on which it runs. But in some cases, it can be important to test on multiple OS. This can be achieved this way: ```yml name: CI on: push: branches: - 'master' jobs: build: strategy: matrix: os: [ macos-latest, windows-latest, ubuntu-latest] smalltalk: [ Pharo64-9.0, Pharo64-10] runs-on: ${{ matrix.os }} name: ${{ matrix.smalltalk }} on ${{ matrix.os }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: ${{ matrix.smalltalk }} - run: smalltalkci -s ${{ matrix.smalltalk }} shell: bash timeout-minutes: 15 ``` As we can see here: ```yml os: [ macos-latest, windows-latest, ubuntu-latest] runs-on: ${{ matrix.os }} ``` We declare that our project will be run on the latest macos, windows and ubuntu to covers the 3 major OS. To distinguish the builds, we use the Pharo version and the OS name to name our builds: ```yml name: ${{ matrix.smalltalk }} on ${{ matrix.os }} ``` ![Screenshot of ci matrix](GithubActions_os.png) You can find more information on the available OS here: [https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners) ## Manage the workflow target (branchs, PR, ...) In our simple example at the beginning, we were launching the CI on the master branch, but you will often need to execute the CI on multiple branches. ### Target branches It is possible to target multiple branches at once. For example: ```yml on: push: branches: - 'master' - 'development' - 'feature/** ``` This will launch your workflow for every commit on `master`, `development` or any branch starting with `feature/`. You can also target all branches in two ways. The first is by not specifying any: ```yml on: [ push ] ``` The second is to use `'**'`: ```yml on: push: branches: - '**' ``` This second way of declaring "all branches" is useful when you want to execute your workflow on all branches except some of them. In that case, you can use: ```yml on: push: branches: - '**' - '!doc/**' ``` This template will launch the CI for every commit on any branch except the ones starting with `doc/`. ### Target pull requests It is also possible to target others kinds of events. Particularly pull requests made to your project: ```yml on: [ push, pull_request ] ``` And be even more precise on the kinds of PR that should execute a workflow: ```yml on: pull_request: types: [assigned, opened, synchronize, reopened] ``` Note that this can be done by addition of other targets: ```yml on: push: branches: - '**' - '!master' pull_request: types: [assigned, opened, synchronize, reopened] ``` Here the workflow runs on every pull request and every commit of any branch except `master`. ### Target releases It is also sometimes useful to have a workflow targeting releases. We'll exploit that later in this documentation. In that case you can use: ```yml on: release: types: [created, edited] ``` ### Target scheduled workflow In some cases, it might be useful to target the workflow at specific times. For example, it happens that some projects are "meta project". Their goal is to load a bunch of other projects. In that case, it's rare that a commit is made direcly in them. But we want to run the CI on a regular basis to be sure our project still works. In that case, we can use a cron to schedule the workflow: ```yml on: push: branches: - 'master' schedule: - cron: '0 0 * * *' ``` Some utils will help you to configure the parameter of this target such as [Crontab.guru](https://crontab.guru/). ### Other targets GitHub actions have way more options than the options presented here. The most useful ones were presented here, but you can find more information in [Github documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on) ## Managing multiple workflows and SmalltalkCI configurations Until now, we managed only one workflow file, but it is possible to manage multiple of them. To demonstrate this, let's imagine that our project has two groups in its configuration. The first group loads the core of the project and is the default group of our baseline. A second group loads the full projects with additional features. We would now like to have two workflows: - A first workflow launched on all branches and PR to test the core in Pharo 9, 10 and 11 - A second workflow launched on master branch and PR to test the full project in Pharo 11 The first step is to get two smalltalkCI configurations. A first one will be the default `.smalltalk.ston`. ```ston SmalltalkCISpec { #loading : [ SCIMetacelloLoadSpec { #baseline : 'MyProject', #directory : 'src' } ] } ``` And a second one loads the group `all` and is named `.smalltalkFull.ston`. ```ston SmalltalkCISpec { #loading : [ SCIMetacelloLoadSpec { #baseline : 'MyProject', #directory : 'src', #load : [ 'all' ] } ] } ``` Now that we have our two configurations, we can create our two workflow files. The first one is close to what we have seen until here in this documentation: ```yml name: CI Core on: [ push, pull_request ] jobs: build: runs-on: ubuntu-latest strategy: matrix: smalltalk: [ Pharo64-9.0, Pharo64-10, Pharo64-11 ] name: ${{ matrix.smalltalk }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: ${{ matrix.smalltalk }} - run: smalltalkci -s ${{ matrix.smalltalk }} shell: bash timeout-minutes: 15 ``` In the second, we change the targets, and we give one more parameter to the smalltalkCI launch command to specify the path to our specific Smalltalk configuration. ```yml name: CI Full on: push: branches: - 'master' pull_request: types: [assigned, opened, synchronize, reopened] jobs: build: runs-on: ubuntu-latest name: ${{ matrix.smalltalk }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: Pharo64-11 - run: smalltalkci -s ${{ matrix.smalltalk }} .smalltalkFull.ston shell: bash timeout-minutes: 15 ``` Each file uses a different configuration file. The first workflow file is named `CI` uses the default file `smalltalk.ston` implicitly. The second workflow file is named `CI full` uses `smalltalkFull.ston` explicitly. ```yml - run: smalltalkci -s ${{ matrix.smalltalk }} .smalltalkFull.ston shell: bash timeout-minutes: 15 ``` > Note: If we wanted to run on the same targets with two smalltalkCI configurations, we could also have used another matrix axis and avoid needing of different workflows. Example: ```yml name: CI on: [ push, pull_request ] jobs: build: runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} strategy: matrix: smalltalk: [ Pharo64-9.0, Pharo64-10, Pharo64-11 ] config: [ .smalltalk.ston, .smalltalkFull.ston ] name: ${{ matrix.smalltalk }} - ${{ matrix.config }} steps: - uses: actions/checkout@v3 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-image: ${{ matrix.smalltalk }} - run: smalltalkci -s ${{ matrix.smalltalk }} ${{ matrix.config }} shell: bash timeout-minutes: 15 ``` Having multiple workflows can have other usages that we explore in the next sections. ## Continuous releases Until now, we have used Github actions to test our project. But Github Actions has more features. For instance, we are able to realease our projects. In this section, we see how to save the result of the builds of our master branch in a github release. To do that, we can first remove the master branch from the targets of our test workflow because we will handle the master branch in another workflow. ```yml on: push: branches: - '**' - '!master' pull_request: types: [assigned, opened, synchronize, reopened] ``` Then we will create another workflow called `.github/workflows/continuous.yml`. ```yml name: continuous on: push: branches: - 'master' jobs: build: runs-on: ubuntu-latest env: PROJECT_NAME: MyProject-${{ matrix.smalltalk }} strategy: matrix: smalltalk: [ Pharo64-10, Pharo64-11 ] name: ${{ matrix.smalltalk }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-version: ${{ matrix.smalltalk }} - run: smalltalkci -s ${{ matrix.smalltalk }} shell: bash timeout-minutes: 15 # Here we zip the result of the build to be able to keep the artefacts - name: package run: | mv /home/runner/.smalltalkCI/_builds/* . mv TravisCI.image $PROJECT_NAME.image mv TravisCI.changes $PROJECT_NAME.changes echo ${${{ matrix.smalltalk }}} | sed -e 's/.*\-//g ; s/\..*//g ; s/$/0/' > pharo.version zip -r $PROJECT_NAME.zip $PROJECT_NAME.image $PROJECT_NAME.changes *.sources pharo.version ls #Save the artefact of the build under "continuous" tag - name: Update release uses: johnwbyrd/update-release@v1.0.0 with: release: 'continuous' token: ${{ secrets.GITHUB_TOKEN }} files: ${{ env.PROJECT_NAME }}.zip ``` This workflow starts to like what we have seen until now. It checkout our project, install smalltalkCI and runs it. But it also adds three steps to the process. The first step is to give a name to our project. We will use that name to name our build artifact: ```yml env: PROJECT_NAME: MyProject-${{ matrix.smalltalk }} ``` In the second step, we rename the image produced by SmalltalkCI. We generate a version file that is useful in tools such as the `PharoLauncher`. And we zip the files in the archive to save. ```yml # Here we zip the result of the build to be able to keep the artefacts - name: package run: | mv /home/runner/.smalltalkCI/_builds/* . mv TravisCI.image $PROJECT_NAME.image mv TravisCI.changes $PROJECT_NAME.changes echo ${${{ matrix.smalltalk }}} | sed -e 's/.*\-//g ; s/\..*//g ; s/$/0/' > pharo.version zip -r $PROJECT_NAME.zip $PROJECT_NAME.image $PROJECT_NAME.changes *.sources pharo.version ls ``` The last step is to publish the artifact in the continuous tag. For that we are using the action `johnwbyrd/update-release@v1.0.0`. ```yml #Save the artefact of the build under "continuous" tag - name: Update release uses: johnwbyrd/update-release@v1.0.0 with: release: 'continuous' token: ${{ secrets.GITHUB_TOKEN }} files: ${{ env.PROJECT_NAME }}.zip ``` > Note: The name of the continuous release can be changed Once this is done, each commit on master will result in updating the assets of the `continuous` release on GitHub to save the latest one. ![Screenshot of continuous release](GithubActions_continuous.png) ## Save releases artifacts We have seen how to save the last artifact of a branch in a `continuous` release, but another case needs to be taken into account: the real releases. It is useful when we release a project to save a Pharo image of this project with the version of the release. This can be done with a new workflow file that will target releases: ```yml name: Release on: release: types: [created, edited] jobs: build: runs-on: ubuntu-latest env: PROJECT_NAME: MyProject-${{ matrix.smalltalk }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} strategy: matrix: smalltalk: [ Pharo64-10, Pharo64-11 ] name: ${{ matrix.smalltalk }} steps: - uses: actions/checkout@v3 - uses: hpi-swa/setup-smalltalkCI@v1 with: smalltalk-version: ${{ matrix.smalltalk }} - run: smalltalkci -s ${{ matrix.smalltalk }} shell: bash timeout-minutes: 15 # Here we zip the result of the build to be able to keep the artefacts - name: package run: | mv /home/runner/.smalltalkCI/_builds/* . mv TravisCI.image $PROJECT_NAME.image mv TravisCI.changes $PROJECT_NAME.changes echo ${${{ matrix.smalltalk }}} | sed -e 's/.*\-//g ; s/\..*//g ; s/$/0/' > pharo.version zip -r $PROJECT_NAME.zip $PROJECT_NAME.image $PROJECT_NAME.changes *.sources pharo.version ls - name: Release uses: softprops/action-gh-release@v1 with: files: ${{ env.PROJECT_NAME }}.zip ``` This file looks a lot like the one we wrote in the previous section. A few changes are to be noted. The is the name of the workflow to identify it easily in the Actions tab of Github. The second is the target to build only on releases. ```yml on: release: types: [created, edited] ``` And the last change is the action used after creating our Pharo archive. ```yml - name: Release uses: softprops/action-gh-release@v1 with: files: ${{ env.PROJECT_NAME }}.zip ``` This action will add the files matching the $files: property to the assets of the release. ![Screenshot of releases](GithubActions_releases.png) ## Depending on the resources of your repository with GitBridge It happens in some projects that we need resources to do some tests. In the past, it was common to save those resources as a string or a byte array directly in a method of the project. This makes can have multiple drawbacks like making the management of the project harder, dropping Pharo's performances in code management, no real versioning of those resources… Now that we can store our projects on git, an alternative is possible: Save your resources in your git repository and use file references in Pharo to access them. To help with that, the project [GitBridge](https://github.com/jecisc/GitBridge) was created. This project helps one to access resources from the git repository and information from git directly from the Pharo image. This project can be added as a dependency of your project with this spec: ```Smalltalk spec baseline: 'GitBridge' with: [ spec repository: 'github://jecisc/GitBridge:v1.x.x/src' ] ``` In order to create a GitBridge to your project, you first need to subclass `GitBridge` and to store your bridge in a package of your project. ```Smalltalk GitBridge subclass: #MyProjectBridge slots: { } classVariables: { } package: 'MyProject' ``` This new bridge needs a class initialization like this one: ```Smalltalk MyProjectBridge class>>initialize SessionManager default registerSystemClassNamed: self name ``` This will allow the bridge to reset some cache at the image startup. Now that your bridge is created, if it finds an Iceberg repository associated with its local clone containing the package in which the bridge is defined, you will be able to use the bridge to access some resources. For example, you can get a file reference to the git folder like this: ```Smalltalk MyProjectBridge root ``` And this allows you to access your test resources. Once your project is using `GitBridge`, you just need to be sure of two things in order for the CI to work. The first is to have the option `#registerInIceberg` to true in your smalltalkCI configuration. ```ston SmalltalkCISpec { #loading : [ SCIMetacelloLoadSpec { #baseline : 'MyProject', #directory : 'src', #registerInIceberg : true } ] } ``` And the second is to add a parameter to the checkout action of your workflow file in order to fetch the full history of git: ```yml steps: - uses: actions/checkout@v3 with: fetch-depth: '0' ``` Once those steps are set up, your tests should be able to run and fetch resources from your git repository without trouble. For more information, you can look at the [documentation of GitBridge](https://github.com/jecisc/GitBridge). ## Add your build artifacts to PharoLauncher [Pharo launcher](https://pharo-project.github.io/pharo-launcher//) is a great tool to manage Pharo images, and here we are going to explain how to be able to get images of your project from it. > Note: In order to do this, you will need to have a continuous release and/or a release workflow setup as we explained earlier in this documentation. Pharo launcher comes with a default set of sources to fetch Pharo images. It also allows one to add its own sources, and we will show here how to add your project as one of your own source. It is doable thanks to the `mysources.list` of pharo-launcher file located in the `PhLUserTemplateSources sourcesFile` folder. If present, this file defines additional template sources beyond the official list of templates. At this time, there is no UI to add them. To find the right folder: * Open the Pharo Launcher * Open a Playground (Ctrl + O, Ctrl + W) * Execute `PhLUserTemplateSources sourcesFile` You can now edit `mysources.list` in this folder to add the images of your project you wish to have in your Pharo launcher. Here is how to add images from your continuous release: ```st [ PhLTemplateSource { #type : #URLGroup, #name : 'MyProject', #templates : [ PhLTemplateSource { #type : #URL, #name : 'MyProject Pharo 10 - master', #url : 'https://github.com/MY_USERNAME/MY_PROJET_NAME/releases/download/continuous/MyProject-Pharo64-10.zip' }, PhLTemplateSource { #type : #URL, #name : 'MyProject 11 - master', #url : 'https://github.com/MY_USERNAME/MY_PROJET_NAME/releases/download/continuous/MyProject-Pharo64-11.zip' } ] } ] ``` And here is how to add images from a specific release, lets say `v1.4.3`: ```st [ PhLTemplateSource { #type : #URLGroup, #name : 'MyProject', #templates : [ PhLTemplateSource { #type : #URL, #name : 'MyProject Pharo 10 - v1.4.3', #url : 'https://github.com/MY_USERNAME/MY_PROJET_NAME/releases/download/v1.4.3/MyProject-Pharo64-10.zip' }, PhLTemplateSource { #type : #URL, #name : 'MyProject Pharo 11 - v1.4.3', #url : 'https://github.com/MY_USERNAME/MY_PROJET_NAME/releases/download/v1.4.3/MyProject-Pharo64-11.zip' } ] } ] ``` You can then adapt those sources to what you need. Once it is done, you can click on the `New` button of the launcher and see a new source named `MyProject`. ## External ressources Here are some resources on GitHub actions and Pharo: - [https://badetitou.fr/blog/2020-11-30-Testing_pharo_with_github_actions](https://badetitou.fr/blog/2020-11-30-Testing_pharo_with_github_actions/) - [https://badetitou.fr/blog/2022-10-28-test-your-moose-code-using-ci](https://badetitou.fr/blog/2022-10-28-test-your-moose-code-using-ci) - [https://modularmoose.org/posts/2021-07-19-automatic-metamodel-documentation-generation](https://modularmoose.org/posts/2021-07-19-automatic-metamodel-documentation-generation) Do not hesitate to do a PR to add more resources ;) ================================================ FILE: General/Glossary.md ================================================ # Pharo vocabulary The Pharo community uses specific vocabulary to designate object oriented concepts, Pharo concepts and Pharo tools. This page aims to provide disambiguation for the words belonging to this vocabulary. ## $a The character a. In Pharo strings are composed of characters, a character starts with a $. For unprintable characters such as tab, just send the message to the class e.g., `Character tab`. ## Binary message A binary message is a [message](#message) composed of special character(s) where two objects are involved (the [receiver](#receiver) and one argument), which is the reason it is call binary. This is mostly used for arithmetic, comparison, and logical operations. For example, `+` is a binary message involving the receiver and the argument in parameter. ## Bootstrap According to [wikipedia](https://en.wikipedia.org/wiki/Bootstrapping), > "Bootstrapping usually refers to a self-starting process that is supposed to proceed without external input." In the case of Pharo, [images](#image) are bootstrapped from the sources. Anyone can download the sources and [create their own custom Pharo image](https://github.com/carolahp/pharo/tree/candle). The Pharo [continuous integration server](https://ci.inria.fr/pharo-ci-jenkins2/job/Test%20pending%20pull%20request%20and%20branch%20Pipeline/) launch the bootstrap process each time a pull-request is merged into [pharo repository](https://github.com/pharo-project/pharo). This seems to be a natural process but one has to know that up to Pharo 7.0, images were not bootstrapped. This means that, each version of Pharo was in fact a copy of the previous image with some additional changes. ## Browser The browser is the tool for browsing and editing packages, classes and methods. In Pharo 6.1 and greater, the browser is called Calypso. ## Candidates In the context of a method-call the candidates are the potential classes in the system that can receive the call. This list is computed from a static analysis of the source code. ## Changes (file) The changesfile logs all source code modifications (especially all the changes you did while programming) since the [sources file](#sources) was generated. This facilitates a per-method history for diffs or reverting. It means that even if you did not manage to save the image file on a crash or you just forgot to, you can recover your changes from this file. A changes file is always coupled with an image file. They work as a pair. > Note: Since Pharo 5, a project called Epicea implementing a new logging system has been introduced in the system. The long term goal of Epicea is to replace the changes file, but this objective has not been reached yet. ## Class-side The class-side of a class refers to its meta-class. This meta-class contains methods that can be sent to the class directly. For example, `#x:y:` method (which creates an instance of `Point` with arbitrary x and y coordinates) is held by the meta-class of `Point`. In Pharo, both `Class` and `Metaclass` understand `#classSide` method. For example: ```Smalltalk Point classSide. "Point class" Point class classSide. "Point class" Point includesSelector: #x:y:. "false" Point class includesSelector: #x:y:. "true" ``` The best way to understand what the class-side is, is to have a look at `#classSide` methods implementations: ```Smalltalk Class>>#classSide ^ self class ``` ```Smalltalk Metaclass>>#classSide ^ self ``` ## Cog Cog is the name of the [virtual machine](#virtual-machine) used by Pharo. It is also used by other programming languages such as [Squeak](#squeak), Newspeak and Cuis. ## Context A *Context* represent a program execution. It will store, for example, the `CompiledMethod` being currently executed, the receiver and arguments of the message that invoked this `CompiledMethod`, the temporary variables, the stack of calls until the moment of the execution, ... In Pharo, you can use the keyword `thisContext` to interact with the current context of your code. It will return the `Context` object at the moment of the execution. This object is different on each method call. ## Debugger The *Debugger* is a tool opened by the system when an exception is raised and never got caught. This tool allows the developer to interact with the execution [context](#context) that raised the error and its previous contexts. It lets the users inspect the objects present in this context and updates the code to fix the bug using the execution information as help. ## Dispatch Dispatch is a technique used in object-oriented programming to delegate behavior using [polymorphism](#polymorphism). The goal is to define a concept that will change depending on the context. For example we can have an application with commands. When we need to execute the commands, instead of creating a switch/case, each object will implement its own behaviour on a method of the same name and we just need to call this method. ## DNU See [DoesNotUnderstand](#doesnotunderstand). ## DoesNotUnderstand This name is used to describe the error that arises when a [message](#message) is sent to an object but the object does not understand it. It also happens that people use the "DNU" shortcut. ## Iceberg *Iceberg* is git client integrated in the system since Pharo 7 (Pharo 6.1 contained a technical preview of the tool). It is a set of tools that allows one to handle git repositories directly from a Pharo image. Iceberg is the default repository manager for Pharo, allowing for smoother and faster integration of contributions, as well as better branch and version management. ## Image A Pharo image is a snapshot of Pharo memory at any given moment. This is the file where all objects are stored and as such, it’s a cross platform format. An image file contains the live state of all objects of the system (including classes and compiled methods, since they are objects too) at a given point. It can bee seen as a virtual object container. ## Implementors For a given selector in the system, implementors are classes that have a method with this selector (they implement the selector). For example the implementors of the method `#banana` are all the classes containing a method named `#banana`. ## Inspector The *Inspector* is a Pharo tool which allows one to inspect objects, see their current state, interact with them and display specific information depending on the object. It offers multiple views and it uses a Finder as a navigation. One particular feature is that you can use the evaluator tab to enter code, and evaluating it results in the opening of another pane to the right. ## Instance An instance is a concrete realisation of a class. One can send [messages](#message) to an instance. These messages correspond to methods defined by the class related to the instance. All instances of a class share the behaviour (methods) and structure ([slots](#slot)) defined by the class but two instances can hold different data in their [slots](#slot). ## Instance-side The instance-side of a meta-class refers to its class. This class contains methods that can be sent to its instances. For example, `#x` method (which returns the x coordinate of a point) is held by the class `Point`. In Pharo, both `Class` and `Metaclass` understand `#instanceSide` method. For example: ```Smalltalk Point instanceSide. "Point" Point class instanceSide. "Point" Point includesSelector: #x. "true" Point class includesSelector: #x. "false" ``` The best way to understand what the instance-side is, is to have a look at `#instanceSide` methods implementations: ```Smalltalk Class>>#instanceSide ^ self ``` ```Smalltalk Metaclass>>#instanceSide ^ self soleInstance ``` ## Instance variable See [slot](#slot). ## Keyword message A keyword message is a [message](#message) where two or more objects are involved (the [receiver](#receiver) and the arguments). A message is composed of alphanumeric characters. The arguments are injected inside the message selector and must be preceded by a colon (`:`). For example, `between:and:` is a keyword message with a receiver and two arguments. It can be used like this: `13 between: 12 and: 14`. ## Late binding *Late binding* (or *dynamic binding*) is a general mechanism in which methods or functions called are [looked up](#lookup) at run-time. This mechanism is the opposite of *early binding* that does the lookup at compilation-time and fixes all types of variables during that time. More concretely, it means that in Pharo, when you send a [message](#message) to an object, the lookup of the method to execute is performed and then the method found by the lookup is executed. This simplifies the use of reflectivity since the user can invoke new methods without recompiling the whole application. ## Lookup Method lookup is the name of the technique Pharo uses to find the method to execute when an object receives a [message](#message). It proceeds as follows: When a message is sent, methods in the [receiver](#receiver)'s class are searched for a matching method. If no match is found, the superclass is searched, and so on up the class chain. In the end, if no method is found, the object calls the method [`#doesNotUnderstand:`](#doesnotunderstand) with the message as a parameter. ## Message A message represents an interaction between two objects. A [sender](#sender) sends a message to a [receiver](#receiver) which will start a [lookup](#lookup). A message is composed of two elements: the selector, which is the name of the method to lookup, and argument values which are the values of each argument of the method to execute once found by the lookup. Three kinds of messages exists: [unary messages](#unary-message), [binary messages](#binary-message) and [keyword messages](#keyword-message). ## Message-send ## Monticello *Monticello* is a distributed versioning system for [Squeak](#squeak) and Pharo code. It was the main versioning system until Pharo 6. It is now recommended to use [Iceberg](#iceberg). ## Object The word object is used to refer to a particular [instance](#instance) of a [class](#class). "Object" and "instance" words are synonyms. ## Playground The playground / workspace designate a code editor allowing one to run Smalltalk code that is not contained in a method. This tool is useful to run small scripts that can be thrown after the execution. If one needs to write a script that needs to be kept for a long time, it is better to right it as a class-method with the `