Packages and indexes
A package is a Fortran project anyone can use by name: its source lives at a URL (a git repository on any host, or a directory of release archives on any web server), and package indexes list it. There is no central server. An index is a small file that a person, a lab, a vendor or a committee publishes wherever it likes, and FoBiS reads several of them side by side. Indexes help you find a package; they never decide what your build uses.
How it works:
- An index lists sources, not versions. An entry holds the package's source URL (its identity), a description, keywords and the modules it provides. Versions come from the source itself: a git repository's semantic-version tags, or a release manifest. Tagging a release is publishing it.
- A name is resolved once.
fobis add NAMElooks the name up in your indexes and writes the source URL into the fobos[dependencies].fetchandbuildnever read an index: if every index disappears, the project still builds. A name found in two indexes is refused, with theINDEX:NAMEchoices. - Content is identified by hashes. The lock records each git dependency's tree hash and each release archive's sha256. A tag that moves is reported, and a release whose archive changes is refused.
- Packages are built with your compiler.
fobis pkg installbuilds from source and files the result under a toolchain key (compiler family, version, and a digest of the compiler's path, MPI and flags), because module files work only with the compiler that wrote them.
Tutorial: quad as a package, and the whole packages track. Reference: index, search, info, add, publish, pkg, audit.
Find a package
Register an index, then search it by words or by the module you need. fortran-index is a public index of FoBiS and fpm packages, with a browsable catalogue of packages and modules:
fobis index add szaghi https://github.com/szaghi/fortran-indexThe outputs below come from the tutorial's own index:
fobis index add mine ~/my-index
fobis search --module quad_rules
fobis info quad$ fobis index add mine ~/my-index
mine: 1 package(s), dir index
Added index mine = ~/my-index to ~/.config/fobis/config.ini$ fobis search --module quad_rules
mine:quad file://~/remotes/quad.gitfobis info reads the source, not the index: the versions are the source's tags, the modules those of its newest version.
$ fobis info quad
quad (file://~/remotes/quad.git)
index : mine
license : unknown
build : fobis, make
versions : v0.1.0
modules : integrands, quad_rules- Indexes come in priority order. The project fobos's
[indexes]are searched first, then those of your user configuration. Indexes that an indexfollowsare searched too, two levels deep by default (--depth). - Remote indexes are cached in
~/.fobis/indexes(FOBIS_INDEX_DIRmoves it);fobis index updateor--refreshfetches them again.fobis index listshows each index with its scope (project or user) and when it was fetched;fobis index remove NAMEforgets one. searchmatches names, descriptions and keywords;--modulematches the modules of each package's newest version, as the index lists them, and--build fpm(orfobis,cmake,make) keeps the packages built with that build system.- A followed index is named by its own
name(mylab:kinds), with a short suffix when two followed indexes share a name:fobis searchprints the qualified names to use. - Private indexes: a git index is read with your git credentials (an SSH URL, a credential helper), like any clone. A static HTTP index must be public.
fobis index addreads the index once to check it;--no-checkregisters it without reading it (offline).
Add a dependency by name
fobis add quad
fobis add vendor:json --use fobos
fobis add --module stringifor_string_t
fobis add https://github.com/szaghi/PENF --tag v2.0.1$ cd ~/survey && fobis add quad
Added quad = file://~/remotes/quad.git :: semver=^0.1.0
Run 'fobis fetch' to fetch it.[dependencies]
transitive = true
# added by fobis add from index mine
quad = file://~/remotes/quad.git :: semver=^0.1.0- The range starts at the newest version (
semver=^0.1.0);--semver,--tag,--revor--branchchoose otherwise. A source with no version tag is added unpinned, with a warning. - A URL needs no index at all:
fobis add URLreads the source's tags and writes the same kind of entry. - Options of the entry:
--as NAMEnames the dependency in the fobos (default: the package's name, lower-case),--devmakes it test-only,--use fobosbuilds it as a library from its own fobos (--modechooses its mode), and--forcereplaces an entry of the same name. - What the index knows is copied: its
mirrorsbecome:: mirror=URL[,URL], and its recipe is copied into the project (see CMake, make and other packages). - The fobos is edited line by line: its comments and layout are kept, and a comment line records the index the name came from.
- In a FoBiS project
addwrites into the fobos, and the package added can be built with FoBiS, fpm, CMake or make: see how a dependency is built. In an fpm project it writes intofpm.toml: see fpm projects.
Update or remove a dependency
fobis fetch --update
fobis add quad --semver "~0.2" --force
fobis remove quadfetch --updatemoves within the range written in the fobos (semver=^0.1.0: the newest 0.x.y), and records what it chose in.fobis_deps/fobos.lock. Atag=orrev=pin never moves.- To change the range, add the package again with
--force: the entry is replaced, the rest of the fobos kept. fobis remove NAMEtakes the entry out of the fobos, with itsaddcomment and the recipeaddcopied for it. The nextfetchdrops it from the build; its clone stays in.fobis_deps/NAMEuntil you delete it.fetch --frozenchecks every dependency out at its locked commit, for a checkout that keeps.fobis_deps/(see dependencies).
fpm projects
In a project with an fpm.toml and no fobos, add and remove edit fpm.toml (--fpm chooses it when the project has both). The index finds the package; fpm fetches and builds it:
fobis search --build fpm json
fobis add M_CLI2
fpm build
fobis remove M_CLI2[dependencies]
# added by fobis add from index szaghi
M_CLI2 = { git = "https://github.com/urbanjost/M_CLI2", tag = "1.0.0" }- The newest tag is pinned exactly: fpm takes a
tag,revorbranch, not a range, so--semveris refused. To move to a newer version, add the package again with--force.--devwrites into[dev-dependencies]. - The name is the package's own: fpm requires the dependency's key to be the
namein the package'sfpm.toml, soaddreads it there (urbanjost'sM_changerepository isM_match) and refuses--as. - The package must have an fpm.toml at that version: fpm builds nothing else. A package with only a fobos, a CMake or make build, or published as release archives is refused, with the build systems it has.
- fpm does the rest: the package's own dependencies, its build and its lock are fpm's;
transitive,fetch,fobos.lockand the module-collision check are FoBiS's and do not apply. An index entry'smirrorscannot be written (fpm has none). - Comments and layout of
fpm.tomlare kept: an entry is one line. An entry you wrote as a[dependencies.NAME]table, or over several lines, is left to you.
Publish your package in an index
An index is a file named index.toml. Keep it in a git repository, serve it as a static file, or keep it in a directory. fobis publish --index adds or refreshes the entry of the current project:
fobis publish --index ~/my-index/index.toml --source https://github.com/me/quad$ cat ~/my-index/index.toml
schema = 1
[package.quad]
source = "file://~/remotes/quad.git"
provides = ["integrands", "quad_rules"]
builds = ["fobis", "make"]To publish a whole family of projects, list them in a seed index (each entry needs only its source), or take the projects of your ecosystem; index build fills the descriptions, licences and modules from each source:
fobis index build seeds.toml -o index.toml
fobis index build --from-ecosystem --name me -o index.toml
fobis index check index.toml --online- The file format is described in Index format.
publish --indexandindex buildrewrite the whole file, sorted by name: comments of a hand-written index are lost. --from-ecosystempublishes only public repositories. It writes the HTTPS form of each origin and reads every source without your credentials: a private repository is left out, with a warning.- Hand-written fields win when an index is built again (a description, the
provideslist), and an index canfollowother indexes:follows = ["https://example.org/fortran/index.toml"]. - The provided modules are read from the sources: for an fpm package its library directory (
[library] source-dir,src/by default), for the others the whole tree without tests, examples and vendored copies (third_party/,vendor/, ...). A package that bundles another one's sources in its library really defines those modules too: the two cannot be linked into one program, andfetchsays so.
An index for a lab, a company or a project
Anyone can publish an index, and a project can name the indexes it relies on:
[indexes]
# a git repository holding index.toml
lab = https://git.example.org/fortran/index
# a static file
vendor = https://example.org/fortran/index.toml
# a directory, relative to this fobos
local = ./third-party-indexThe same section in the user configuration (~/.config/fobis/config.ini) lists your own; fobis index add and remove edit it. Ambiguous names are never resolved silently: with json in two of them, fobis add json fails and asks for lab:json or vendor:json.
Releases on any static host
fobis publish --archive DIR writes the tarball of the latest tag and its entry in fortran-releases.toml:
$ fobis publish --archive ~/site/quad
Wrote ~/site/quad/quad-0.1.0.tar.gz and its entry in ~/site/quad/fortran-releases.toml (sha256 20d203fc68b21b1a...)$ cat ~/site/quad/fortran-releases.toml
schema = 1
name = "quad"
[[release]]
version = "0.1.0"
archive = "quad-0.1.0.tar.gz"
sha256 = "20d203fc68b21b1accb41a7ba74e5d325710e5f6e26b06f3553884f318f58840"Upload DIR to any web server, object store or shared directory; its URL is then a source like a git repository:
$ fobis info ~/site/quad
quad (~/site/quad)
license : unknown
build : fobis, make
versions : 0.1.0
modules : integrands, quad_rules[dependencies]
kinds = https://example.org/fortran/kinds/ :: semver=^1.0- A published release never changes.
publish --archiverefuses to rewrite a version, andfetchrefuses an archive whose sha256 differs from the one it locked. - An older release:
--version TAGpublishes that tag instead of the latest one;--namesets the package name when the fobos[project]has none. - The manifest is found at the URL itself, at
URL/fortran-releases.tomlor atURL/.well-known/fortran-releases.toml. - A stable URL on your own domain: an HTML page holding
<meta name="fortran-source" content="git https://host/group/repo">sends FoBiS to the real source, so the code can move between hosts while the URL stays.
Dependencies of dependencies
By default fetch reads your fobos only. With transitive = true it also reads the [dependencies] of each dependency and fetches them:
[modes]
modes = gnu
[gnu]
compiler = gnu
src = ./src/
build_dir = ./build/
target = src/survey.F90
[dependencies]
transitive = true$ cd ~/survey && fobis fetch
Cloning quad from file://~/remotes/quad.git
Checking out v0.1.0 for quad
Cloning penf from https://github.com/szaghi/PENF
Checking out v2.0.1 for penf
penf: v2.0.1 (required by quad)
Saved deps config to .fobis_deps/.deps_config.ini
Saved lock file to .fobis_deps/fobos.lock$ cd ~/survey && grep -E "^\\[|resolved|required_by" .fobis_deps/fobos.lock
[quad]
resolved = v0.1.0
[penf]
resolved = v2.0.1
required_by = quad- Each range is a minimum (Minimal Version Selection): every package gets the highest of the minimum versions required anywhere, so the answer depends on the fobos files only, never on what was published since. A range that excludes the chosen version is an error naming both requirers.
- An exact pin of your project wins (
tag=,branch=,rev=): a dependency's requirement it violates is a warning. - Test-only and path dependencies of a dependency are not followed, nor the dependencies of a release-manifest source. The dependencies of an fpm package are fetched by fpm (see how a dependency is built).
- Two dependencies defining the same module are reported by every
fetchbefore anything compiles: they could not be linked into one program.
When a module is missing, fobis check and fobis build --explain name the package of your indexes that provides it, with the fobis add INDEX:NAME command that adds it (read from the cached indexes: no network).
How a dependency is built
fetch builds each dependency with its own build system, using the compiler of your project's default mode, and build links what it installed:
| The dependency has | It is built by |
|---|---|
a recipe= (yours, or copied from an index by add) | the recipe |
| a fobos | FoBiS, as part of your project (use=sources) or as a library (use=fobos) |
an fpm.toml and no fobos | fpm itself: fpm build --profile release --compiler <your compiler> |
a CMakeLists.txt or a Makefile only | CMake or make (see below) |
An fpm package is installed into .fobis_deps/.prefix/NAME: its library archive and module files. Your build compiles none of its sources and none of its programs.
- fpm is required for a dependency that has no fobos: without fpm on PATH,
fetchfails and says so. FoBiS never compiles an fpm package itself. - Its own dependencies come with it: fpm fetches what the package's
fpm.tomlrequires and puts their objects in the package's archive. FoBiS does not see them:fobos.lockand--frozenpin the package, not what fpm fetched for it. - The same module twice is an error: when your fobos also fetches a package that an fpm dependency brings with it,
fetchnames both and fails before anything compiles. Keep one of them. - Only the compiler is passed to fpm, with the release profile and
-fPIC: the module files are your compiler's. Your mode's other flags are not, so a flag changing the binary interface (-fdefault-real-8) does not reach an fpm dependency. - Examples and programs are not built: fpm builds the library from a copy of the package whose manifest drops them, so a demo that does not compile cannot stop the library.
Install a package for your compiler
fobis pkg install quad
fobis pkg install json-fortran --version ^9 --compiler intel_nextgen
fobis pkg list
fobis pkg upgrade
fobis pkg uninstall quad$ fobis pkg install quad --bin-dir ~/bin
Building quad v0.1.0 with gfortran (gnu-13.4.0-de7b0728), fobis
Installed quad v0.1.0 for gnu-13.4.0-de7b0728 in ~/.fobis/pkgs/gnu-13.4.0-de7b0728/quad/0.1.0
modules : quad_rules
use it : export PKG_CONFIG_PATH=~/.fobis/pkgs/gnu-13.4.0-de7b0728/quad/0.1.0/lib/pkgconfigEvery library gets a pkg-config file, so a FoBiS, CMake or make project links it the same way:
$ PKG_CONFIG_PATH=$(fobis pkg path quad --pkgconfig) pkg-config --cflags --libs quad
-I~/.fobis/pkgs/gnu-13.4.0-de7b0728/quad/0.1.0/include -L~/.fobis/pkgs/gnu-13.4.0-de7b0728/quad/0.1.0/lib -lquadfind_package(PkgConfig REQUIRED)
pkg_check_modules(QUAD REQUIRED IMPORTED_TARGET quad)
target_link_libraries(survey PkgConfig::QUAD)From a make or CMake project of your own, fobis pkg path gives the directories to use:
export PKG_CONFIG_PATH=$(fobis pkg path quad --pkgconfig)
gfortran main.f90 $(pkg-config --cflags --libs quad)- Choosing what is built:
--versiontakes a range (^9) or an exact tag;--compiler,--fc PATH(a compiler binary),--mpi(the MPI wrapper),--cflagsand--lflagsset the toolchain;--modepicks the mode of a FoBiS package's fobos.--forcerebuilds an installed package,--verboseshows the build's own output. - Several toolchains: a package installed for two keys is chosen with
--keybypkg pathandpkg uninstall;fobis pkg listshows every key. - The pkg-config name is the library's, not always the package's:
ls $(fobis pkg path NAME --pkgconfig)lists the.pcfiles (stdlib installs one per component andfortran_stdlib.pcfor all of them). - The store is
~/.fobis/pkgs/<key>/<name>/<version>/(FOBIS_PKGS_DIRmoves it). Installing with other flags (--cflags,--mpi, another compiler) gives another key: both stay installed. - Applications are linked into
~/.local/bin(--bin-dir), never over a file that is not one of their links. Tests and examples are never installed. uninstallremoves exactly what the install wrote, recorded inreceipts.json.upgradestays within the range you asked for and on the same toolchain.- FoBiS packages are built in the mode of their fobos that uses your compiler family (a static library first), after
fobis fetchof their own dependencies. fpm packages are built by fpm, which must be on PATH: the library first, then the applications withfpm install(an application that does not compile is a warning, the library stays installed). - Shared libraries: the generated
.pcfiles carry no run path (as usual for pkg-config). Link--static, or setLD_LIBRARY_PATHto the library directory. fobis install REPOis unchanged: it installs into a prefix and keeps no record.
CMake, make and other packages
A package without a fobos or an fpm.toml is built from a recipe. The recipe is shipped by the package (fortran-recipe.toml at its root) or named by an index entry (recipe = "recipes/NAME.toml"):
schema = 1
backend = "cmake"
cmake-args = ["-DENABLE_TESTS=OFF"]
depends = ["https://github.com/szaghi/PENF :: semver=^2"]backend = "make" takes make-args; backend = "recipe" takes steps, shell lines with the placeholders {fc}, {cc}, {fflags}, {lflags}, {prefix}, {jobs}, {src} and {build}. Without any recipe, a CMakeLists.txt or Makefile is used as found.
- Your compiler is always passed (
-DCMAKE_Fortran_COMPILER,FC=): the module files must be its own. fobis addcopies an index's recipe into the project (recipes/NAME.toml,:: recipe=recipes/NAME.tomlin the spec), so that building never depends on the index.- In a FoBiS project,
fetchbuilds such a dependency with the compiler of the default mode into.fobis_deps/.prefix/NAME, andbuildadds its module directories and libraries. - Module files: CMake installs none by default, and many Makefiles have no install rule. FoBiS then collects the libraries, module files and programs the build produced.
- Install layouts vary. Libraries, module files and
.pcfiles are found wherever they were installed, and a package's.pcwhose directories do not exist (json-fortran 9.3.1 installs one) is replaced by one FoBiS writes. - External libraries (MPI, HDF5, BLAS) are never built: a recipe lists them in
externals, and the package's own build finds them.
Offline, or on a cluster
What needs a network, and what does not:
| Command | Network |
|---|---|
search, --module hints of check and build --explain | no, once the indexes are cached |
info, add | yes: they read the source's tags |
fetch | yes, unless every dependency is already fetched |
build | never: it reads .fobis_deps/ |
pkg install | yes: it reads and builds the source |
For a machine with no network at all, keep everything on a shared filesystem: an index in a directory (fobis index add lab /shared/fortran/index), releases published with publish --archive into another directory, and FoBiS itself as the single file fobis.pyz attached to each GitHub release (python3 fobis.pyz build). Those directories are sources like any URL: add, fetch and pkg install read them without a network.
Scripts and agents
search, info, add, remove, index list, index check, pkg install, pkg list and audit print JSON with --json, and only JSON on stdout. An AI agent connected to fobis mcp has three read-only tools for packages: package_search, package_info and index_check.
Trust: locks, mirrors, advisories, archives
[dependencies]
penf = https://github.com/szaghi/PENF :: tag=v2.0.1 :: mirror=https://gitlab.com/me/PENF :: archive=swhfobis audit
fobis audit --witness$ cd ~/survey && fobis audit
quad v0.1.0 ok
penf v2.0.1 okMirrors (
mirror=URL[,URL]) are cloned in order when the source fails. A mirror needs no trust: the lock's tree hash checks what it serves.Advisories have no central database. A package publishes
fortran-advisories.tomlon its default branch, andfobis auditreports those affecting the versions your lock records (exit 1):toml# an example, for a fictitious package schema = 1 [[advisory]] id = "KINDS-2026-001" affected = "<1.1.0" severity = "high" summary = "wrong kind selected for 128-bit reals" url = "https://example.org/kinds/issues/1"Witnesses:
fobis audit --witnesscompares the commit each locked tag names at the source, at every mirror and in the lock. A disagreement means the tag was moved somewhere.Software Heritage (
archive=swh): when the source and its mirrors are gone,fetchdownloads the tag from Software Heritage. The copy is accepted only when its tree equals the one in the lock (Software Heritage names directories like git trees). It holds only what its last visit of the repository saw.Advisories, witnesses and archives need a network, and the advisory channel is only as good as the package's maintainers: this is the cost of having no central service.