Open Source Docs

Sbtix

Reproducible Nix builds for sbt projects

Creating a Build

Generated compositions, builders, and local dependencies


Creating a build

  • For generated composition builds, run sbtix genComposition (or sbt genComposition inside your project) to have sbtix emit sbtix-generated.nix for you. The file is rendered from the same template used in our tests, so it already contains the sandbox-friendly SBT_OPTS, Ivy generation, and install logic. Check it in as-is and have your default.nix (or another entry point) simply import it.

  • For flake-native or custom builds, call the helper functions from your flake input instead of importing the generated composition. Use buildSbtProgram for projects with a stage task, buildSbtLibrary for libraries, or buildSbtProject when you need a custom installPhase. In this flow, the generated repo.nix files are the source-control lockfiles; sbtix-generated.nix, sbtix.nix, and sbtix-plugin-repo.nix are not required unless your build imports them.

If you want a starting point, copy default.nix.example from this repository into your project. It simply imports the generated composition:

{ pkgs ? import <nixpkgs> {} }:

import ./sbtix-generated.nix { inherit pkgs; }

The root-level default.nix now just raises an error so it no longer looks like part of the build. If you need to hand-roll the derivation instead, use the lower-level API directly:

{ pkgs ? import <nixpkgs> {} }: with pkgs;
let
    sbtixDir = fetchFromGitHub {
        owner = "natural-transformation";
        repo = "sbtix";
        rev = "<<current git rev>>"; # Replace as needed
        sha256 = "<<<corresponding sha256 hash>>>"; # Replace as needed
    };
    sbtix = pkgs.callPackage "${sbtixDir}/plugin/nix-exprs/sbtix.nix" {};
in
    sbtix.buildSbtProgram {
        name = "sbtix-example";
        src = pkgs.lib.cleanSource ./.;
        repo = [ (import ./manual-repo.nix)
                 (import ./repo.nix)
                 (import ./project/repo.nix)
               ];
    }
  • generate your repo.nix files with one of the commands listed above.

  • if you use the generated composition path, rerun sbtix genComposition after changing dependencies to regenerate sbtix-generated.nix so it stays in sync with both the template and the sbtix revision you are using.

  • check the generated nix files that your build imports into your source control.

  • finally, run nix-build for generated composition builds or nix build .#... for flake-native builds.

  • any additional missing dependencies that nix-build encounters should be fetched with nix-prefetch-url and added to manual-repo.nix.

Keeping nix files separate from the project sources

In some cases, it can be preferable to keep the nix packaging definitions separate from the project being packaged. In that case, move the .nix files elsewhere, and point the src attribute to the location that contains the Scala project.

You're free to choose any filenames and directory layout, as long as you make sure you update the references from default.nix to the generated repo files. When re-generating the files, you'll still have to run sbtix-gen from the library directory (and temporarily copy your sbtix-build-inputs.nix there if you use that), then move the generated files to your custom location again.

Project Types

Libraries and programs need to be "installed" in different ways. Sbtix currently knows how to install "programs" and Maven-style libraries. The project type is selected by the builder function you use. Use sbtix.buildSbtProgram for building programs, and sbtix.buildSbtLibrary.

There is also sbtix.buildSbtProject, which allows you to define a custom Nix installPhase.

Programs

Programs must have a stage task, and it is assumed that calling this task will put its output in target/universal/stage. This folder is then copied to be the Nix build output.

This is generally fulfilled by SBT-Native-Packager's sbt-np-jaa(Java Application Archetype).

Libraries

Libraries are built by running SBT's built-in publishLocal task and then copying the resulting Ivy local repo to the Nix output folder.

Source Dependencies

Sbtix builds can depend on dependencies not found in online repositories by adding the attr sbtixBuildInputs to their call to buildSbt*.

These local dependencies should be provided as a derivation that produces a Maven or Ivy directory layout in its output, such as buildSbtLibrary. You should first package the dependency and then sbtix-gen the metadata for the project using it.

To make sure transitive dependencies and overrides work correctly, the local dependency must be available both when running 'sbtix-gen' and when building. You provide it in a sbtix-build-inputs.nix file, which could hold a single dependency:

{ pkgs ? import <nixpkgs> {} }:

pkgs.callPackage ./path/to/dependency/derivation {}

.. or multiple:

{ pkgs ? import <nixpkgs> {} }:

pkgs.symlinkJoin {
  name = "sbtix-dependencies";

  paths = [
    (pkgs.callPackage ./path/to/dependency/derivation {})
    (pkgs.callPackage ./path/to/other/dependency {})
  ];
}

This file is picked up (by name) by sbtix-gen, and also should be passed in as a parameter in the buildSbtProgram invocation in your default.nix:

sbtix.buildSbtProgram {
    ...
    sbtixBuildInputs = (pkgs.callPackage ./sbtix-build-inputs.nix {});
    ...
}

nix Extra Attribute

By adding the nix extra attribute, sbtix will ignore the dependency for the purpose of locking.

This used to be the only mechanism for handling local dependencies, but is now a legacy solution and/or escape hatch.