diff --git a/README.md b/README.md index 0cd0aadbe..2c3bb61f9 100755 --- a/README.md +++ b/README.md @@ -58,9 +58,9 @@ int main(){ A full "Hello world" service with a working nix workflow is available in the [hello world repo](https://github.com/includeos/hello_world). The repository can also be used as a a starting point for developing your own IncludeOS service. -For more advanced service examples see the the integration tests (under ./IncludeOS/test/\*/integration). +For more advanced service examples see the the integration tests (under `./IncludeOS/test/\*/integration`). -### Kernel development +### Building IncludeOS To build IncludeOS, run @@ -70,24 +70,35 @@ $ nix-build This will build the toolchain and all IncludeOS kernel libraries. -Note that the first build will take some time to complete, as the IncludeOS toolchain is rebuilt from source code. This includes clang, llvm, libcxx, musl and so on. There is no nix binary cache available for these files at the moment. Subsequent builds will go much faster when the toolchain has been cached in the local nix-store. +Note that the first build will take some time (~7 hours on our machines) to complete, as the IncludeOS toolchain is rebuilt from source code. This includes clang, llvm, libcxx, musl and so on. There is no nix binary cache available for these files at the moment. Subsequent builds will go much faster when the toolchain has been cached in the local nix-store. -After making changes to the kernel, run `nix-build` again to get new binaries. If you are iterating on changes in one section of the kernel you can speed up the build significantly by using ccache. All `nix-build` and `nix-shell` commands in this section support the optional parameter `--arg withCcache true`. +After making changes to the kernel, run `nix-build` again to get new binaries. If you are iterating on changes in one section of the kernel you can speed up the build significantly (from a few minutes to a matter of seconds) by using ccache. All `nix-build` and `nix-shell` commands in this section support the optional parameter `--arg withCcache ` (this is only enabled by default for the development shell). We strongly advise setting up the ccache before you start iterating on the code. For NixOS, you can [check the wiki](https://nixos.wiki/wiki/CCache) on how to do this, other distros will have different instructions. -It's not always practical to rebuild the whole kernel during development. You can get a development shell with a preconfigured environment using `shell.nix`: +### Kernel development workflow +It's not always practical to rebuild the whole kernel during development. You can get a development shell with a preconfigured environment using `develop.nix`: ```bash -$ nix-shell +$ nix-shell ./develop.nix ``` -Further instructions will be shown for optionally configuring VM networking or overriding the build path when starting the shell. +From here, you will be put in a shell suited for development. If your editor has LSP support with clangd (e.g. neovim, emacs, VSCode, Zed), you will get proper goto-declarations and warnings. You can quickly rebuild the kernel library from here with `cmake -B build && (cd build; make)`, with all build dependencies handled by the shell. You can also build and run a unikernel from the shell with this oneliner: `(cd ./example && cmake -B build && (cd build && make) && boot ./build/hello_includeos.elf.bin)` after building IncludeOS. It might be convenient to use one terminal (or tab) for your editor, a second for building IncludeOS, and a third for building a unikernel. -By default th shell will also build the unikernel from `example.nix`. The example unikernel can be booted from within the shell: +In summary: +```bash +~/repos/IncludeOS $ nix-shell ./develop.nix +[nix-shell:~/repos/IncludeOS]$ cmake -B build && (cd build; make) # rebuilds IncludeOS +[nix-shell:~/repos/IncludeOS]$ cd example +[nix-shell:~/repos/IncludeOS/example]$ cmake -B build && (cd build; make) # rebuilds a bootable unikernel +[nix-shell:~/repos/IncludeOS/example]$ boot ./build/hello_includeos.elf.bin # runs the unikernel image with qemu through vmrunner +``` + +Alternatively, you may want to use `nix-shell` by itself (or nested inside the development shell), which handles both building the unikernel found under `./example/` and puts you in a convenient shell for testing out a unikernel. ```bash -$ nix-shell -[...] -nix$ boot hello_includeos.elf.bin +~/repos/IncludeOS $ nix-shell --run 'boot hello_includeos.elf.bin' +# or +~/repos/IncludeOS $ nix-shell # updating IncludeOS after entering this shell won't rebuild the os library automatically! +[nix-shell:~/repos/IncludeOS]$ boot hello_includeos.elf.bin ``` If you want to build a different unikernel than the example, this can be specified with the `--argstr unikernel [path]` parameter. This is primarily used for integration tests. For example, to build and run the stacktrace-test: diff --git a/develop.nix b/develop.nix new file mode 100644 index 000000000..f0403dba3 --- /dev/null +++ b/develop.nix @@ -0,0 +1,100 @@ +# develop.nix +# sets up a development shell in which you can open your editor +{ + buildpath ? "build", + + # path to your unikernel source (project root with CMakeLists.txt) + unikernel ? ".", + + # optional: path to a local vmrunner checkout (otherwise use includeos.vmrunner) + vmrunner ? "", + + # Enable ccache support. See overlay.nix for details. + withCcache ? true, + + # Enable multicore suport. + smp ? false, + + includeos ? import ./default.nix { inherit withCcache smp; }, + + arch ? "x86_64" +}: + +# override stdenv for furhter derivations so they're in sync with includeos patch requirements +includeos.pkgs.mkShell.override { inherit (includeos) stdenv; } rec { + vmrunnerPkg = + if vmrunner == "" then + includeos.vmrunner + else + includeos.pkgs.callPackage (builtins.toPath /. + vmrunner) {}; + + # handy tools available in the shell + packages = [ + (includeos.pkgs.python3.withPackages (p: [ + vmrunnerPkg + ])) + includeos.pkgs.buildPackages.cmake + includeos.pkgs.buildPackages.nasm + includeos.pkgs.qemu + includeos.pkgs.which + includeos.pkgs.grub2 + includeos.pkgs.iputils + includeos.pkgs.xorriso + includeos.pkgs.jq + ]; + + # libraries/headers we include against + buildInputs = [ + includeos + includeos.chainloader + includeos.lest + includeos.pkgs.openssl + includeos.pkgs.rapidjson + ]; + + shellHook = '' + IOS_SRC=${toString ./.} + if [ ! -d "$IOS_SRC" ]; then + echo "$unikernel is not a valid directory" >&2 + return 1 + fi + + echo "Configuring in: ${buildpath}" + echo "Source tree: $IOS_SRC" + + # delete old just in case it's dirty + rm -rf ${buildpath} + mkdir -p ${buildpath} + + # build includeOS + cmake -S "$IOS_SRC" -B ${buildpath} \ + -D CMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -D ARCH=${arch} \ + -D CMAKE_MODULE_PATH=${includeos}/cmake + + # procuced by CMake + CCDB="${buildpath}/compile_commands.json" + + # + # attempting to use -resource-dir with 'clang++ -print-resource-dir' + # doesn't work here as we're using -nostdlib/-nostdlibinc + # + tmp="$CCDB.clangd.tmp" + jq \ + --arg libcxx "${includeos.libraries.libcxx.include}" \ + --arg libc "${includeos.libraries.libc}" \ + --arg localsrc "${toString ./.}" \ + ' + map(.command |= ( . + + " -isystem \($libcxx)" + + " -isystem \($libc)/include" + | gsub("(?-I)(?/lib/LiveUpdate/include)"; .a + $localsrc + .b) + )) + ' "$CCDB" > "$tmp" && mv "$tmp" "$CCDB" + + + # most clangd configurations and editors will look in ./build/, but this just makes it easier to find for some niche edge cases + ln -sfn "${buildpath}/compile_commands.json" "$IOS_SRC/compile_commands.json" + ''; +} + diff --git a/overlay.nix b/overlay.nix index f8c385c03..608125f41 100644 --- a/overlay.nix +++ b/overlay.nix @@ -56,7 +56,20 @@ final: prev: { }; }); - pkgsIncludeOS = prev.pkgsStatic.lib.makeScope prev.pkgsStatic.newScope (self: { + pkgsIncludeOS = prev.pkgsStatic.lib.makeScope prev.pkgsStatic.newScope (self: + let + ccacheNoticeHook = prev.writeTextFile { + name = "ccache-notice-hook"; + destination = "/nix-support/setup-hook"; + text = '' + echo "=====" + echo "ccache is enabled!" + echo "If you run into any issues, try: --arg withCcache false" + echo "It's recommended to run tests with ccache disabled to avoid cache incoherencies." + echo "=====" + ''; + }; + in { # self.callPackage will use this stdenv. stdenv = final.stdenvIncludeOS.includeos_stdenv; @@ -70,26 +83,30 @@ final: prev: { ccacheWrapper = prev.ccacheWrapper.override { inherit (self.stdenv) cc; extraConfig = '' - export CCACHE_COMPRESS=1 export CCACHE_DIR="/nix/var/cache/ccache" - export CCACHE_UMASK=007 - export CCACHE_SLOPPINESS=random_seed if [ ! -d "$CCACHE_DIR" ]; then echo "=====" echo "Directory '$CCACHE_DIR' does not exist" echo "Please create it with:" echo " sudo mkdir -m0770 '$CCACHE_DIR'" echo " sudo chown root:nixbld '$CCACHE_DIR'" + echo "" + echo 'Alternatively, disable ccache with `--arg withCcache false`' echo "=====" exit 1 fi if [ ! -w "$CCACHE_DIR" ]; then echo "=====" - echo "Directory '$CCACHE_DIR' is not accessible for user $(whoami)" + echo "Directory '$CCACHE_DIR' exists, but is not accessible for user $(whoami)" echo "Please verify its access permissions" + echo 'Alternatively, disable ccache with `--arg withCcache false`' echo "=====" exit 1 fi + + export CCACHE_COMPRESS=1 + export CCACHE_UMASK=007 + export CCACHE_SLOPPINESS=random_seed ''; }; @@ -123,7 +140,7 @@ final: prev: { nativeBuildInputs = [ prev.buildPackages.cmake prev.buildPackages.nasm - ] ++ prev.lib.optionals withCcache [self.ccacheWrapper]; + ] ++ prev.lib.optionals withCcache [self.ccacheWrapper ccacheNoticeHook]; buildInputs = [ self.botan2 @@ -145,7 +162,7 @@ final: prev: { cp -r -v ${final.stdenvIncludeOS.libraries.libcxx.include} $out/libcxx/include cp -r -v ${final.stdenvIncludeOS.libraries.libunwind} $out/libunwind cp -r -v ${final.stdenvIncludeOS.libraries.libgcc} $out/libgcc - ''; + ''; archFlags = if self.stdenv.targetPlatform.system == "i686-linux" then [