Skip to content

jgfoster/gspm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gspm — GemStone Package Manager

A Cargo-inspired package manager for GemStone/S. Manages dependencies using Git, resolves version constraints, and generates Topaz scripts to load .gs, Tonel, and FileTree sources into a stone.

gspm takes the position that files on disk are the source of truth and the GemStone image is a build artifact. This eliminates the round-trip problem (image ↔ files) and lets you use standard Git workflows — branches, pull requests, diffs — without any image-side tooling.

Installation

Requires Python 3.9+.

pip install gemstone-gspm

Or install from source:

git clone https://github.com/jgfoster/gspm.git
cd gspm
pip install -e .

Quick Start

# Create a new project
gspm init my-app
cd my-app

# Add a dependency
gspm add seaside --version "^3.5" --git https://github.com/SeasideSt/Seaside

# Resolve and download dependencies
gspm fetch

# See what was resolved
gspm tree

# Preview the Topaz load script
gspm install --dry-run my_stone

# Load into a stone
gspm install my_stone

The Manifest: gemstone.toml

Every gspm project has a gemstone.toml at its root. This is the file you edit by hand.

[package]
name = "seaside"
version = "3.5.0"
description = "Web application framework for GemStone"
gemstone = ">=3.5"
authors = ["Seaside Team"]
license = "MIT"
repository = "https://github.com/SeasideSt/Seaside"

[load]
# Files to file-in, in order. Order matters in Smalltalk.
files = [
    "src/core/SeasideCore.gs",
    "src/core/SeasideSession.gs",
    "src/rendering/SeasideRendering.gs",
    "src/adaptors/SeasideGemStone.gs",
]
# Directories containing Tonel (.st) files — auto-discovered and ordered
tonel = ["src/MyPackage"]

[load.conditions]
# Additional files loaded for specific GemStone versions
">=3.7"       = ["src/platform/Seaside37Features.gs"]
">=3.6,<3.7"  = ["src/platform/Seaside36Features.gs"]

[dependencies]
magritte = { version = "^2.0", git = "https://github.com/magritte-metamodel/magritte" }
zinc     = { version = "^1.2", git = "https://github.com/svenvc/zinc" }

[dev-dependencies]
sunit = { version = "^1.0", git = "https://github.com/example/sunit-gs" }

[test]
files = ["tests/SeasideTests.gs"]

Loading legacy dependencies

Dependencies that don't have their own gemstone.toml can be consumed by specifying load instructions inline:

[dependencies]
# FileTree-based package — specify which .package directories to transpile
seaside = { version = "^3.5", git = "https://github.com/SeasideSt/Seaside", filetree = [
    "repository/Seaside-Core.package",
    "repository/Seaside-Component.package",
]}

# Tonel-based package — specify which directories to transpile
magritte = { version = "^3.7", git = "https://github.com/magritte-metamodel/Magritte", tonel = ["source/Magritte-Model"] }

# .gs-based package — specify which files to load
mylib = { version = "^1.0", git = "https://example.com/mylib", files = ["src/MyLib.gs"] }

If a dependency has neither a manifest nor load overrides, gspm auto-discovers Tonel directories and FileTree packages, and falls back to .gs files.

See docs/source-formats.md for a detailed guide to each format.

Version constraints

gspm uses Cargo-style version constraints:

Syntax Meaning Example
^1.2.3 >=1.2.3, <2.0.0 Compatible with 1.x
^0.2.3 >=0.2.3, <0.3.0 Compatible with 0.2.x
~1.2.3 >=1.2.3, <1.3.0 Patch-level changes only
>=3.5 >=3.5 Minimum version
* Any version No constraint

The Lock File: gemstone.lock

Running gspm fetch produces gemstone.lock, which records the exact resolved SHA for every dependency. Commit this file to version control — it is the reproducibility guarantee.

# This file is automatically generated by gspm.
# Do not edit it manually. Commit it to version control.

[[package]]
name = "zinc"
version = "1.3.1"
source = "git+https://github.com/svenvc/zinc"
sha = "b84c1f209d3e5a..."

[[package]]
name = "magritte"
version = "2.1.3"
source = "git+https://github.com/magritte-metamodel/magritte"
sha = "f92b3a71cc04d8..."
dependencies = ["zinc"]

Commands

gspm init [name]

Create a new project with a skeleton gemstone.toml, src/, and tests/ directories. If name is given, creates a subdirectory.

gspm init my-project

gspm add <package>

Add a dependency to gemstone.toml.

gspm add seaside --version "^3.5" --git https://github.com/SeasideSt/Seaside
gspm add sunit --version "^1.0" --git https://example.com/sunit --dev

For legacy packages without a gemstone.toml, specify load overrides:

gspm add seaside --version "^3.5" --git https://github.com/SeasideSt/Seaside \
    --filetree repository/Seaside-Core.package \
    --filetree repository/Seaside-Component.package
gspm add magritte --version "^3.7" --git https://github.com/magritte-metamodel/Magritte \
    --tonel source/Magritte-Model
gspm add mylib --version "^1.0" --git https://example.com/mylib \
    --files src/MyLib.gs

If the package is published to the registry, --git can be omitted:

gspm add seaside --version "^3.5"

gspm fetch

Resolve all dependencies and download them. Updates gemstone.lock and populates .gspm/cache/ and .gspm/deps/.

gspm fetch

gspm install <stone>

Generate a Topaz script and run it to load all code into the named stone. Dependencies are loaded in topological order (dependencies before dependents).

gspm install my_stone
gspm install my_stone --user SystemUser --password secret
gspm install my_stone --gs-version 3.7.0    # enables conditional loading
gspm install my_stone --dry-run              # print script, don't run it

gspm test <stone>

Like install, but also loads dev-dependencies and test files.

gspm test my_stone
gspm test my_stone --dry-run

gspm update [package]

Re-resolve dependencies within the constraints declared in gemstone.toml. Optionally update only a single package.

gspm update           # update all
gspm update seaside   # update only seaside

gspm tree

Display the resolved dependency tree.

gspm tree
seaside 3.5.0
├── magritte 2.1.3
│   └── zinc 1.3.1
└── zinc 1.3.1

gspm publish

Publish the current package to the registry by opening a pull request to the registry index repository. Requires the gh CLI.

gspm publish

Tonel Support

gspm can consume packages written in Tonel format (.st files — one file per class), as used by Pharo and some GemStone projects. Since GemStone's Topaz does not load .st files directly, gspm transpiles them to .tpz file-in format automatically during install and test.

Declaring Tonel directories

Add a tonel key to the [load] section listing directories that contain .st files:

[load]
files = ["src/bootstrap.gs"]
tonel = ["src/MyPackage"]

gspm auto-discovers all .st files in each listed directory and determines load order from the class hierarchy — superclasses are loaded before subclasses, and extension methods are loaded after all class definitions.

Tonel file format

Each .st file defines a single class (or extension) and its methods:

"A simple model class"
Class {
    #name : #Customer,
    #superclass : #Object,
    #instVars : [ 'name', 'email' ],
    #classVars : [],
    #poolDictionaries : [],
    #category : #'MyApp-Models'
}

{ #category : #accessing }
Customer >> name [
    ^ name
]

{ #category : #accessing }
Customer >> name: aString [
    name := aString
]

Extensions use Extension instead of Class:

Extension { #name : #String }

{ #category : #'*MyApp' }
String >> isValidEmail [
    ^ self includes: $@
]

How transpilation works

When gspm install or gspm test runs:

  1. For each dependency (and the project itself) that declares load.tonel directories, gspm parses every .st file in those directories.
  2. All parsed classes are combined into a single .tpz file using two-phase loading:
    • Phase 1 — class definitions: Every class is created (superclasses first) so that all class names exist in the dictionary.
    • Phase 2 — methods: All instance, class, and extension methods are compiled. Because every class name already exists, forward references in method bodies resolve correctly.
  3. The generated .tpz file is written to .gspm/tonel/<package>/ and included in the Topaz load script via an input directive.

The two-phase approach eliminates forward-reference problems — methods in one class can freely reference another class even if it would otherwise be loaded later. GemStone treats a duplicate class definition as a no-op, so there is no conflict if a class is also defined in a .gs file.

The .gspm/tonel/ directory is ephemeral and gitignored — it is regenerated on each install.

This works for both the project's own source and fetched dependencies. If a dependency's gemstone.toml declares tonel directories, gspm transpiles them automatically.

Project Layout

my-project/
  gemstone.toml           # manifest — you write this
  gemstone.lock           # lockfile — gspm generates this
  src/
    MyApp.gs              # your Smalltalk code (.gs format)
    MyModel.gs
    MyPackage/            # Tonel directory
      Customer.st
      Order.st
  tests/
    MyTests.gs
  .gspm/                  # managed by gspm, gitignored
    cache/                # immutable cache of fetched SHAs
    deps/                 # working copies for current build
    bare/                 # bare git clones for resolution
    tonel/                # transpiled .tpz files from Tonel and FileTree sources

The Registry

gspm supports a lightweight package registry: a GitHub repository containing an index.json that maps package names to git URLs and published versions. This is similar to how Homebrew works — no server infrastructure, just a git repo.

Publishing a package means opening a PR to the index repository (default: github.com/GemTalk/gspm-index).

You can also bypass the registry entirely by specifying --git URLs directly in gspm add or in your gemstone.toml.

Configuration

Global configuration lives in ~/.gspm/config.toml:

registry_url = "https://github.com/jgfoster/gspm-index"

[stones.my_stone]
user = "DataCurator"
password = "swordfish"

[stones.production]
user = "SystemUser"
password = "secret"

Stone credentials configured here are used as defaults by gspm install and gspm test, and can be overridden with --user and --password.

How gspm Differs from Rowan

Rowan is a code management system integrated into the GemStone image. It handles round-tripping between the image and files on disk using Tonel format, manages symbol dictionaries, supports session methods, and provides IDE integration through Jadeite.

gspm takes a fundamentally different approach:

Rowan gspm
Source of truth GemStone image Files on disk
File format Tonel .gs / .tpz file-in (with Tonel & FileTree transpilation)
Code loading Image-aware loader Topaz input
Dependency management Project configuration gemstone.toml + resolver
Version control Integrated (Jadeite/git) Standard git workflow
IDE Jadeite (Dolphin/Pharo) Any text editor
Cross-platform GemStone, Pharo, Squeak GemStone only
Image write-back Yes No

gspm deliberately omits image-to-disk write-back. You always edit files and reload — never extract code from the image. This eliminates the round-trip problem entirely, at the cost of the traditional Smalltalk workflow where the image is the source of truth.

The two tools serve different needs. Rowan is the right choice when you need tight IDE integration and image-based development. gspm is the right choice when you want a workflow that feels like every other modern programming environment — edit files, commit, push, resolve dependencies, build.

Development

git clone https://github.com/jgfoster/gspm.git
cd gspm
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest

Publishing to PyPI

  1. Install the build and upload tools:

    pip install build twine
  2. Build the distribution:

    python -m build
  3. Upload to TestPyPI first to verify everything looks correct:

    twine upload --repository testpypi dist/*
    pip install --index-url https://test.pypi.org/simple/ gemstone-gspm
  4. Upload to PyPI:

    twine upload dist/*

You will need a PyPI account and an API token configured in ~/.pypirc or passed via --username __token__ --password <token>.

License

MIT

About

GemStone Package Manager

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages