Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Nix/NixOS #1101

Draft
wants to merge 4 commits into
base: theseus_main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions NIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Building and Running Theseus with Nix

Theseus provides a Nix shell ([shell.nix](./shell.nix)) that provides a build environment for building, developing, and running Theseus in QEMU. It can bring in all the dependencies needed, as described in the [README](./README.md), including the correct build of Rust nightly (see [Theseus, Nix, and Rust Toolchains](#theseus_nix_and_rust_toolchains)). The shell also includes some optional arguments you can use to optimize the dependency imports as well as other things. See [Nix Shell](#nix_shell) for more.

## Quick Start

To enter the Nix shell, simply use `nix-shell` while in the Theseus source.

```sh
nix-shell # [./shell.nix]
```

From here you should be able to build, run, and develop any part of Theseus:

```sh
# Equivilant to `make iso` but builds with all features enabled. See Makefile.
make
```

```sh
make iso
```

```sh
# Build and run Theseus with full features in QEMU.
make run
```

## Nix Shell

### Args

The following are some of the more relevant arguments for the shell. Some additional arguments exist, but are only listed and documented in [shell.nix](./shell.nix) itself. They are omitted here for the sake of not cluttering this documentation for less advanced Nix users.

#### Dependency-Related Options

- `run` - Whether or not to prepare for running Theseus in QEMU. This can be highly useful when you don't plan to run Theseus in QEMU, as QEMU is a very heavy dependency. The default is `true`.
- `extraRustDevelopmentTools` - Brings additional development-related tools, not in the standard Theseus build toolchain, into the provided toolchain. Currently, this only includes rust-analyzer. The default is `true`.
- `extraRustToolchainComponents` - Extra, custom components to add to the provided toolchain.
- `rustToolchain` - Override with a completely custom Rust toolcahin derivation. Not recommended.
- `bootloader` - Chooses which bootloader(s) the shell should prepare for usage.
Possible values are:
- "any" - All bootloaders are made available to use, and the `bootloader` env var is not set.
- "grub" - GRUB's dependancies are added, and the `bootloader` env var is set to
"grub" if `setMakeBootloaderPreference` is `true`.
- "limine" - The shell preforms the setup stated in the README, cloning limine and resetting
to the needed commit. Additionally, the `bootloader` env var is set to "limine" if
`setMakeBootloaderPreference` is `true`.
The default is `"grub"`.
- `setMakeBootloaderPreference` - If false, overrides the behavior of `bootloader` to _not_
set the `bootloader` environment variable. The default is `true`.

#### Limine-Related Options

These options are specific to the usage of the Limine bootloader. If the above arguments do not create a shell in which Limine is set as a dependency, these options have no effect. See [README.md](./README.md) about Limine and its usage in Theseus.

- `preserveOldLimine` - Before downloading Limine, check if './limine-prebuilt' already exists. If so, skip downloading Limine for the already-existing Limine to be used. The default is `true`.
- `requiredLimineCommit` - If Limine is downloaded, this specifies the commit that Limine should be checked out to. The default is what is specified in README.md and changing this is not recommeded. It is merely set as an arg rather than a constant for convenience.

### Examples

Default shell (with QEMU for running, and both GRUB and Limine availbale to use):

```sh
nix-shell
```

Create build/dev environment without QEMU dependency (avoids downloading qemu_kvm but disables running):

```sh
nix-shell --arg run 'false'
```

Use the Limine bootloader (will fully set up for Limine usage):

```sh
nix-shell --arg bootloader '"limine"'
# Should now use Limine by default:
make run
```

### Targetting Other Architectures

TODO

### Development Shell for Debugging

TODO

## Theseus, Nix, and Rust Toolchains

Theseus's Rust codebase and the locked versions of its dependencies rely on not only nightly Rust, but a specific nightly build. This is not an issue for systems using rustup, however, Nix's package manager is at its core and not only is using something like rustup looked down on, but it may not even work on NixOS. Normally, when Theseus is built using cargo, it will see the rust-toolchain.toml file and use rustup to install that toolchain to use instead. Nix, however, only provides the latest _stable_ Rust in Nixpkgs, and the wrapped toolchain components that come with it do not do this. This is an obvious problem.

Luckily, though, there is an easy solution: [Oxalica's rust-overlay](https://github.com/oxalica/rust-overlay/tree/master). Using this, we can easily produce a working, NixOS-compatible Rust toolchain from a Rust toolchain file, such as the one that provides the toolchain definition for Theseus, ([rust-toolchain.toml](./rust-toolchain.toml)).

This toolchain is provided by the [rust-toolchain.nix](./rust-toolchain.nix) file. You can import it directly to produce a Rust toolchain for Theseus but its main use is in the Theseus Nix shell.

## Nix Derivation for Theseus

A derivation for Theseus is in the works.

## Flake Support

TODO
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ We are continually working to improve the OS, including its fault recovery abili


# Quick start
On Linux (Debian-like distros), do the following:
On Linux (Debian-like distros, non-Nix), do the following:
1. Obtain the Theseus repository (with all submodules):
```
git clone --recurse-submodules --depth 1 https://github.com/theseus-os/Theseus.git
Expand All @@ -39,6 +39,22 @@ On Linux (Debian-like distros), do the following:
```
To exit QEMU, press <kbd>Ctrl</kbd> + <kbd>A</kbd>, then <kbd>X</kbd>.

On Linux (using Nix):
1. Follow step 1 of the above to obtain the Theseus source.
2. Cd into the source:
```sh
cd Theseus
```
3. Enter the Nix shell from ./shell.nix:
```sh
nix-shell
```
4. Build and run (like for ordinary Linux):
```sh
make run
```
**Note:** Installing rustup is not necessary, and should not be done on Nix-based platforms, especially via the traditional rustup installation method. The Nix shell provided by [shell.nix](./shell.nix) will provide a functional, and even somewhat modular, Rust toolchain, fit for building and/or developing Theseus. See [NIX.md](./NIX.md) for more information.

See below for more detailed instructions.


Expand All @@ -50,14 +66,17 @@ git submodule update --init --recursive

Currently, we support building Theseus on the following platforms:
* Linux, 64-bit Debian-based distributions like Ubuntu, tested on Ubuntu 16.04, 18.04, 20.04.
- Arch Linux and Fedora have also been reported to work correctly.
- Arch Linux and Fedora have also been reported to work correctly.
- NixOS support is experimental, however, success has been verified for basic builds (with both GRUB and Limine), using the provided Nix shell [./shell.nix](./shell.nix).
* Windows, using the Windows Subsystem for Linux (WSL), tested on the Ubuntu version of WSL and WSL2.
* MacOS, tested on versions High Sierra (v10.13), Catalina (v10.15), and Ventura (v13.5).
* Docker, atop any host OS that can run a Docker container.


## Setting up the build environment

**Note:** For Linux-Nix users, including NixOS, see the [NIX.md](./NIX.md) file for build instructions. For MacOS Nix users, out-of-the-box support is a major TODO. For WSL users, you may need to follow the extra, WSL-specific instructions below.

First, install Rust by following the [setup instructions here](https://www.rust-lang.org/en-US/install.html). On Linux, just run:
```sh
curl https://sh.rustup.rs -sSf | sh
Expand Down Expand Up @@ -169,6 +188,8 @@ make run bootloader=limine
```
Feel free to try newer versions, however they may not work.

### Using Nix
The above step is part of the Nix shell's setup when `bootloader` == `"limine"`. See [NIX.md](./NIX.md) for more.

## Targeting ARMv8 (aarch64)
Support for Theseus on aarch64 is an ongoing effort, but most of the core subsystems are complete.
Expand Down
20 changes: 20 additions & 0 deletions rust-toolchain.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
pkgs ? import <nixpkgs> {
overlays = [
(import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"))
];
},
lib ? pkgs.lib,
withDevPkgs ? true,
additionalComponents ? [ ],
}:
let
toolchainFile = ./rust-toolchain.toml;
toolchainDesc = (builtins.fromTOML (builtins.readFile toolchainFile)).toolchain;
finalToolchain = toolchainDesc // {
components = toolchainDesc.components ++ lib.lists.optionals withDevPkgs [
"rust-analyzer"
] ++ additionalComponents;
};
in
(pkgs.rust-bin.fromRustupToolchain finalToolchain)
96 changes: 96 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
# Nix options:
pkgs ? import <nixpkgs> {
overlays = [
(import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"))
];
},
lib ? pkgs.lib,

# Dependency options:

# qemu_kvm is a heavy package so we should probably allow users to avoid it if they want to.
run ? true,
# Adds extra components to the defaulttoolchain for developing Theseus. See rust-toolchain.nix and
# NIX.md.
extraRustDevelopmentTools ? true,
# Adds a custom set of components to the default toolchain.
extraRustToolchainComponents ? [ ],
# Allows you to completely override the default toolchain.
rustToolchain ? import ./rust-toolchain.nix {
inherit pkgs;
withDevPkgs = extraRustDevelopmentTools;
additionalComponents = extraRustToolchainComponents;
},
# The prefered bootloader. This controls both dependencies in terms of bootloaders as well as the
# `bootloader` env var, which sets which bootloader make will use if an explicit arg is not specified
# to make at runtime.
bootloader ? "grub",
# By default, this shell exports the `bootloader` env var as mentioned for the above arg. If set to
# `false`, it will _not_ set the `bootloader` env var.
setMakeBootloaderPreference ? true,

# Limine-Specific options:

# Whether or not to preserve old Limine clones when entering the shell.
preserveOldLimine ? true,
# This should generially not be overriden as Theseus relies on a specific commit of Limine and other
# commits do not have guarenteed support. Use at your own risk.
requiredLimineCommit ? "3f6a330",
}:
let
inherit (lib.strings) optionalString;

gitOutputHandler = "while read line; do echo \" > $line\"; done";

bootloaderPackageMap = rec {
any = grub ++ limine;
grub = [ pkgs.grub2 ];
limine = [ ]; # This is provided by manually cloning.
};
in
assert pkgs.lib.asserts.assertMsg
(builtins.hasAttr bootloader bootloaderPackageMap)
"\"${bootloader}\" is not a valid value for argument \"bootloader\". Please see the Theseus \
Nix shell documentation in `Theseus/NIX.md`.";
pkgs.mkShell {
nativeBuildInputs = with pkgs; [
# See NIX.md and rust-toolchain.nix.
rustToolchain
# Misc dependancies mentioned in the README
gnumake gcc nasm pkg-config mtools xorriso wget
] ++ (
lib.lists.optionals run [ pkgs.qemu_kvm ]
) ++ bootloaderPackageMap.${bootloader};

shellHook = let
limineGit = gitArgs: "${pkgs.git}/bin/git -C limine-prebuilt ${gitArgs} |& ${gitOutputHandler}";
prepareLimine = if preserveOldLimine
&& builtins.pathExists ./limine-prebuilt
&& (builtins.readFileType ./limine-prebuilt) == "directory"
then ''
echo "Detected possible old Limine clone, $(realpath limine-prebuilt). Using that."
'' else ''
${optionalString (builtins.pathExists ./limine-prebuilt) ''
echo "Removing old ./limine-prebuilt..."
rm -rf limine-prebuilt
''}
echo "Cloning Limine..."
${pkgs.git}/bin/git clone https://github.com/limine-bootloader/limine.git limine-prebuilt \
|& ${gitOutputHandler}
echo "Checking out required Limine commit (${requiredLimineCommit})..."
${limineGit "checkout ${requiredLimineCommit}"}
'';
in
''
${optionalString (bootloader == "limine" || bootloader == "any") ''
echo "Detected using Limine bootloader. Preparing Limine..."
# TODO: Firgure out a way to do this using Nix with Limine instead of doing this cloning the repo
# thing here.
${prepareLimine}
''}
${optionalString (bootloader != "any" && setMakeBootloaderPreference) ''
export bootloader=${bootloader}
''}
'';
}