How to Create a Julia Package from Scratch
Master the tools and techniques to create a Julia package.
This post was written by Steven Whitaker.
The Julia programming language is a high-level language that is known, at least in part, for its excellent package manager and outstanding composability. (See another blog post that illustrates this composability.)
Julia makes it super easy for anybody to create their own package. Julia's package manager enables easy development and testing of packages. The ease of package development encourages developers to split reusable chunks of code into individual packages, further enhancing Julia's composability.
In this post, we will learn what comprises a Julia package. We will also discuss tools that automate the creation of packages. Finally, we will talk about the basics of package development and walk through how to publish (register) a package for others to use.
This post assumes you are comfortable navigating the Julia REPL. If you need a refresher, check out our post on the Julia REPL.
Components of a Package
Packages are easy enough to use:
just install them with add PkgName
in the package prompt
and then run using PkgName
in the julia prompt.
But what actually goes into a package?
Packages must follow a specific directory structure and include certain information to be recognized as a package by Julia.
Suppose we are creating a package called PracticePackage.jl.
First, we create a directory called PracticePackage
.
This directory is the package root.
Within the root directory we need a file called Project.toml
and another directory called src
.
The Project.toml
requires the following information:
name = "PracticePackage"
uuid = "11111111-2222-3333-aaaa-bbbbbbbbbbbb"
authors = ["Your Name <youremail@email.com>"]
version = "0.1.0"
uuid
stands for universally unique identifier, and can be generated in Julia withusing UUIDs; uuid4()
. The purpose of a UUID is to allow different packages of the same name to coexist.version
should be set to whatever version is appropriate for your package, typically"0.1.0"
or"1.0.0"
for an initial release. The versioning of Julia packages follows SemVer.- The
Project.toml
will also include information about package dependencies, but more on that later.
The src
directory requires one Julia file
named PracticePackage.jl
that defines a module named PracticePackage
:
module PracticePackage
# Package code goes here.
end
So, the directory structure of the package looks like the following:
PracticePackage
├── Project.toml
└── src
└── PracticePackage.jl
And that's all there is to a package! (Well, at least minimally.)
Some Technicalities
Feel free to skip this section, but if you are curious about some technicalities for what comprises a valid package, read on.
- The
Project.toml
only needs thename
anduuid
fields for Julia to recognize the package. Without theversion
field, Julia treats the version asv0.0.0
.- However, the
version
andauthors
fields are needed to register the package.
- However, the
- The name of the package root directory doesn't matter,
meaning it doesn't have to match the package name.
However, the
name
field inProject.toml
does have to match the name of the module defined insrc/PracticePackage.jl
, and the file name ofsrc/PracticePackage.jl
also has to match.- For example,
we could change the name of the package
by setting
name = "Oops"
inProject.toml
, renamingsrc/PracticePackage.jl
tosrc/Oops.jl
, and definingmodule Oops
in that file. We would not have to rename the package root directory fromPracticePackage
toOops
(though that would be a good idea to avoid confusion).
- For example,
we could change the name of the package
by setting
Automatically Generating Packages
The basic structure of a package is pretty simple, so there ought to be a way to automate it, right? (I mean, who wants to manually generate a UUID?) Good news: package creation can be automated!
Package generate
Command
Julia comes with a generate
package command built-in.
First, change directories
to where the package root directory should live,
then run generate
in the Julia package prompt:
pkg> generate PracticePackage
This command creates the package root directory PracticePackage
and the Project.toml
and src/PracticePackage.jl
files.
Some notes:
- The
Project.toml
is pre-filled with the correct fields and values, including an automatically generated UUID. When I rangenerate
on my computer, it also pre-filled theauthors
field with my name and email from my~/.gitconfig
file. src/PracticePackage.jl
is pre-filled with a definition for the modulePracticePackage
. It also defines a functiongreet
in the module, but typically you will replace that with your own code.
PkgTemplates.jl
The generate
command works fine,
but it's barebones.
For example,
if you are planning on hosting your package on GitHub,
you might want to include a GitHub Action
for continuous integration (CI),
so it would be nice
to automate the creation of the appropriate .yml
file.
This is where PkgTemplates.jl comes in.
PkgTemplates.jl is a normal Julia package,
so install it as usual and run using PkgTemplates
.
Then we can create our PracticePackage.jl:
t = Template(; dir = ".")
t("PracticePackage")
Running this code creates the package with the following directory structure:
PracticePackage
├── .git
│ ⋮
├── .github
│ ├── dependabot.yml
│ └── workflows
│ ├── CI.yml
│ ├── CompatHelper.yml
│ └── TagBot.yml
├── .gitignore
├── LICENSE
├── Manifest.toml
├── Project.toml
├── README.md
├── src
│ └── PracticePackage.jl
└── test
└── runtests.jl
As you can see, PkgTemplates.jl automatically generates a lot of files that aid in following package development best practices, like adding CI and tests.
Note that many options
can be supplied to Template
to customize what files are generated.
See the PkgTemplates.jl docs for all the options.
Basic Package Development
Once your package is set up,
the next step is to actually add code.
Add the functions, types, constants, etc.
that your package needs
directly in the PracticePackage
module in src/PracticePackage.jl
,
or add additional files in the src
directory
and include
them in the module.
(See a previous blog post for more information about modules,
though note that using modules directly works slightly differently
than using packages.)
To add dependencies for your package to use, you will need to activate your project's package environment and then add packages. For example, if you want your package to use the DataFrames.jl package, start Julia and navigate to your package root directory. Then, activate the package environment and add the package:
(@v1.X) pkg> activate .
(PracticePackage) pkg> add DataFrames
After this,
you will be able to include using DataFrames
in your package code
to enable the functionality provided by DataFrames.jl.
Adding packages after activating the package environment
edits the package's Project.toml
file.
It adds a [deps]
section
that lists the added packages and their UUIDs.
In the example above,
adding DataFrames.jl
adds the following lines to the Project.toml
file:
[deps]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
(And (PracticePackage) pkg> rm DataFrames
would remove the DataFrames = ...
line,
so it is best not to edit the [deps]
section manually.)
Finally, to try out your package, activate your package environment (as above) and then load your package as usual:
julia> using PracticePackage # No need to `add PracticePackage` first.
Note that by default Julia will have to be restarted to reload any changes you make to your package code. If you want to avoid restarting Julia whenever you make changes, check out Revise.jl.
Publishing/Registering a Package
Once your package is in working order, it is natural to want to publish the package for others to use.
A package can be published by registering it in a package registry, which basically is a map that tells the Julia package manager where to find a package so it can be downloaded.
The General registry is the largest registry
as well as the default registry used by Julia;
most, if not all, of the most popular open-source packages
(DataFrames.jl, Plots.jl, StaticArrays.jl, ModelingToolkit.jl, etc.)
exist in General.
Once a package is registered in General,
it can be installed with pkg> add PracticePackage
.
(Note that if registering a package is not desired for some reason,
a package can be added via URL, e.g.,
pkg> add https://github.com/username/PracticePackage.jl
,
assuming the package is in a public git repository.
However,
the package manager has limited ability
to manage packages added in this way;
in particular,
managing package versions must be done manually.)
The most common way to register a package in General is to use Registrator.jl as a GitHub App. See the README for detailed instructions, but the process basically boils down to:
- Write/test package code.
- Update the
version
field in theProject.toml
(e.g., to"0.1.0"
or"1.0.0"
for the first registered version). - Add a comment with
@JuliaRegistrator register
to the latest commit that should be included in the registered version of the package.
Note that there are additional steps for preparing a package for publishing that we did not discuss in this post (such as specifying compatible versions of Julia and package dependencies). Refer to the General registry's documentation and links therein for details.
Summary
In this post, we discussed creating Julia packages. We learned what comprises a package, how to automate package creation, and how to register a package in Julia's General registry.
What package development tips do you have? Let us know in the comments below!
Additional Links
- Official Package Creation Docs
- Official Julia documentation on creating packages.
- PkgTemplates.jl Docs
- Documentation for PkgTemplates.jl.
Project.toml
Docs- Documentation for the
Project.toml
file.
- Documentation for the
- Julia General Registry
- GitHub repo (including README) for Julia's General registry.
Cover image background provided by www.proflowers.com at https://www.flickr.com/photos/127365614@N08/16011252136.
Treasure map image source: https://openclipart.org/detail/299283/x-marks-the-spot