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:
| Command | What it does |
|---|---|
salam add github.com/owner/repo | Adds the newest release; @v1.2.0 picks a tag |
salam remove github.com/owner/repo | Takes it out of salam.toml and salam.lock; the download stays cached |
salam update | Moves every package, or only the ones you name, to the newest release with the same major version |
salam deps | Downloads what salam.lock lists and checks every checksum; run it after cloning a project |
salam search words | Looks in the standard library and in the registry |
salam install github.com/owner/tool | Builds a program's main.salam and puts it in ~/.salam/bin |
salam uninstall tool | Removes 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"
nameis the program or library name, andversionis for people; the version that counts for users is the git tag.salamis the oldest compiler the package builds with.salam addrefuses a package that needs a newer one and tells you to runsalam upgrade.kind = "lib"marks a library. A program leaves it out.[dependencies]lists what your package needs, in the same formsalam addwrites. 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:
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:
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:
$ 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:
- Put the package in a repository named after it, with the entry file and
salam.tomlat the top level (or the folder layout above). - Check it:
salam testpasses, andsalam check slug.salamshows no errors. - Write a
README.mdthat shows how to add and import the package and one short example, and add aLICENSEfile. Without a licence nobody is allowed to use your code. - 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 as | Means |
|---|---|
"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 see | Why, 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.salam | You ran salam build or salam run outside a project. Start one with salam init |
'...' has no main.salam, so there is no program to install | salam install is for programs. A library is used with salam add |
needs Salam X or newer; run: salam upgrade | The 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_TOKENraises GitHub's rate limit for listing tags, andSALAM_NO_GIT=1forces the archive route. SALAM_PKGmoves the download folder, andsalam deps cleanempties it.- If a build seems to use old code, delete the project's
.salam-buildfolder 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.