Very recently, we announced the open sourcing of the Mojo standard library. This has marked a significant milestone for our community, not only providing the best way to understand the implementation details of various functionalities within the standard library but also creating an excellent opportunity to contribute to Mojo. Since the announcement, we have been fortunate to receive a range of contributions, from documentation fixes to code changes. To better foster contributions from our community, this blog post will guide you through the step-by-step process of contributing to the Mojo standard library, including finding a GitHub issue, initial Git setup, writing code, performing local testing and finally creating a pull request (PR) and getting it merged. Please also refer to our contribution guide that contains detailed information, including Mojo code of conduct and style guide , not covered in this post.
Contributing to Mojo can take many forms; from participating in discussions and identifying or reporting issues, to proposing language changes through RFCs. In this context, we will focus on code contributions, which involves a series of steps that we are going to cover next.
Contribution steps: from issue to pull request If you are new to Mojo, begin with the quick start and familiarize yourself with the Mojo documentation . This post assumes a foundational understanding of Mojo.
First, it is important to note that the Mojo standard library is hosted on GitHub and we follow the Git pull request workflow for code contribution so basic knowledge of Git is essential. Secondly, the Mojo standard library maintains two branches: main and nightly . The main branch contains the latest released version of Mojo and is updated with each release. In contrast, the nightly branch holds all the new unreleased changes to the codebase, and contributions are made against the latest code changes in the nightly branch.
To install the nightly Mojo build, follow these steps
Bash
# install modular CLI
curl https://get.modular.com | sh -
modular auth
modular install nightly/mojo
Copy
or update the nightly via
Bash
modular update nightly/mojo
Copy
Then follow the instructions as indicated in the Mojo installer guide .
This step is crucial as it incorporates the latest code changes, enabling us to proceed with our contribution and perform local testing, details of which will be discussed later.
We are now ready to start our contribution journey by following these steps:
Step 0: Find a GitHub issue that interests you If it is the first time and you are unsure which issue to choose, we recommend looking for an issue labeled with good-first-issue . To avoid duplicated effort, ensure that the issue is not already assigned to someone else and that there isn’t a linked pull request already in progress. For the purpose of this blog post, we have chosen this issue to work on.
Step 1: Fork the repository and Git remote setup If this is your first contribution, start by forking the repository. By default, GitHub forks only the main branch. However, as our work will use the nightly branch, make sure to uncheck the Copy the main branch only option prior to forking.
Once our fork is created, it will be accessible at https://github.com/<username>/mojo
Next, we clone our fork to our local machine:
Bash
git clone https://github.com/<username>/mojo
Copy
To clarify, we are working with three key entities:
Since everything stems from https://github.com/modularml/mojo which is referred to as upstream . This exists remotely and in Git terminology, is known as the upstream remote. Our fork at <username>/mojo , known as origin. Like upstream this is also stored remotely. Our local clone of the fork which is stored on our machine. Given that a fork can diverge from the original repository that it was forked from, it is crucial to configure our local clone to track changes from upstream . Navigate into the clone directory and set up the upstream remote as follows:
Bash
git remote add upstream https://github.com/modularml/mojo
Copy
Now, to synchronize our fork with the latest changes from upstream , we execute
Bash
git fetch upstream && git rebase upstream/nightly
Copy
Or more concisely
Bash
git pull --rebase upstream nightly
Copy
This sequence fetches updates from upstream and rebases, which means it integrates these changes into our local clone, placing any of our modifications (Git commits) on top. It is highly recommended to regularly update from the upstream. This proactive approach helps identify and resolve any merge conflicts at the earliest opportunity.
Set up a tracking branch After cloning the repository, running git branch -a lists all the available branches
Output
* main
remotes/origin/HEAD -> origin/main
remotes/origin/main
remotes/origin/nightly
Copy
Given that our contributions are made against the nightly branch, we will need to create a tracking branch as follows
Bash
git checkout --track origin/nightly
Copy
This command creates a local nightly branch in our clone, synchronized with origin/nightly . We will see
Output
branch 'nightly' set up to track 'origin/nightly'.
Switched to a new branch 'nightly'
Copy
Here, origin refers to our fork (<username>/mojo ) and is distinct from the upstream (modularml/mojo ). We have already set up the upstream for syncing purposes, as demonstrated previously.
Now we are on a tracking nightly local branch. For our fix, let’s create a dedicated branch and call it fix-branch
Bash
git checkout -b fix-branch
Copy
With fix-branch ready, we can proceed to implement our fixes.
Step 2: Local development and git push To contribute to Mojo, LLVM installation is a prerequisite.
For Ubuntu users, install the latest LLVM version 17.0 with these commands
Bash
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 17
Copy
For MacOS users, one option is with
Bash
brew install llvm@17
Copy
If multiple LLVM versions are installed, manage their precedence as needed. After installing LLVM 17, then binaries like llvm-config-17 or FileCheck-17 are versioned. To simplify access, set them as default with a higher priority, e.g. 100 (change this according to any previous setup)
Bash
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-17 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-17 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-17 100
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-17 100
sudo update-alternatives --install /usr/bin/ld.lld ld.lld /usr/bin/ld.lld-17 100
sudo update-alternatives --install /usr/bin/lldb lldb /usr/bin/lldb-17 100
sudo update-alternatives --install /usr/bin/FileCheck FileCheck /usr/bin/FileCheck-17 100
Copy
Building the standard library We start by building the standard library and making sure everything functions correctly. We can do so by using the provided build-stdlib.sh script
Bash
./stdlib/scripts/build-stdlib.sh
Copy
This script creates a build directory for the build artifacts.
Running tests locally We install the necessary Python package for testing with
Bash
python3 -m pip install lit
Copy
With all the dependencies in place, we should be able to run the unit tests successfully using the run-tests.sh script
Bash
./stdlib/scripts/run-tests.sh
Copy
If you encounter issues such as FileCheck is not found , refer to the part we installed LLVM and make sure FileCheck is added to your PATH , for example via
Bash
# on Ubuntu
export PATH="/usr/bin/llvm/bin:$PATH"
# on MacOS
export PATH="/opt/homebrew/opt/llvm/bin:$PATH
Copy
Implementing fixes With the setup ready, we can proceed to code. For simple changes such as the fix for the chosen issue, a single commit may suffice. We should also write unit tests and run them ./stdlib/scripts/run-tests.sh . For this specific PR, the test is already covered in the compile time (because we have added constrained statements).
We should make sure our code is also formatted which can be done via mojo format command.
Next, we proceed with pushing our commits
Output
git push origin fix-branch
Copy
If there are multiple commits, we can group them together which we will cover below.
Grouping multiple commits For complicated code changes, it is advised to keep the Git commits atomic which means each commit is doing one and only one thing. Later, if we desire we can group multiple commits into one by doing git rebase .
First we configure your Git editor if not have done so
Bash
git config core.editor <the-editor-you-like-such-as-vim>
Copy
Assume we want to merge the last 3 commit together, we can do interactive rebase as follows
Bash
git rebase -i HEAD~3
Copy
A text editor will pop up
Set the first top commit as pick (or p for short), and change later ones to squash (or s for short). After you save the file, another text editor will pop open to ask you modify the combined commit message. Push the changes to your fork, you need to force push git push —-force Important note: It is fine to force push to your own fork, as long as the commits changed are only yours. Otherwise, it can have bad consequences as it rewrites the Git history. In case there are multiple collaborators who use your branch, the safer option is git push —-force-with-lease
Step 3: Create a pull request against the nightly branch Once our Git commits are finalized, we are ready to create a pull request. We can do so by navigating to our fork repository on GitHub and following the prompt “Create pull request”. This action takes us to a new page where we can define the specifics of our pull request. It is crucial to choose the nightly branch as shown below
As part of our contribution guidelines, it is important to choose a descriptive title. Additionally, it is required to prepend the title with [mojo-stdlib] to better keep track of the related PRs in Mojo codebase. Moreover, we should also write a detailed description of the changes and include a link to the related issue
Step 4: Sign your commit, monitor CI failures and reviewers feedback To ensure the integrity of contributions, each commit must be signed. The standard library follows the Developer Certificate of Origin protocol, established by the Linux Foundation . This step is integrated in our continuous integration (CI). During the CI checks, we look for the DCO status . Then we select the DCS option and proceed to confirm our agreement by clicking on Set DCO to pass.
Once the commit is signed, keep an eye on any CI failures including formatting issues or failed unit tests. Your PR will be reviewed in a timely manner and reviewers comments should be addressed to get the final approval.
Step 5: Get approval and merge Once all criteria are met and our PR is approved by the Mojo standard library team, it will be merged into the codebase.
That is it! 🎉 Congratulations on your successful contribution to Mojo 🔥❤️
Conclusion Throughout this guide, we have covered all the steps required for contributing to the Mojo standard library. From finding a starting point and setting up the necessary Git workflow, to building, testing locally, and submitting a pull request, we have covered the groundwork to get you started. Remember the initial setup steps need to be completed just once. We hope that this post has equipped you with the knowledge and confidence to begin your contribution journey to the standard library.
Additional resources:
Report feedback, including issues on our Mojo and MAX GitHub tracker.
Until next time!🔥