This document covers the essentials of developing for the standard library.
If this is your first time contributing, first read everything in CONTRIBUTING.md.
To get started, you need to do the following:
-
If you're using VS Code, Install the nightly Mojo VS Code extension
NOTE: You can only have one Mojo extension enabled at a time, remember to switch when using the stable release!
-
Install the nightly Mojo compiler:
We recommend using
pixi, which you can install with this command:curl -fsSL https://pixi.sh/install.sh | shThen create a new project environment like this and it will install the latest nightly version of
mojo(the CLI compiler) by default:pixi init my-project \ -c https://conda.modular.com/max-nightly/ -c conda-forge \ && cd my-project
pixi add modular
Now you're ready to start developing.
Or, instead of setting up with pixi as shown above, you can use an
externally maintained
Mojo Dev Container with all
prerequisites installed.
The Dev Container also includes the unit test dependencies lit and FileCheck
(from LLVM) and has pre-commit already set up.
See Mojo Dev Container > Usage on how to use with Github Codespaces or VS Code.
If there is a problem with the Dev Container, please open an issue here.
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.shTo run the unit tests, you first need to install lit:
python3 -m pip install litAnd 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 llvmAnd 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, after this is done, the script will
run all the tests automatically.
./stdlib/scripts/run-tests.shNote that tests are run with -D ASSERT=all.
If you wish to run the unit tests that are in a specific test file, you can do so with
./stdlib/scripts/run-tests.sh ./stdlib/test/utils/test_span.mojoYou can do the same for a directory with
./stdlib/scripts/run-tests.sh ./stdlib/test/utilsAll the tests should pass on the main 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/collectionsThis 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.
This is rarely useful, but if you wish to run the unit tests with assertions
disabled, you can set the environment
variable MOJO_ENABLE_ASSERTIONS_IN_TESTS=0.
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 installand 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 get_cwd_message() raises -> String:
return "Your cwd is: " + cwd()And make sure you're importing it from the module root:
# ./stdblib/src/pathlib/__init__.mojo
from .path import (
DIR_SEPARATOR,
cwd,
get_cwd_message,
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():
print(pathlib.get_cwd_message())Now when you run mojo main.mojo it'll reflect the changes:
Your cwd is: /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 get_cwd_and_paths() raises -> List[String]:
result = List[String]()
result.append(pathlib.get_cwd_message())
for path in cwd().listdir():
result.append(String(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.shThis 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():
all_paths = os.get_cwd_and_paths()
print(all_paths.__str__())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_MAX_IMPORT_PATH=../build mojo main.mojoWhich 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_MAX_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.
Note: you should stop entr while doing commits, otherwise you could have
some issues, this is because some pre-commit hooks use mojo scripts
and will try to load the standard library while it is being compiled by entr.
If you haven't already, follow the steps at: Installing unit test dependencies
This section will show you how to add a unit test.
Add the following at the top of ./stdlib/test/pathlib/test_pathlib.mojo:
# RUN: %mojo %sNow we can add the test and force it to fail:
def test_get_cwd_message():
assert_equal(get_cwd_message(), "Some random text")Don't forget to call it from main():
def main():
test_get_cwd_message()Now, instead of testing all the modules, we can just test pathlib:
lit -sv test/pathlibThis will give you an error showing exactly what went wrong:
/Users/jack/src/mojo-toy-2/stdlib/test/pathlib/test_pathlib.mojo:27:11:
AssertionError: `left == right` comparison failed:
left: Your cwd is: /Users/jack/src/mojo/stdlib
right: Some random text
Lets fix the test that we just added:
def test_get_cwd_message():
assert_true(get_cwd_message().startswith("Your cwd is:"))We're now checking that get_cwd_message is prefixed with Your cwd is: 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 issues, reach out on Discord.