Tomo Package Design
In Tomo, the use statement serves multiple purposes. The first purpose is for
local imports. However, the more complicated case is for
package imports. Packages in Tomo are a bundle of source files that define
their own namespace and symbols that can be used by other code.
When you type
use mypackage
Tomo will try to figure out where to get the package, download it from there (with user confirmation), check its authenticity, build the package, and link your code against it.
Using Packages
In a particular project, there may be many files that want to use a package.
Each file that uses the package must include use packagename or alias := use packagename at the top level of the file.
If a package is used without an alias, all the package's symbols are visible
within the file. This is the equivalent of from packagename import * in a
language like Python. If an alias is used, then all the package's symbols must
be accessed through the alias, like alias.foo().
Installing Packages
When you put use foo in a Tomo source file, Tomo needs to know where to
find foo. In order to find it, Tomo will search through the following .ini
files in order:
filename.tm.packages.ini: A per-file package config, sofoo.tmwould havefoo.tm.packages.ini. This should only rarely be used.packages.ini: A package config that is shared for every file in a directory.- A system-wide
packages.inidistributed with the Tomo compiler (default location:~/.local/lib/tomo@version/packages.ini).
The format of a packages.ini file looks like this:
[packagename]
digest=sha256:2551b5ecc2617d884d856764a7f4f5ba394e97aaf1412110fe63f137b510f546
source=https://example.com/tomo/package-v1.2.3.tar.gz
source-2=https://mirror.example.com/tomo/package-v1.2.3.tar.gz
source-3=https://mirror2.example.com/tomo/package-v1.2.3.tar.gz
The important features are:
- The package name in square brackets. This is used to map
usestatements to the corresponding package. - A hash digest (optional). This is a hash value used to check package integrity when installing a package. See Package Digests for more info.
- One or more sources. See Package Sources for more info.
Each key/value pair is separated by an equals sign. Additional fields may be provided, and are ignored.
Package Digests
A package is canonically defined by its digest. The digest is a cryptographic
hash digest of the archived source file for the package. The default digest
algorithm used is SHA-256. When you put a digest in your packages.ini file,
you define precisely what code you are depending on. Tomo does not have a
notion of package versioning or package registries other than the digest.
However, it is easy to play nicely with whichever versioning and distribution
system you prefer to use.
You do not need to manually compute your own digests. When adding a project
dependency, you may list a package with just a source and no digest. Running
the project with tomo will cause Tomo to download the package and save its
digest to packages.ini so you can be sure future users will get the same
version. You can think of this as a combined package file and lockfile. See
Package Sources for more information.
The philosophy of package digests is rooted in reproducibility and security.
Because Tomo does not have a centrally managed package repository and does not
plan to build one, we need a way for someone to publish a piece of Tomo code
that relies on a package and have anyone be able to build that code. The code
should build as consistently as possible over time, with no breaking changes or
issues with local version incompatibilities. If the author of a piece of code
knows that the code works with the package whose source code hash is X, then
all users can be confident that if they have a package whose source code has is
also X, then it can be used without issues, regardless of where they got it.
Package Sources
When you run a piece of Tomo code for the first time, Tomo will look to see if
you have the necessary packages installed. To do so, Tomo will look for
~/.local/lib/tomo@<tomo-version>/<package-digest>. If not, then Tomo will
look for sources listed in your packages.ini files. Sources are enumerated
as source, source-2, source-3, and so on. Each will be checked in order
to find a valid version of the package.
For each source, Tomo will perform the following steps:
- A source archive is downloaded from the given source:
a. If the source is a file path (e.g.
./mypackage.tar.gz), then the archive from the local filesystem is used. b. If the source is a URI (e.g.https://example.com/mypackage-v1.2.3.tar.gzorftp://example.com/...), then cURL is used to download the archive file to a temporary directory. Tomo will prompt for user confirmation before downloading the file. - If a digest is provided in your
packages.ini, then the archive file's digest is computed and compared to the digest inpackages.ini. If there is any mismatch, an error will be raised and the process will exit. If you experience a digest mismatch, you should consider removing the source that provided it from your sources list, as it may be compromised. - If a digest is not provided in your
packages.ini, Tomo will compute a digest for the newly downloaded file and save it to yourpackages.inifile so that all future compilations will know what the digest must be. - The source archive will be extracted to
~/.local/lib/tomo@<tomo-version>/<package-digest>and compiled withtomo -L.
Package sources are not tied to any single distribution channel by design. You can host your packages on GitHub, BitBucket, GitLab, your own personal webserver, a cloud bucket, an FTP server in your closet, a vendored archive in your repository, a floppy disk, whatever you like. For your end users, the experience should be seamless, without being tied to a single point of failure or tech monopoly. If one of your sources becomes unavailable, Tomo will continue down the line, trying each source until it gets one that can provide a source archive with the right hash.
Local Directory Packages
As a special case, Tomo also permits using local directories as sources. If a
source is listed as a file path to a local directory (instead of an archive
file), Tomo will use the code in that directory for the package without saving
a digest or installing the package in ~/.local/lib/tomo@<tomo-version>. This
makes it possible to easily vendor a dependency (e.g. with git submodules).
[mypackage]
source = ./vendor/mypackage
If a digest is provided for a local directory package, Tomo will give you an error, because digests can only be computed for files, not directories. When compiling a package, Tomo produces build files which would affect the hash digest of the directory, making it difficult to compute a correct hash. Additionally, it is much better as a developer to be able to make local edits to a vendored package without having to worry about digest mismatches.
Package Versioning
Tomo's package design is deliberately version-agnostic. If you want to use a versioning system for distributing a package (a very reasonable goal), then the recommended practice is to use the source URL to convey versioning:
[mylib]
source = https://example.com/mypackage/mypackage-v1.2.3.tar.gz
source-2 = https://mirror.example.com/mypackage/mypackage-v1.2.3.tar.gz
To upgrade a dependency, simply delete the digest from your packages.ini,
update the source URL, and rebuild. Tomo will update the digest for you. In
this way, you may use any versioning system you like, not limited to semantic
versioning.
When you upgrade a dependency and rebuild, Tomo will pull in the upgraded
version of the dependency's source code, including its packages.ini files,
which will trigger a chain reaction of downloading the correct versions of its
dependencies, and so on.
Package Coresidence
Because pacakges are installed to unique locations based on their source code
hashes, it is possible for your project to install different versions of the
same package through transitive dependencies. For example, if you use package
foo and package baz and foo relies on commonlib-v1.2 while package
baz relies on commonlib-v1.3, then Tomo will dutifully install and link
both versions of commonlib, without any problems. Left unchecked, this can
result in a bloated binary with multiple versions of many libraries. If this is
a problem that you are experiencing, you may wish to consider synchronizing
your dependencies so they depend on the same common library (e.g. upgrading
foo in this example) or considering pruning your dependency tree to reduce
the number of transitive dependencies in your project.
Creating a Package Archive
A Tomo package source archive may be any of the following formats:
.tar.gzor.tgz.tar.xzor.txz.tar.zip
The contents of the archive should be either the files in your package, or a single folder containing the files in your package. Tomo will handle both.
Tomo package archives can be created any way you like, but here are some easy options:
- If you are using git, then
git archive --format=tar.gz -o mypackage.tar.gz - If you are using GitHub, then when you create a tag and push to GitHub, a
source archive will be created automatically at
https://github.com/<org>/<repo>/archive/refs/tags/<tagname>.tar.gz - Run
tar -czf mypackage.tar.gz --exclude='*/.*' /path/to/project
1 # Tomo Package Design5 package imports. Packages in Tomo are a bundle of source files that define6 their own namespace and symbols that can be used by other code.8 When you type11 use mypackage12 ```14 Tomo will try to figure out where to get the package, download it from there15 (with user confirmation), check its authenticity, build the package, and link16 your code against it.18 ## Using Packages20 In a particular project, there may be many files that want to use a package.24 If a package is used without an alias, all the package's symbols are visible26 language like Python. If an alias is used, then all the package's symbols must29 ## Installing Packages33 files in order:38 directory.44 ```45 [packagename]46 digest=sha256:2551b5ecc2617d884d856764a7f4f5ba394e97aaf1412110fe63f137b510f54647 source=https://example.com/tomo/package-v1.2.3.tar.gz48 source-2=https://mirror.example.com/tomo/package-v1.2.3.tar.gz49 source-3=https://mirror2.example.com/tomo/package-v1.2.3.tar.gz50 ```52 The important features are:55 the corresponding package.56 2. A hash digest (optional). This is a hash value used to check package58 for more info.61 Each key/value pair is separated by an equals sign. Additional fields may be62 provided, and are ignored.64 ## Package Digests66 A package is canonically defined by its digest. The digest is a cryptographic67 hash digest of the archived source file for the package. The default digest70 notion of package versioning or package registries other than the digest.71 However, it is easy to play nicely with whichever versioning and distribution72 system you prefer to use.74 You do not need to manually compute your own digests. When adding a project75 dependency, you may list a package with just a source and no digest. Running78 version. You can think of this as a combined package file and lockfile. See81 The philosophy of package digests is rooted in reproducibility and security.82 Because Tomo does not have a centrally managed package repository and does not83 plan to build one, we need a way for someone to publish a piece of Tomo code84 that relies on a package and have anyone be able to build that code. The code85 should build as consistently as possible over time, with no breaking changes or86 issues with local version incompatibilities. If the author of a piece of code88 all users can be confident that if they have a package whose source code has is92 ## Package Sources94 When you run a piece of Tomo code for the first time, Tomo will look to see if95 you have the necessary packages installed. To do so, Tomo will look for99 to find a valid version of the package.101 For each source, Tomo will perform the following steps:103 1. A source archive is downloaded from the given source:105 from the local filesystem is used.108 download the archive file to a temporary directory. Tomo will prompt for109 user confirmation before downloading the file.112 any mismatch, an error will be raised and the process will exit. If you113 experience a digest mismatch, you should consider removing the source that114 provided it from your sources list, as it may be compromised.117 so that all future compilations will know what the digest must be.121 Package sources are not tied to any single distribution channel by design. You122 can host your packages on GitHub, BitBucket, GitLab, your own personal123 webserver, a cloud bucket, an FTP server in your closet, a vendored archive in124 your repository, a floppy disk, whatever you like. For your end users, the125 experience should be seamless, without being tied to a single point of failure126 or tech monopoly. If one of your sources becomes unavailable, Tomo will127 continue down the line, trying each source until it gets one that can provide128 a source archive with the right hash.130 ### Local Directory Packages132 As a special case, Tomo also permits using local directories as sources. If a133 source is listed as a file path to a local directory (instead of an archive134 file), Tomo will use the code in that directory for the package without saving136 makes it possible to easily vendor a dependency (e.g. with git submodules).138 ```139 [mypackage]140 source = ./vendor/mypackage141 ```143 If a digest is provided for a local directory package, Tomo will give you an144 error, because digests can only be computed for files, not directories. When145 compiling a package, Tomo produces build files which would affect the hash146 digest of the directory, making it difficult to compute a correct hash.147 Additionally, it is much better as a developer to be able to make local edits148 to a vendored package without having to worry about digest mismatches.150 ## Package Versioning152 Tomo's package design is deliberately version-agnostic. If you want to use a153 versioning system for distributing a package (a very reasonable goal), then154 the recommended practice is to use the source URL to convey versioning:156 ```157 [mylib]158 source = https://example.com/mypackage/mypackage-v1.2.3.tar.gz159 source-2 = https://mirror.example.com/mypackage/mypackage-v1.2.3.tar.gz160 ```163 update the source URL, and rebuild. Tomo will update the digest for you. In164 this way, you may use any versioning system you like, not limited to semantic165 versioning.167 When you upgrade a dependency and rebuild, Tomo will pull in the upgraded169 which will trigger a chain reaction of downloading the correct versions of its170 dependencies, and so on.172 ## Package Coresidence174 Because pacakges are installed to unique locations based on their source code175 hashes, it is possible for your project to install different versions of the176 same package through transitive dependencies. For example, if you use package180 result in a bloated binary with multiple versions of many libraries. If this is181 a problem that you are experiencing, you may wish to consider synchronizing182 your dependencies so they depend on the same common library (e.g. upgrading184 the number of transitive dependencies in your project.186 ## Creating a Package Archive188 A Tomo package source archive may be any of the following formats:195 The contents of the archive should be either the files in your package, or a196 single folder containing the files in your package. Tomo will handle both.198 Tomo package archives can be created any way you like, but here are some easy199 options:202 - If you are using GitHub, then when you create a tag and push to GitHub, a203 source archive will be created automatically at