This document covers the essentials of developing for the standard library.
If this is your first time contributing, first read everything in CONTRIBUTING.md. Logistically, you need to do the following:
And if you're using VS Code:
To build the standard library, you can run the
build-stdlib.sh
script from the
mojo/stdlib/scripts/
directory. This will create a build artifacts directory,
build
, in the top-level of the repo and produce the stdlib.mojopkg
inside.
./stdlib/scripts/build-stdlib.sh
To run the unit tests, you first need to install lit
:
python3 -m pip install lit
And make sure that FileCheck
from LLVM is on path. If your are on macOS, you
can brew install llvm
and add it to your path in ~/.zshrc
or ~/.bashrc
:
export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
If you are on Ubuntu you can:
sudo apt update
sudo apt install llvm
And it will be available in PATH
.
In the near future, we will be moving away from FileCheck
in favor of writing
the unit tests using our own testing
module and remove this dependency
requirement for contributors. We are happy to welcome contributions in this
area!
We provide a simple Bash script to build the standard library package and
test_utils
package that is used by the test suite.
Just run ./stdlib/scripts/run-tests.sh
which will produce the necessary
mojopkg
files inside your build
directory, and then run lit -sv stdlib/test
.
./stdlib/scripts/run-tests.sh
lit -sv stdlib/test
All the tests should pass on the nightly
branch with the nightly Mojo
compiler. If you've pulled the latest changes and they're still failing please
open a GitHub
issue.
If you’d like to run just a subset of the tests, feel free to use all of the
normal options that the lit
tool provides. For example, to run just the
builtin
and collections
tests:
lit -sv stdlib/test/builtin stdlib/test/collections
This can quickly speed up your iteration when doing development to avoid running the entire test suite if you know your changes are only affecting a particular area. We recommend running the entire test suite before submitting a Pull Request.
Reminder that if you’re choosing to invoke lit
directly and not use the
run-tests.sh
, you need to ensure your stdlib.mojopkg
and
test_utils.mojopkg
are up-to-date. We’re not currently imposing any build
system right now to ensure these dependencies are up-to-date before running the
tests.
If you run into any issues when running the tests, please file an issue and we’ll take a look.
Please make sure your changes are formatted before submitting a pull request.
Otherwise, CI will fail in its lint and formatting checks. The mojo
compiler
provides a format
command. So, you can format your changes like so:
mojo format ./
It is advised, to avoid forgetting, to set-up pre-commit
, which will format
your changes automatically at each commit, and will also ensure that you
always have the latest linting tools applied.
To do so, install pre-commit:
pip install pre-commit
pre-commit install
and that's it!
If you need to manually apply the pre-commit
, for example, if you
made a commit with the github UI, you can do pre-commit run --all-files
,
and it will apply the formatting to all Mojo files.
You can also consider setting up your editor to automatically format Mojo files upon saving.
Here is a complete walkthrough, showing how to make a change to the Mojo standard library, test it, and raise a PR.
First, follow everything in the prerequisites.
IMPORTANT We'll be in the mojo/stdlib
folder for this tutorial, check and
make sure you're in that location if anything goes wrong:
cd [path-to-repo]/stdlib
Let's try adding a small piece of functionality to path.mojo
:
# ./stdlib/src/pathlib/path.mojo
fn print_cwd() raises:
print("cwd:", cwd())
And make sure you're importing it from the module root:
# ./stdblib/src/pathlib/__init__.mojo
from .path import (
DIR_SEPARATOR,
cwd,
print_cwd,
Path,
)
Now you can create a temporary file named main.mojo
for trying out the new
behavior. You wouldn't commit this file, it's just to experiment with the
functionality before you write tests:
# ./stdlib/main.mojo
from src import pathlib
def main():
pathlib.print_cwd()
Now when you run mojo main.mojo
it'll reflect the changes:
cwd: /Users/jack/src/mojo/stdlib
Here's a more tricky example that modifies multiple standard library files that depend on each other.
Try adding this to os.mojo
, which depends on what we just added to
pathlib.mojo
:
# ./stdlib/src/os/os.mojo
import pathlib
fn print_paths() raises:
pathlib.print_cwd()
for path in cwd().listdir():
print(path[])
This won't work because it's importing pathlib
from the stdlib.mojopkg
that
comes with Mojo. We'll need to build our just-modified standard library running
the command:
./scripts/build-stdlib.sh
This builds the standard library and places it at the root of the repo in
../build/stdlib.mojopkg
. Now we can edit main.mojo
to use the normal import
syntax:
import os
def main():
os.print_paths()
We also need to set the following environment variable that tells Mojo to prioritize imports from the standard library we just built, over the one that ships with Mojo:
MODULAR_MOJO_NIGHTLY_IMPORT_PATH=../build mojo main.mojo
Which now outputs:
cwd: /Users/jack/src/mojo/stdlib
main.mojo
test
docs
scripts
src
If you like a fast feedback loop, try installing the file watcher entr
, which
you can get with sudo apt install entr
on Linux, or brew install entr
on
macOS. Now run:
export MODULAR_MOJO_NIGHTLY_IMPORT_PATH=../build
ls **/*.mojo | entr sh -c "./scripts/build-stdlib.sh && mojo main.mojo"
Now, every time you save a Mojo file, it packages the standard library and
runs main.mojo
.
If you haven't already, follow the steps at: Installing unit test dependencies
This will show you how the FileCheck
utility works. First, turn it on by
adding it to the end of line 7 in ./stdlib/test/pathlib/test_pathlib.mojo
:
# RUN: %mojo -debug-level full -D TEMP_FILE=%t %s | FileCheck %s
Now we can add the test and force it to fail:
# CHECK-LABEL: test_print_cwd
def test_print_cwd():
print("== test_print_cwd")
# CHECK: This will fail
print_cwd()
Don't forget to call it from main()
:
def main():
test_print_cwd()
Now, instead of testing all the modules, we can just test pathlib
:
lit -sv test/pathlib
This will give you an error showing exactly what went wrong:
/Users/jack/src/mojo-toy-2/stdlib/test/pathlib/test_pathlib.mojo:27:11:
error: CHECK: expected string not found in input
# CHECK: This will fail
^
<stdin>:1:18: note: scanning from here
== test_print_cwd
^
Lets fix the test that we just added:
# CHECK-LABEL: test_print_cwd
def test_print_cwd():
print("== test_print_cwd")
# CHECK: cwd:
print_cwd()
We're now checking that print_cwd
is prefixed with cwd:
just like the
function we added. Run the test again:
Testing Time: 0.65s
Total Discovered Tests: 1
Passed: 1 (100.00%)
Success! Now we have a test for our new function.
The last step is to run mojo format
on all the files.
This can be skipped if pre-commit
is installed.
Make sure that you've had a look at all the materials from the standard library README.md. This change wouldn't be accepted because it's missing tests, and doesn't add useful functionality that warrants new functions. If you did have a worthwhile change you wanted to raise, follow the steps to create a pull request.
Congratulations! You've now got an idea on how to contribute to the standard library, test your changes, and raise a PR.
If you're still having troubles make sure to reach out on GitHub or Discord!