Skip to content

Moose

2 posts with the tag “Moose”

Building and sharing Moose models with MooseNexus

Building a Moose model is often only the first step of an analysis. We also need to know which source project produced it, which dependencies were used, which tools extracted it, and how another person can build or retrieve the same model.

MooseNexus brings these steps into one repeatable workflow. It builds Moose models, records how they were produced, stores them as versioned artifacts, and can share them through an artifact registry.

This post first uses the MooseNexus API from a Moose image, then performs the same kind of build with the command-line interface. The final section explains how an artifact registry can store and distribute the resulting models and images.

MooseNexus sits between a source project and the Moose model that we want to analyze.

A source project and build specification flow through model extraction into a versioned artifact, which can be stored locally or in a shared registry.

From source project to versioned model artifact: MooseNexus records the result locally and can publish it to a shared artifact registry.

A build starts with two pieces of information:

  • the directory containing the source project;
  • coordinates that identify it: a group, a name, and a version.

MooseNexus then imports the project metadata, resolves the dependencies needed for extraction, runs the model extractor, and records the result. For Java projects it can recognize Maven and Gradle projects. Projects without a supported build descriptor can be described explicitly as unmanaged projects.

The result is more than a model file. A model artifact also records its source-project identity, dependency-resolution result, and build provenance, including the Moose, Pharo, MooseNexus, and extractor versions. This information makes it possible to inspect how a model was produced and to select a compatible environment later.

By default, projects and artifacts are kept in a local repository below ~/.moose. The important principle is similar to Maven coordinates: com.example:demo:1.0.0 identifies one version of a project independently of the machine on which it was analyzed.

The main API entry point is MooseNexusBuildSpec. A build specification describes the intended result; MooseNexus handles the individual import, dependency, extraction, and storage steps.

For a Java project, load the Java runtime group in a Moose image:

Metacello new
githubUser: 'moosetechnology'
project: 'MooseNexus'
commitish: 'v1.x.x'
path: 'src';
baseline: 'MooseNexus';
load: 'Java'.

The floating v1.x.x tag selects the newest compatible version in the MooseNexus 1.x series. Use an exact release tag when the build must be fully reproducible.

Let us build a model for a project located at /path/to/demo. We first give it stable coordinates:

coordinates := MooseNexusCoordinates
group: 'com.example'
name: 'demo'
version: '1.0.0'.

The coordinates identify the project in the local repository and, later, in an artifact registry. They do not need to be the same as Maven or Gradle coordinates, although using the same identity can be convenient.

Create a build specification from the coordinates and source directory:

spec := MooseNexusBuildSpec
coordinates: coordinates
sourceDirectory: '/path/to/demo' asFileReference.
spec
modelName: 'demo-model';
modelComment: 'Model used for the architecture analysis'.

The source directory can be anywhere; it does not have to be a Git checkout. For a Maven or Gradle project, MooseNexus detects the appropriate importer from the directory. The explicit coordinates remain the identity of the MooseNexus artifact.

This separation is useful: the build tool describes how the software project is assembled, while the build specification describes the model artifact that we want MooseNexus to produce.

Execute the specification in the default repository:

result := spec execute.

This performs the complete workflow and records the project below the default ~/.moose repository. The result gives access to three useful objects:

result project.
result modelArtifact.
result dependencyResolutionResult.

Use executeIn: with a MooseNexusRepository when the artifact should be stored elsewhere:

repository := MooseNexusRepository
localDirectory: '/path/to/model-repository' asFileReference.
result := spec executeIn: repository.

Building records the artifact but does not force it into the current image. Import it explicitly when it is time to analyze it:

model := result project importModel: result modelArtifact.

The imported model is registered in MooseModel root. Keeping the build and import steps separate also lets headless jobs produce artifacts without retaining a full analysis image.

For most clients, execute or executeIn: is the API to keep. MooseNexus also exposes the individual build phases, but those are mainly useful when an application needs custom orchestration.

The MooseNexus CLI runs the same ideas without requiring us to prepare a Moose image by hand. It creates an isolated workspace, provisions a compatible Moose/Pharo runtime, loads MooseNexus, executes a build specification, and stores the result.

Install it with npm. The CLI requires Node.js 22 or later:

Terminal window
npm install --global @moosetechnology/moosenexus

For Java extraction, the default setup also requires Docker. We can check the available tools before starting a build:

Terminal window
moosenexus doctor

The interactive wizard is the easiest way to discover the commands:

Terminal window
moosenexus --wizard

For a Maven or Gradle project, a direct build can be as small as:

Terminal window
moosenexus build-model \
com.example:demo:1.0.0 \
/path/to/demo

This creates a model artifact and installs it in the local MooseNexus repository. To create a ready-to-open Moose image containing the imported model instead, use build-image:

Terminal window
moosenexus build-image \
com.example:demo:1.0.0 \
/path/to/demo \
--adopt

The built image is first stored as an immutable artifact. --adopt also copies it to the standard Pharo images directory, where it becomes a working image that we can open and modify.

The two outputs serve different needs:

  • a model artifact is smaller and can be imported into a compatible existing Moose image;
  • an image artifact is a ready-to-open environment containing the model and its compatible runtime.

An image artifact is selective rather than a copy of the complete build workspace. Its portable ZIP contains:

  • at its root, the .image file, its .changes file when available, the available Pharo .sources files, meta-inf.ston, pharo.version, and a CLI build report;
  • under pharo-local/MooseNexus/, the recorded project metadata, model artifact, and a copy of the analyzed source project.

Other image-local directories, such as pharo-local/iceberg, and the Pharo VM are not included. MooseNexus does not scan these files for credentials or remove secrets: the source copy, saved image, or changes file may contain sensitive information. Review an artifact before publishing it.

An installed image artifact can be adopted several times, much like making working copies from a template. Each adoption creates a separate writable image while leaving the repository artifact unchanged:

Terminal window
moosenexus adopt-image com.example:demo:1.0.0
moosenexus adopt-image com.example:demo:1.0.0 --adopt-as demo-alice

By default, these copies are placed in the standard Pharo images directory and appear in Pharo Launcher. This is not a Launcher template: MooseNexus copies the image files and launcher metadata from an installed artifact. The recorded project and source copy remain in the local MooseNexus repository rather than being duplicated into every adopted image.

Use moosenexus artifacts to see what is installed locally. Build options can also be kept in a YAML file and passed with --config, which is preferable for repeatable scripts and continuous integration. --dry-run shows the resolved workflow without executing it, and --json produces machine-readable output for automation.

The important need here is artifact storage and distribution: a shared place where versioned Moose models and images can be published, found, and retrieved.

MooseNexus currently implements this with the Open Container Initiative distribution conventions. Although OCI is best known for container images, its registries can store other versioned artifacts as well. Reusing that standard gives MooseNexus an existing upload, download, authentication, and access-control ecosystem instead of inventing a Moose-specific artifact server.

An organization can therefore use infrastructure it may already have—such as Harbor, Sonatype Nexus, Artifactory, or another OCI-compatible registry—to distribute Moose models and images. MooseNexus defines what goes into the artifact, while ORAS performs the standard registry upload and download.

A MooseNexus model bundle contains:

  • the exported model;
  • an archive of the analyzed sources;
  • the project metadata;
  • the model manifest, including build and extractor provenance.

Including the sources makes the artifact inspectable and helps reproduce the model, but it is also important to remember before publishing: a model bundle can contain proprietary source code. Use a registry and access policy appropriate for the project.

First authenticate ORAS with the target registry:

Terminal window
oras login registry.example.com

Then add a registry and namespace to the build command:

Terminal window
moosenexus build-model \
com.example:demo:1.0.0 \
/path/to/demo \
--registry registry.example.com \
--namespace team/models

MooseNexus still installs the artifact locally and also publishes it under a reference derived from the project coordinates, for example:

registry.example.com/team/models/moosenexus/com.example/demo:1.0.0

Another machine can retrieve the model with the same coordinates:

Terminal window
moosenexus pull-model \
com.example:demo:1.0.0 \
--registry registry.example.com \
--namespace team/models

pull-model installs the artifact in the local MooseNexus repository. It can then be imported through the API shown earlier. Use pull-image ... --adopt when the published artifact is a Moose image and a ready-to-open copy is wanted instead.

Publication is always explicit: building locally does not silently upload anything. MooseNexus also keeps registry credentials outside its own configuration and relies on the authenticated ORAS installation.

Both interfaces use the same project coordinates, build specification, repository, and artifact concepts.

Use the API when…Use the CLI when…
the build is part of a Moose applicationthe build should start from a clean, isolated runtime
an image needs custom import or extraction policya shell script or CI job should run the build
the resulting model will be analyzed immediatelya model or ready-to-open image should be produced and shared

The API is the stable foundation, and the CLI automates the surrounding runtime setup. Starting with the CLI is often easier; moving to a Smalltalk build specification gives more control without changing the underlying model-artifact workflow.

MooseNexus therefore turns a one-off model extraction into a named, versioned, and shareable build result. That becomes especially valuable when a model must outlive the image in which it was first created—or when another person, machine, or CI job needs to produce the same analysis input.

Setup MooseCI on your repository with GitHub Actions

Setup MooseCI on your repository with GitHub Actions

Section titled “Setup MooseCI on your repository with GitHub Actions”

MooseCI is a tool that runs automatic analyses on your code. It looks for code quality problems and writes a report for you.

MooseCI is built on top of Moose. It can run in headless mode, so you can use it on a CI server.

You can use MooseCI in two ways:

  • as a command line tool, with Docker
  • inside a CI, with its GitHub Action for example

For now, MooseCI supports Java and Python.

MooseCI runs its analyses on the Famix model of your project, not on the FAST model.

The easiest way to try MooseCI is with Docker.

First, pull the image for your language. For Java, use the latest image, and for Python, use the python image:

Terminal window
docker pull ghcr.io/moosetechnology/moose-ci:latest
docker pull ghcr.io/moosetechnology/moose-ci:python

Go to the folder you want to analyze, and create a configuration file with the init command:

Terminal window
docker run -v "$(pwd):/src" ghcr.io/moosetechnology/moose-ci:latest init

This creates a moose-ci.ston file. You can edit it to choose the rules and the metrics.

Then run the analysis:

Terminal window
docker run -v "$(pwd):/src" ghcr.io/moosetechnology/moose-ci:latest analyze

MooseCI prints the report in the console and writes a JSON report in .moose-ci/report.

MooseCI is also available as a GitHub Action. Add it to a repository, and it runs on every pull request.

The Action:

  • runs MooseCI on your project
  • uploads the report as an artifact
  • comments on the pull request with a download link and a short summary of the analysis

Let’s take VerveineJ as an example. VerveineJ is a Java project that parses Java code and exports it to JSON/MSE for Moose.

We added MooseCI to VerveineJ. You can see the result in this pull request.

First, create the configuration file. Go to the folder you want to analyze, and run init:

Terminal window
cd app/src/main/java/fr
docker run -v "$(pwd):/src" ghcr.io/moosetechnology/moose-ci:latest init

This creates app/src/main/java/fr/moose-ci.ston. The file looks like this:

MooseCIConfig {
#projectLanguage : #java,
#metrics : [
#packages,
#classes
],
#rules : [
#too_many_parameters : 10,
#large_class : 20,
#unused_local_variable,
#unused_parameter
],
#outputFormats : [
#json
],
#outputPath : '.moose-ci/report',
#visualizations : [ ],
#isRemote : false
}

In this file, you can:

  • set the language with #projectLanguage
  • choose the metrics to compute with #metrics
  • choose the quality rules with #rules, and give a threshold to some rules (for example #too_many_parameters : 10)
  • choose the report format and location with #outputFormats and #outputPath

Then add the workflow. Create .github/workflows/moose-ci.yml:

name: MooseCI
on: pull_request
permissions:
contents: read
actions: write
pull-requests: write
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: moosetechnology/setup-MooseCI@v1.0.0
with:
project-path: ./app/src/main/java/fr
project-language: java

Some details:

  • on: pull_request runs the workflow on every pull request.
  • actions: write is needed to upload the report.
  • pull-requests: write is needed to write the comment.
  • project-path is the folder to analyze. It must contain moose-ci.ston.
  • project-language selects the image. Use java or python.

On each pull request, MooseCI comments with the analysis. For VerveineJ, the summary looks like this:

MooseCI comment on a pull request

The report also contains the list of rule violations, with the location of each problem in the source code.

Content of the downloaded JSON report

MooseCI is not limited to GitHub. In any other CI, you can run the same Docker image and the same commands as the CLI, for example:

Terminal window
docker run -v "$(pwd):/src" ghcr.io/moosetechnology/moose-ci:latest analyze

The report is written in the MooseCI report folder (.moose-ci/report). Your CI can then publish it as an artifact.

MooseCI is easy to use. You can run it locally with Docker, or on every pull request with the GitHub Action. You only need a configuration file and a small workflow.