From b08f5ac3301d5771f1ae32384655471c04d8bead Mon Sep 17 00:00:00 2001 From: Chris Ostrouchov Date: Mon, 17 Sep 2018 12:00:23 -0400 Subject: [PATCH] Feature: Adding nix support for repo2docker test url: https://gitlab.com/costrouc/nix-binder-example --- .travis.yml | 1 + docs/source/config_files.rst | 24 ++++++- repo2docker/app.py | 4 +- repo2docker/buildpacks/__init__.py | 1 + repo2docker/buildpacks/nix/__init__.py | 70 ++++++++++++++++++++ repo2docker/buildpacks/nix/install-nix.bash | 12 ++++ repo2docker/buildpacks/nix/nix-shell-wrapper | 15 +++++ tests/nix/simple/README.rst | 6 ++ tests/nix/simple/default.nix | 21 ++++++ tests/nix/simple/verify | 3 + 10 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 repo2docker/buildpacks/nix/__init__.py create mode 100644 repo2docker/buildpacks/nix/install-nix.bash create mode 100644 repo2docker/buildpacks/nix/nix-shell-wrapper create mode 100644 tests/nix/simple/README.rst create mode 100644 tests/nix/simple/default.nix create mode 100755 tests/nix/simple/verify diff --git a/.travis.yml b/.travis.yml index 219aba752..6cb94ab88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,6 +49,7 @@ env: - REPO_TYPE=stencila - REPO_TYPE=julia - REPO_TYPE=r + - REPO_TYPE=nix - REPO_TYPE=dockerfile - REPO_TYPE=external/* - REPO_TYPE=**/*.py diff --git a/docs/source/config_files.rst b/docs/source/config_files.rst index fe3fb02e2..efc0c8485 100644 --- a/docs/source/config_files.rst +++ b/docs/source/config_files.rst @@ -164,7 +164,6 @@ used for installing libraries. To see an example R repository, visit our `R example in binder-examples `_. - ``Dockerfile`` - Advanced environments ====================================== @@ -179,3 +178,26 @@ With Dockerfiles, a regular Docker build will be performed. See the `Advanced Binder Documentation `_ for best-practices with Dockerfiles. + +.. _default.nix: + +``default.nix`` +~~~~~~~~~~~~~~~ + +This allows you to use the `nix package manager `_. It is hard to explain what nix +is to new users and why it is usefull. If you are inclined please read +more at the `nix homepage `_. It is currently +the largest package repository, offers reproducible builds, multiple +versions of same package coexisting, source and binary based, and +packages many languages such as python, R, go, javascript, haskell, +ruby, etc. . + +A ``default.nix`` file allows you to use `nix-shell `_ +to evaluate a ``nix`` expression to define a reproducible nix environment. +The only requirement is that you expose a ``jupyter`` command within the shell +(since jupyterlab is currently what ``repo2docker`` is designed +around). While the ``nix`` environment does have ``NIX_PATH`` set with +``nixpkgs=...`` you should not rely on it and make sure to +`pin your nixpkgs `_. +By doing this you are truley producing a reproducible environment. To see an +example repository visit a `nix binder example `_. diff --git a/repo2docker/app.py b/repo2docker/app.py index 17f1b39a1..ea4038139 100644 --- a/repo2docker/app.py +++ b/repo2docker/app.py @@ -30,7 +30,8 @@ from . import __version__ from .buildpacks import ( PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack, - CondaBuildPack, JuliaBuildPack, RBuildPack + CondaBuildPack, JuliaBuildPack, BaseImage, + RBuildPack, NixBuildPack ) from . import contentproviders from .utils import ( @@ -77,6 +78,7 @@ def _default_log_level(self): LegacyBinderDockerBuildPack, DockerBuildPack, JuliaBuildPack, + NixBuildPack, RBuildPack, CondaBuildPack, PythonBuildPack, diff --git a/repo2docker/buildpacks/__init__.py b/repo2docker/buildpacks/__init__.py index 5ccc0c0e3..1900a449a 100644 --- a/repo2docker/buildpacks/__init__.py +++ b/repo2docker/buildpacks/__init__.py @@ -5,3 +5,4 @@ from .docker import DockerBuildPack from .legacy import LegacyBinderDockerBuildPack from .r import RBuildPack +from .nix import NixBuildPack diff --git a/repo2docker/buildpacks/nix/__init__.py b/repo2docker/buildpacks/nix/__init__.py new file mode 100644 index 000000000..a4841ebf0 --- /dev/null +++ b/repo2docker/buildpacks/nix/__init__.py @@ -0,0 +1,70 @@ +"""BuildPack for nixpkgs environments""" +import os + +from ..base import BuildPack + + +class NixBuildPack(BuildPack): + """A nix Package Manager BuildPack""" + + def get_path(self): + """Return paths to be added to PATH environemnt variable + """ + return super().get_path() + [ + '/home/${NB_USER}/.nix-profile/bin' + ] + + def get_env(self): + """Ordered list of environment variables to be set for this image""" + return super().get_env() + [ + ('NIX_PATH', "nixpkgs=/home/${NB_USER}/.nix-defexpr/channels/nixpkgs"), + ('NIX_SSL_CERT_FILE', '/etc/ssl/certs/ca-certificates.crt'), + ('GIT_SSL_CAINFO', '/etc/ssl/certs/ca-certificates.crt') + ] + + def get_build_scripts(self): + """ + Return series of build-steps common to all nix repositories. + Notice how only root privileges are needed for creating nix + directory. + + - create nix directory for user nix installation + - install nix package manager for user + """ + return super().get_build_scripts() + [ + ("root", """ + mkdir -m 0755 /nix && \ + chown -R ${NB_USER}:${NB_USER} /nix /usr/local/bin/nix-shell-wrapper /home/${NB_USER} + """), + ("${NB_USER}", """ + bash /home/${NB_USER}/.local/bin/install-nix.bash && \ + rm /home/${NB_USER}/.local/bin/install-nix.bash + """) + ] + + def get_build_script_files(self): + """Dict of files to be copied to the container image for use in building + """ + return { + "nix/install-nix.bash": "/home/${NB_USER}/.local/bin/install-nix.bash", + "nix/nix-shell-wrapper": "/usr/local/bin/nix-shell-wrapper" + } + + def get_assemble_scripts(self): + """Return series of build-steps specific to this source repository. + """ + return super().get_assemble_scripts() + [ + ('${NB_USER}', """ + nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs && \ + nix-channel --update && \ + nix-shell default.nix --command "command -v jupyter" + """) + ] + + def get_start_script(self): + """The path to a script to be executed as ENTRYPOINT""" + return "/usr/local/bin/nix-shell-wrapper" + + def detect(self): + """Check if current repo should be built with the nix BuildPack""" + return os.path.exists(self.binder_path('default.nix')) diff --git a/repo2docker/buildpacks/nix/install-nix.bash b/repo2docker/buildpacks/nix/install-nix.bash new file mode 100644 index 000000000..c4bf70e1e --- /dev/null +++ b/repo2docker/buildpacks/nix/install-nix.bash @@ -0,0 +1,12 @@ +#!/bin/bash +# This downloads and installs a pinned version of nix +set -ex + +NIX_VERSION="2.1.1" +NIX_SHA256="ad10b4da69035a585fe89d7330037c4a5d867a372bb0e52a1542ab95aec67999" + +wget https://nixos.org/releases/nix/nix-$NIX_VERSION/nix-$NIX_VERSION-x86_64-linux.tar.bz2 +echo "$NIX_SHA256 nix-2.1.1-x86_64-linux.tar.bz2" | sha256sum -c +tar xjf nix-*-x86_64-linux.tar.bz2 +sh nix-*-x86_64-linux/install +rm -r nix-*-x86_64-linux* diff --git a/repo2docker/buildpacks/nix/nix-shell-wrapper b/repo2docker/buildpacks/nix/nix-shell-wrapper new file mode 100644 index 000000000..be7a3bfb6 --- /dev/null +++ b/repo2docker/buildpacks/nix/nix-shell-wrapper @@ -0,0 +1,15 @@ +#!/bin/bash + +_term() { + echo "Caught SIGTERM signal!" + # kill -TERM "$PID" 2>/dev/null + exit 0 +} + +trap _term SIGTERM + +echo "$*" +nix-shell default.nix --command "$*" & + +PID=$! +wait "$PID" diff --git a/tests/nix/simple/README.rst b/tests/nix/simple/README.rst new file mode 100644 index 000000000..fc1bbe72a --- /dev/null +++ b/tests/nix/simple/README.rst @@ -0,0 +1,6 @@ +Nix environment - default.nix +----------------------------- + +You can install a nix shell environment using the traditional default.nix. + +Documentation on the syntax and typical setup of a ``nix-shell`` environment can be found `here `_. diff --git a/tests/nix/simple/default.nix b/tests/nix/simple/default.nix new file mode 100644 index 000000000..7b2f7f594 --- /dev/null +++ b/tests/nix/simple/default.nix @@ -0,0 +1,21 @@ +let + # Pinning nixpkgs to specific release + # To get sha256 use "nix-prefetch-git --rev " + commitRev="5574b6a152b1b3ae5f93ba37c4ffd1981f62bf5a"; + nixpkgs = builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/${commitRev}.tar.gz"; + sha256 = "1pqdddp4aiz726c7qs1dwyfzixi14shp0mbzi1jhapl9hrajfsjg"; + }; + pkgs = import nixpkgs { config = { allowUnfree = true; }; }; +in +pkgs.mkShell { + buildInputs = with pkgs; [ + python36Packages.numpy + python36Packages.scipy + python36Packages.jupyterlab + ]; + + shellHook = '' + export NIX_PATH="nixpkgs=${nixpkgs}:." + ''; +} diff --git a/tests/nix/simple/verify b/tests/nix/simple/verify new file mode 100755 index 000000000..4f794e84b --- /dev/null +++ b/tests/nix/simple/verify @@ -0,0 +1,3 @@ +#!/usr/bin/env python +import numpy +import scipy