The package manager

Salam packages live in ordinary git repositories. You add one with a single command, import it by its address, and publish your own by pushing a tag.

This page is about packages written by other people, kept in their own repositories. Everything in the standard library ships with the compiler and needs no salam add. The language side, package, include and pub, is taught in lesson 18.

Using a package

A project is a folder with a main.salam and a salam.toml. salam init turns the current folder into one and keeps any file that is already there:

$ mkdir app && cd app
$ salam init
Created project 'app':
  main.salam
  salam.toml
  .gitignore

Next steps:
  salam run

$ salam add github.com/demo/slug
added github.com/demo/slug v1.1.0

salam add looks up the newest release of the repository, downloads it into ~/.salam/pkg and writes it down in two files. salam.toml says what you asked for, and salam.lock records exactly what you got, the commit and a checksum, so every machine builds the same code.

[dependencies]
"github.com/demo/slug" = "v1.1.0"

A package from a repository is imported with a quoted address. The last part of the address becomes the name you use in the code, so github.com/demo/slug is reached as slug:

import "github.com/demo/slug"

func main:
    println slug.Make("Hello, World!")
    println slug.MakeMax("Packages in Salam are simple", 14)
end
$ salam run
hello-world
packages-in-sa

If two packages would get the same name, give one of them another: import s "github.com/demo/slug" and then s.Make(...). To reach a package inside a repository's folder, add the folder to the address, as in import "github.com/owner/repo/sub".

The commands you will use most:

CommandWhat it does
salam add github.com/owner/repoAdds the newest release; @v1.2.0 picks a tag
salam remove github.com/owner/repoTakes it out of salam.toml and salam.lock; the download stays cached
salam updateMoves every package, or only the ones you name, to the newest release with the same major version
salam depsDownloads what salam.lock lists and checks every checksum; run it after cloning a project
salam search wordsLooks in the standard library and in the registry
salam install github.com/owner/toolBuilds a program's main.salam and puts it in ~/.salam/bin
salam uninstall toolRemoves it again

Commit salam.toml and salam.lock with your code, and leave the downloaded packages out. A fresh clone then needs only salam deps:

$ salam deps
github.com/demo/slug v1.1.0
downloaded 1 package(s)

Removing a package leaves your imports behind, and the compiler tells you so:

$ salam remove github.com/demo/slug
removed github.com/demo/slug
$ salam run
error[E008]: package 'github.com/demo/slug' is not a dependency of this project; run: salam add github.com/demo/slug

Writing a package

A package is a git repository with a manifest and one entry file. The entry file carries the repository's name, so the repository github.com/demo/slug looks like this:

slug/
  salam.toml        the manifest
  slug.salam        the entry file: package slug
  slug_test.salam   tests, run by salam test
  README.md
  LICENSE

The rule that decides the file names is short. A program, something you run, has a main.salam and nothing else is ever taken as its entry. A library, something other projects import, has no main at all; its entry is <name>.salam, and a package inside a folder of the repository is <folder>/<folder>.salam, which is what import "github.com/owner/repo/sub" loads. Other files of the same package sit next to the entry file and are joined to it, as shown in lesson 18.

The manifest is a few lines:

[package]
name = "slug"
version = "1.1.0"
salam = ">=0.6.1"
kind = "lib"

[dependencies]
"github.com/owner/other" = "v1.0.0"
  • name is the program or library name, and version is for people; the version that counts for users is the git tag.
  • salam is the oldest compiler the package builds with. salam add refuses a package that needs a newer one and tells you to run salam upgrade.
  • kind = "lib" marks a library. A program leaves it out.
  • [dependencies] lists what your package needs, in the same form salam add writes. The packages of your packages are found from their own manifests, and when two ask for different versions of one package the newer one is used.

Now the code. What you mark pub is the package's public face and a promise to the people who use it; everything else you may rename or delete in any later release. Here Make and MakeMax are public and is_word is not. The two @en and @fa lines give each public function a name in both languages, so Persian programs can call it too:

{{example.package-slug}}

While you work, try the package from a small program beside it. include loads a file of your own project, so this runs without any download:

{{example.package-slug-demo}}

Tests go in a file whose name ends in _test.salam, with functions named test_something. salam test runs every such file in the folder, each in its own process:

{{example.package-slug-test}}
$ salam test
=== RUN   test_make
--- PASS: test_make
=== RUN   test_make_max
--- PASS: test_make_max
ok   slug_test.salam: 2 tests passed
PASS: 1 test files

Users see only pub names. From a project that depends on the package, calling the private helper is refused:

error[E017]: 'is_word' is not exported by package 'slug' (mark it 'pub')

Publishing it

There is no upload step and no salam publish. A package is published when a tag exists in a public git repository, because that is what salam add reads. The whole procedure:

  1. Put the package in a repository named after it, with the entry file and salam.toml at the top level (or the folder layout above).
  2. Check it: salam test passes, and salam check slug.salam shows no errors.
  3. Write a README.md that shows how to add and import the package and one short example, and add a LICENSE file. Without a licence nobody is allowed to use your code.
  4. Commit, tag the release as vMAJOR.MINOR.PATCH, and push the tag.
$ git tag v1.1.0
$ git push origin main v1.1.0

From now on anyone can run salam add github.com/owner/slug. Try it yourself in an empty project before you announce the package: it is the same path your users take, and it catches a missing entry file at once.

Packages on gitlab.com and codeberg.org work too, and any host works when git is installed. Private repositories work with the credentials git already has.

Making it findable

salam search reads registry/index.toml in the Salam repository, a list kept by pull request. Listing is optional, since salam add works for any public repository, but it lets people find the package by its words:

[[package]]
name = "github.com/owner/slug"
description = "Turn text into URL slugs"
tags = ["text", "url"]

Open a pull request that appends one such entry. The repository needs a salam.toml and the entry file, and its releases should be tagged vX.Y.Z. After the entry is merged, salam search slug prints the name and description.

The same repository keeps registry/sums.txt, a reviewed list of checksums, one module@version sum line per release. A download that differs from a published line is refused. Inside a project that uses your package, salam deps sums prints the lines to propose in a pull request:

$ salam deps sums
github.com/demo/slug@v1.1.0 sha256:f20515bfd08073aebe3d95568695089f8bc7264437e02bad6d87e8b094b9efde

Versions

A release is a git tag shaped like v1.2.3. Follow the usual meaning: a patch (v1.2.4) fixes bugs, a minor (v1.3.0) adds things without breaking old code, and a major (v2.0.0) may break it. Tags cannot be moved safely once someone has downloaded them, because the checksum in their salam.lock would stop matching. Publish a new version instead.

The version in salam.toml can be written in four ways:

Written asMeans
"v1.2.0"exactly that tag
"^1.2"any 1.x from 1.2.0 up (for 0.x, the same minor)
"~1.2"any 1.2.x
">=1.2"1.2.0 or anything newer

salam add github.com/owner/repo@^1.2 writes the range and locks the newest release inside it. The lock file still holds one exact version, so builds stay repeatable until you run salam update.

$ salam add github.com/demo/slug@v1.0.0
added github.com/demo/slug v1.0.0
$ salam update
updated github.com/demo/slug v1.0.0 -> v1.1.0

salam update never crosses to a new major version by itself. It tells you when one is out and leaves the choice to you:

$ salam update
  github.com/demo/slug v2.0.0 is out; it may break your code, move with: salam update github.com/demo/slug@latest
github.com/demo/slug is up to date (v1.1.0)
$ salam update github.com/demo/slug@latest
updated github.com/demo/slug v1.1.0 -> v2.0.0

A repository with no tags at all can still be added. Salam then pins the exact commit with a pseudo version such as v0.0.0-8177964855e1, and salam update moves it to the newest commit. That is handy while a package is young, and a good reason to tag a first release early.

For an author, a major version means a new import address. import "github.com/owner/repo/v2" only accepts v2 tags, so a program can use v1 and v2 side by side. The v2 code may live in a v2/ folder or at the root of the repository; to be used next to v1 its entry is named repo2.salam and declares package repo2.

When something goes wrong

You seeWhy, and what to do
package '...' has no entry file (looked for '.../argon.salam')The repository does not follow the layout of the Writing a package section: import "github.com/owner/name" loads name.salam at the top of the repository. salam add succeeds anyway, since it only fetches. Ask the author to add the entry file, or use the files with include as the package's own README says
package '...' is not a dependency of this project; run: salam add ...The import is not listed in salam.toml. Run the command it prints, or work from the project's folder
'name' is not exported by package '...' (mark it 'pub')The package keeps that name private. Use its public API, or ask the author to export it
salam: cannot list the versions of https://github.com/...The address is misspelled, the repository is private without credentials, or git cannot reach the host
salam: git could not fetch v9.0.0 from ...That tag does not exist. Look at the repository's tags, or leave the version out to get the newest
no main.salam in '...': a project's entry file must be named main.salamYou ran salam build or salam run outside a project. Start one with salam init
'...' has no main.salam, so there is no program to installsalam install is for programs. A library is used with salam add
needs Salam X or newer; run: salam upgradeThe package's salam line asks for a newer compiler than yours

A few more facts that explain surprises:

  • A tag that was moved after release gives a different checksum, and the command stops instead of building different code. If the new copy is the right one, delete its line from ~/.salam/pkg/sums.txt.
  • Without git on the machine, packages from GitHub, GitLab and Codeberg are downloaded as archives. GITHUB_TOKEN raises GitHub's rate limit for listing tags, and SALAM_NO_GIT=1 forces the archive route.
  • SALAM_PKG moves the download folder, and salam deps clean empties it.
  • If a build seems to use old code, delete the project's .salam-build folder and build again.

Looking for a model to follow? The standard library packages are written exactly this way, one folder per package with the entry file named after it. Browse them in the library reference.