posted by Stephen Chang
This post is a summary of a tutorial presented at RacketCon 2017.
It describes how to create a package starting from a single Racket file.
Specifically, this post explains how to:
raco pkg installto install a package,
- create an
info.rktfile containing package metadata, and
- add a package to Racket’s official package catalog.
Packages vs Collections
Before getting started, we first clarify some terminology. (This blog post explains packages and collections in more depth.)
In Racket, a library is just a module (typically in its own file) that exports some bindings. A programmer may import such a module using a (relative or absolute) filepath:
A Racket collection is a “hierarchical group of modules” that are installed, i.e., their source files are copied to a standard location that Racket manages. This allows programmers to use them without worrying about their location on disk:
Finally, a package is Racket’s mechanism for organizing and distributing collections (or parts of collections). A package may contain multiple modules spanning multiple collections. The official Racket package catalog is a popular source of packages, but github repos or even local directories may act as packages as well.
Local Package Installation
It’s best to follow this tutorial with a running example. We’ll use this one. To start,
clone the repo with
cd to the repo root directory.
The rest of the tutorial assumes all files are in a
bestfit.rkt file in our library computes best-fit lines for Racket’s
test-plot.rkt file (in the same directory) uses the
bestfit.rkt libraries, importing them like this:
;; test-plot.rkt (require plot math "bestfit.rkt")
Having to specify a file path in order to use
bestfit.rkt is somewhat brittle, however, since it depends on the exact location of the file on disk. Instead, we can install our library as a collection:
# run from `plot-bestfit/` directory $ raco pkg install
raco pkg install as described above installs the contents of the current directory as a single-collection package, using the directory name as the name of both the package and the collection.
Alternatively, we can give
raco pkg install an explicit path. The following command, executed from the parent directory, is identical to the above command:
# run from parent of `plot-bestfit/` directory # you may need to run `raco pkg remove plot-bestfit` before trying this command # (don't forget the `/`! omitting it will install from the pkg server instead) $ raco pkg install plot-bestfit/
After installing the package/collection,
test-plot.rkt may use a collection path instead of a file path to import
;; test-plot.rkt (require plot math plot-bestfit/bestfit)
The require path is still somewhat cumbersome, however, considering that our entire package consists of a single file. To shorten the path that programmers must write, we may take advantage of the fact that
require implicitly looks for a
main module. Specifically, we add (in the
plot-bestfit/ directory) a
main.rkt file with contents:
Now we may shorten the requires to:
;; test-plot.rkt (require plot math plot-bestfit)
The name of the collection, however, was chosen automatically to match the name of the directory we happened to put our files in. In the next section, we’ll show how programmers can more directly specify package metadata such as the collection name.
Specifying Package Metadata
The Racket package system looks for metadata in an
info.rkt file, if one exists. Let’s create one for our package. Specifically, we add (in the
plot-bestfit/ directory) an
info.rkt file with contents:
;; info.rkt #lang info (define collection "bestfit")
This directs Racket to name our collection
bestfit, instead of using the directory name (the package name will still be the directory name). Thus if we re-install our package:
# remove old installation $ raco pkg remove plot-bestfit # re-install, from the `plot-bestfit/` directory $ raco pkg install
we may import the collection with the new name (note that
test-plot.rkt may emit an eror during the
raco pkg install above if it still uses the old collection name):
;; test-plot.rkt (require plot math bestfit)
info.rkt field is
deps, which specifies other packages on which our package depends. During package installation, Racket will automatically ask to additionally install any such dependencies.
We could add the dependencies ourselves, but an easier way is to use
$ raco setup --fix-pkg-deps bestfit
raco setup repair the
info.rkt looks like:
Package installation additionally looks to compile and register documentation for the package. A
scribblings field in
info.rkt points to the documentation source file.
For example, we might add to our
;; info.rkt (define scribblings '(("scribblings/bestfit.scrbl")))
scribblings entry is a list of lists, where each sublist begins with a documentation source filename and is followed by various options. In our
info.rkt file, we have one sublist that contains only the documentation source file and does not specify any other options.
See the notes from the “Scribbling documentation” tutorial to learn how to write documentation.
When we are done writing our docs, we can use the
raco tools to compile and view them in rendered form.
# re-compiles `bestfit` collection and its docs $ raco setup bestfit # launch browser to view local docs $ raco docs
raco pkg new
When creating a new package, a convenient way to generate stubs for all the files described in this tutorial (and more) is to run:
# produces directory `my-pkg/` which contains the file stubs $ raco pkg new my-pkg
The Racket Package Server
At this point, assume that we’ve created all the files described in this tutorial and we have pushed them to a github repo. We will use this repo as an example: https://github.com/stchang/plot-bestfit.
To distribute our package where others may discover and download it, we can add the package to the Racket package catalog at https://pkgs.racket-lang.org/.
To add a package to the catalog:
- “register” for an account,
- “sign in” to the account,
- click the “add your own” button on the front page,
- and supply the requested information.
If we named our package “my-bestfit-pkg”, then any Racket user may install our package by running:
$ raco pkg install my-bestfit-pkg
Running the above command will look up the package repo from the package catalog, and then download and install the package source files from that repo.
Single- vs Multi-Collection Packages
The package we created in this tutorial consists of a single collection. Alternatively, packages may contain several modules spanning multiple collections.
Such packages must be declared as multi-collection packages by changing the
collection entry in
info.rkt to have value
'multi. This directs Racket package installation to treat each subdirectory in the package as its own collection (or partial collection).
For example, the
drracket package leverages this organization style to implement its toolbar. Specifically, it adds a
tool.rkt module to many different collections, such as the
scribble, in order to access their callback hooks. Observe that in addition to the package’s root
info.rkt, each collection in this kind of package uses its own
info.rkt to specify collection-specific information such as documentation.
Indeed, any package that wishes to add to an existing collection, even if it’s just one collection, should be declared as
'multi. See the
persistent-array package for an example.
A Final Note on Multi-Package Libraries
NOTE: Most programmers will not need to worry about this section.
If you’ve browsed the Racket source files at all, you may have noticed that many core libraries further subdivide their contents into several packages. The organization is typically arranged as:
- a base package
Xwith just an
X-libpackage that contains most of the source files,
X-docpackage with the documentation files,
- and an
X-testpackage with the test files.
In this setup, the
info.rkt in the base
X package typically specifies
X-doc as dependencies. This division enables users to more finely manage dependencies, i.e., a programmer may want to use the main package but may not want to install the tests (and its dependencies).
See the pict package for a concrete example of a library organized in this manner.