Janet 1.38.0-73334f3 Documentation
(Other Versions:
1.37.1
1.36.0
1.35.0
1.34.0
1.31.0
1.29.1
1.28.0
1.27.0
1.26.0
1.25.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.0
1.19.0
1.18.1
1.17.1
1.16.1
1.15.0
1.13.1
1.12.2
1.11.1
1.10.1
1.9.1
1.8.1
1.7.0
1.6.0
1.5.1
1.5.0
1.4.0
1.3.1
)
jpm
JPM is a build tool that can be installed along with Janet to help build and install libraries for Janet. The main uses are installing dependencies, compiling C/C++ to native libraries, and other project management tasks. The source code for JPM can be found at https://github.com/janet-lang/jpm.git. With janet already installed, JPM is also self bootstrapping.
git clone --depth=1 https://github.com/janet-lang/jpm.git
cd jpm
sudo janet bootstrap.janet
The bootstrap script can also be configured to install jpm to
different directories by setting the DESTDIR
environment
variable. Ideally, jpm should be installed to the same tree as Janet,
although this is not strictly required. See the README in jpm's
repository for more information.
Updating JPM
Once installed and configured, JPM can update itself from the git repository at any time.
sudo jpm install jpm
jpm
's main functions are installing dependencies and building native
Janet modules, but it is meant to be used for much of the life-cycle for
Janet projects. Since Janet code doesn't usually need to be compiled, you
don't always need jpm
, especially for scripts, but jpm
comes
with some functionality that is difficult to duplicate, like compiling Janet
source code and all imported modules into a statically linked executable for
distribution.
Glossary
A self-contained unit of Janet source code as recognized by jpm
is called a project. A project is a directory containing a
project.janet
file, which contains build recipes. Often, a
project will correspond to a single git repository, and contain a
single library. However, a project's project.janet
file can
contain build recipes for as many libraries, native extensions, and
executables as it wants. Each of these recipes builds an
artifact. Artifacts are the output files that will either be
distributed or installed on the end-user or developer's machine.
Building projects with jpm
Once you have the project on your machine, building the various artifacts should be pretty simple.
Global install
sudo jpm deps
jpm build
jpm test
sudo jpm install
(On Windows, sudo
is not required. Use of sudo
on POSIX systems
depends on whether you installed janet
to a directory owned by the root
user.)
User local install
The JANET_TREE environment variable can be used to set the tree the jpm installs things to.
By default, running janet
from the command line separately will not use modules in
the custom tree, so you will likely want to modify JANET_PATH as well.
export JANET_TREE=$HOME/.local/jpm_tree
jpm deps
jpm build
jpm test
jpm install
# alternative: jpm --tree=$HOME/.local/jpm_tree deps
Project local install
JPM also has some flags to install dependencies to a tree local to a project. Dependencies will
be installed to ./jpm_tree/lib (and binaries installed to ./jpm_tree/bin) when passing the -l
flag to jpm.
jpm -l deps
jpm -l build
jpm -l test
# Run a janet interpreter in the local environment with access to all dependencies installed.
jpm -l janet
Dependencies
jpm deps
is a command that installs Janet libraries that the project
depends on recursively. It will automatically fetch, build, and install all
required dependencies for you. As of August 2019, this only works with git,
which you need to have installed on your machine to install dependencies. If
you don't have git you are free to manually obtain the requisite
dependencies and install them manually with sudo jpm install
.
Building
Next, we use the jpm build
command to build artifacts. All built
artifacts will be created in the build
subdirectory of the current
project. Therefore, it is probably a good idea to exclude the build
directory from source control. For building executables and native modules, you
will need to have a C compiler on your PATH where you run jpm build
. For
POSIX systems, the compiler is cc
.
If you make changes to the source code after building once, jpm
will try
to only rebuild what is needed on a rebuild. If this fails for any reason, you
can delete the entire build directory with jpm clean
to reset things.
Windows
For Windows, the C compiler used by jpm
is cl.exe
, which is part
of MSVC. You can get it with Visual Studio, or standalone with the C and C++
Build Tools from Microsoft. You will then need to run jpm build
in a
Developer Command Prompt, or source vcvars64.bat
in your shell to add
cl.exe
to the PATH.
Testing
Once we have built our software, it is a good idea to test it to verify that it
works on the current machine. jpm test
will run all Janet scripts in the
test
directory of the project and return a non-zero exit code if any
fail.
Installing
Finally, once we have built our software and tested that it works, we can
install it on our system. For an executable, this means copying it to the
bin
directory, and for libraries it means copying them to the global
syspath. You can optionally install into any directory if you don't want to
pollute your system or you don't have permission to write to the directory where
janet
itself was installed. You can specify the path to install modules
to via the --modpath
option, and the path to install binaries to with the
--binpath
option. These need to be given before the subcommand
install
.
The project.janet
file
To create your own software in Janet, it is a good idea to understand what the
project.janet
file is and how it defines rules for building, testing, and
installing software. The code in project.janet
is normal Janet source
code that is run in a special environment.
A project.janet
file is loaded by jpm
and evaluated to create
various recipes, or rules. For example, declare-project
creates several
rules, including "install"
, "build"
, "clean"
, and
"test"
. These are a few of the rules that jpm
expects
project.janet
to create when executed.
Declaring a project
Use the declare-project
as the first declare-
macro towards the
beginning of your project.janet
file. You can also pass in any metadata
about your project that you want, and add dependencies on other Janet projects
here.
(declare-project
:name "mylib" # required
:description "a library that does things" # some example metadata.
# Optional urls to git repositories that contain required artifacts.
:dependencies ["https://github.com/janet-lang/json.git"])
Creating a module
A 100% Janet library is the easiest kind of software to distribute in Janet.
Since it does not need to be built and since installing it means simply moving
the files to a system directory, we only need to specify the files that comprise
the library in project.janet
.
(declare-source
# :source is an array or tuple that can contain
# source files and directories that will be installed.
# Often will just be a single file or single directory.
:source ["mylib.janet"])
For information on writing modules, see the modules docpage.
Creating a native module
Once you have written your C code that defines your native module (see the
embedding page on how to do this), you must declare
it in project.janet
in order for jpm
to build the native modules
for you.
(declare-native
:name "mynative"
:source ["mynative.c" "mysupport.c"]
:embedded ["extra-functions.janet"])
This makes jpm
create a native module called mynative
when
jpm build
is run, the arguments for which should be pretty
straightforward. The :embedded
argument is Janet source code that will be
embedded as an array of bytes directly into the C source code. It is not
recommended to use the :embedded
argument, as one can simply create
multiple artifacts, one for a pure C native module and one for Janet source
code.
Creating an executable
The declaration for an executable file is pretty simple.
(declare-executable
:name "myexec"
:entry "main.janet"
:install true)
jpm
is smart enough to figure out from the one entry file what
libraries and other code your executable depends on, and bundles them into
the final application for you. The final executable will be located at
build/myexec
, or build\myexec.exe
on Windows.
If the optional key-value pair :install true
is specified in the
declare-executable
form, by default, the appropriate jpm install
command will install the resulting executable to the JANET_BINPATH
(but see the jpm man page for further details).
Also note that the entry of an executable file should look different than a
normal Janet script. It should define a main
function that can receive a
variable number of parameters, the command-line arguments. It will be called as
the entry point to your executable.
(import mylib1)
(import mylib2)
# This will be printed when you run `jpm build`
(print "build time!")
(defn main
[& args]
# You can also get command-line arguments through (dyn :args)
(print "args: " ;(interpose ", " args))
(mylib1/do-thing)
(mylib2/do-thing))
It's important to remember that code at the top level will run when you invoke
jpm build
, not at executable runtime. This is because in order to create
the executable, we marshal the main
function of the app and write it to
an image. In order to create the main function, we need to actually compile and
run everything that it references, in the above case mylib1
and
mylib2
.
This has a number of benefits, but the largest is that we only include bytecode
for the functions that our application uses. If we only use one function from a
library of 1000 functions, our final executable will not include the bytecode
for the other 999 functions (unless our one function references some of those
other functions, of course). This feature, called tree-shaking, only works
for Janet code. Native modules will be linked to the final executable statically
in full if they are used at all. A native module is considered "used" if is
imported at any time during jpm build
. This may change, but it is
currently the most reliable way to check if a native modules needs to be linked
into the final executable.
There are some limitations to this approach. Any dynamically required modules
will not always be linked into the final executable. If require
or
import
is not called during jpm build
, then the code will not be
linked into the executable. The module can still be required if it is available
at runtime, though.
For an example Janet executable built with jpm
, see
https://github.com/bakpakin/littleserver.
Other declare-
callables
Some additional declare-
callables are:
declare-bin
Declare a generic file to be installed as an executable. Specify file path via
:main
.declare-binscript
Declare a janet file to be installed as an executable script. Creates a shim on windows. If
:hardcode-syspath
is true, will insert code into the script such that it will run correctly even whenJANET_PATH
is changed. If:is-janet
is truthy, will also automatically insert a correct shebang line if jpm's configuration is set with:auto-shebang
as truthy.declare-headers
Declare headers for a library installation. Installed headers can be used by other native libraries. Specify paths via
:headers
and prefix via:prefix
.declare-manpage
Mark a manpage for installation.
Custom Trees
For per-project or per-user development (as opposed to system-wide development), you can use
custom jpm trees rather than a system default by passing the --tree=<somewhere>
argument
all jpm commands or setting the JANET_TREE
environment variable. This will set the location
where jpm will install modules, headers, scripts, and other data to. For project local development in
a tree ./jpm_tree
, you can use the --local
or -l
shorthand for this.
jpm --tree=/opt/jpm_tree deps
jpm --tree=/opt/jpm_tree install spork
jpm -l deps
jpm -l test
Versioning and Library Bundling
JPM does not do any semantic version resolution at the moment. Instead, it is recommended to make all changes to libraries as backwards-compatible as possible, and release new libraries for breaking changes in almost all cases. For creators of executable programs (versus a library author), it is recommended to use a local tree and lockfiles to pin versions for consistent builds.
As a matter of style, it is also recommended to group small libraries together into "bundles" that are updated, tested, and deployed together. Since Janet libraries are often quite small, the cost of downloading more functionality that one might need isn't particularly high, and JPM can remove unused functions and bindings from generated standalone binaries and images, so there is no runtime cost either. By avoiding a plethora of tiny libraries, users of libraries do not manage as many dependencies, and modules are more likely to work together they can be tested together.
While jpm
may superficially resemble npm
, it is the
author's opinion that it is suited to a different style of
development.