From 5dd91799be60927622227d07619008fa91511c4e Mon Sep 17 00:00:00 2001 From: Chuck Grindel Date: Tue, 12 Dec 2023 08:50:52 -0700 Subject: [PATCH] feat: initial implementation (#1) - The initial implementation for the action. - `/tools/run_nix_shell.sh`: The core logic for the action. - `/action.yaml`: The GitHub action definition. - Nix configuration files to support the testing of the action (`/flake.nix`, `/flake.lock`, `/nixpkgs.nix`). - Unit tests (`/tests/tools_tests`) that can be exercised using Bazel. - Files used in the integration tests (`/tests/integration_tests`). The actual integration tests are defined in the CI workflow file. - A CI workflow to test PRs and run a daily build. - `README.md` with usage information. Related to https://github.com/tweag/scalable-builds-group/issues/97. Related to https://github.com/tweag/scalable-builds-group/issues/138. --- .bazelrc | 22 +++ .envrc | 1 + .github/actions/set_up_runner/action.yaml | 26 ++++ .github/workflows/ci.yaml | 80 +++++++++++ .gitignore | 3 + BUILD.bazel | 10 ++ MODULE.bazel | 15 ++ README.md | 66 ++++++++- WORKSPACE | 1 + WORKSPACE.bzlmod | 2 + action.yaml | 34 +++++ flake.lock | 78 +++++++++++ flake.nix | 33 +++++ nixpkgs.nix | 8 ++ tests/integration_tests/README.md | 4 + tests/integration_tests/shell.nix | 25 ++++ tests/tools_tests/BUILD.bazel | 19 +++ tests/tools_tests/custom_var_bool_test.sh | 8 ++ tests/tools_tests/custom_var_str_test.sh | 8 ++ tests/tools_tests/multi_line_script_test.sh | 9 ++ tests/tools_tests/pure_test.sh | 31 +++++ tests/tools_tests/rns_opts_test.sh | 8 ++ tests/tools_tests/run_nix_shell_test.bzl | 30 ++++ .../tools_tests/run_nix_shell_test_runner.sh | 54 ++++++++ tests/tools_tests/script_file_test.sh | 11 ++ tests/tools_tests/simple_script_test.sh | 3 + tests/tools_tests/verbose_test.sh | 25 ++++ tests/tools_tests/working_dir_test.sh | 13 ++ tools/BUILD.bazel | 5 + tools/run_nix_shell.sh | 130 ++++++++++++++++++ 30 files changed, 760 insertions(+), 2 deletions(-) create mode 100644 .bazelrc create mode 100644 .envrc create mode 100644 .github/actions/set_up_runner/action.yaml create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 BUILD.bazel create mode 100644 MODULE.bazel create mode 100644 WORKSPACE create mode 100644 WORKSPACE.bzlmod create mode 100644 action.yaml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nixpkgs.nix create mode 100644 tests/integration_tests/README.md create mode 100644 tests/integration_tests/shell.nix create mode 100644 tests/tools_tests/BUILD.bazel create mode 100644 tests/tools_tests/custom_var_bool_test.sh create mode 100644 tests/tools_tests/custom_var_str_test.sh create mode 100644 tests/tools_tests/multi_line_script_test.sh create mode 100644 tests/tools_tests/pure_test.sh create mode 100644 tests/tools_tests/rns_opts_test.sh create mode 100644 tests/tools_tests/run_nix_shell_test.bzl create mode 100755 tests/tools_tests/run_nix_shell_test_runner.sh create mode 100644 tests/tools_tests/script_file_test.sh create mode 100644 tests/tools_tests/simple_script_test.sh create mode 100644 tests/tools_tests/verbose_test.sh create mode 100644 tests/tools_tests/working_dir_test.sh create mode 100644 tools/BUILD.bazel create mode 100755 tools/run_nix_shell.sh diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..6ed7e7b --- /dev/null +++ b/.bazelrc @@ -0,0 +1,22 @@ +build --enable_bzlmod + +# CI Configuration +# ---------------- +common:ci --announce_rc +test:ci --test_output=errors --test_summary=terse + +# MacOS CI Configuration +# ---------------------- +# The unit tests have a tendency to timeout when executed on the GH macos +# runners. So, we reduce the number of parallel jobs and increase the timeout +# for the tests. +common:macos_ci --jobs=2 +common:macos_ci --test_timeout=600 + +# Remote Cache Authentication +# --------------------------- +try-import %workspace%/.bazelrc.auth + +# User Configuration +# ------------------ +try-import %workspace%/.bazelrc.local diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/actions/set_up_runner/action.yaml b/.github/actions/set_up_runner/action.yaml new file mode 100644 index 0000000..a84941c --- /dev/null +++ b/.github/actions/set_up_runner/action.yaml @@ -0,0 +1,26 @@ +name: Set up GitHub runner + +inputs: + github_token: + type: string + +runs: + using: composite + steps: + - uses: DeterminateSystems/nix-installer-action@v9 + with: + github-token: ${{ inputs.github_token }} + - uses: DeterminateSystems/magic-nix-cache-action@v2 + - name: Configure + shell: bash + run: | + cat >>.bazelrc.local <>.bazelrc.local < integration_test.out + echo "${CUSTOM_VAR_BOOL}" >> integration_test.out + echo "${CUSTOM_VAR_STR}" >> integration_test.out + - name: Confirm output + shell: bash + run: | + output="$(<./tests/integration_tests/integration_test.out)" + expected="$(cat <<-EOF + FIRST + true + Hello, World! + EOF + )" + [[ "${output}" == "${expected}" ]] || \ + (echo >&2 "Ouptput from integration test does not match:" "${output}"; exit 1) + + all_ci_tests: + runs-on: ubuntu-latest + needs: + - unit_tests + - integration_tests + if: ${{ always() }} + steps: + - uses: cgrindel/gha_join_jobs@794a2d117251f22607f1aab937d3fd3eaaf9a2f5 # v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16313eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bazel-* + +.direnv diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..b4297f0 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,10 @@ +# exports_files(["flake.lock"]) + +filegroup( + name = "flake_files", + srcs = [ + "flake.lock", + "flake.nix", + ], + visibility = ["//:__subpackages__"], +) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..db209bc --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,15 @@ +module( + name = "run_nix_shell", + version = "0.0.0", +) + +bazel_dep( + name = "rules_nixpkgs_core", + version = "0.10.0", +) + +bazel_dep( + name = "cgrindel_bazel_starlib", + version = "0.18.1", + dev_dependency = True, +) diff --git a/README.md b/README.md index 918ab0d..88be232 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,64 @@ -# run-nix-shell -GitHub action for executing scripts via nix-shell. +# Execute scripts using `nix-shell` + +[![Continuous Integration](https://github.com/tweag/run-nix-shell/actions/workflows/ci.yaml/badge.svg?event=schedule)](https://github.com/tweag/run-nix-shell/actions/workflows/ci.yaml) + +Executes a script or script file using `nix-shell`. + +## Usage + +To use this action, install Nix on your runner and start executing scripts. + +```yaml +name: Example +on: + workflow_dispatch: # allows manual triggering + +jobs: + run_under_nix: + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Execute script in Nix shell + uses: tweag/run-nix-shell@v0 + with: + run: | + set -o errexit -o nounset -o pipefail + echo "Hello" + + - name: Execute script file + uses: tweag/run-nix-shell@v0 + with: + run: path/to/my/script + + - name: Configure Nix shell before executing script + uses: tweag/run-nix-shell@v0 + with: + options: | + --arg myNixArg true + --argstr anotherNixArg "Hello, World!" + run: | + set -o errexit -o nounset -o pipefail + echo "Hello" + + - name: Execute script in a specific directory + uses: tweag/run-nix-shell@v0 + with: + working-directory: my/working/directory + run: | + set -o errexit -o nounset -o pipefail + echo "${PWD}" +``` + +## Inputs + +| Input | Description | +| ----- | ----------- | +| `run` | The script to be executed using `nix-shell`. This can be the actual script or a path to as cript file. | +| `options` | Any options that you want to pass to `nix-shell`. | +| `working-directory` | This will be the current working direcotry when the script is executed. | +| `verbose` | Enables additional output for debugging this action. | diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..b543b79 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1 @@ +# Intentionally blank; using bzlmod diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod new file mode 100644 index 0000000..5b29d40 --- /dev/null +++ b/WORKSPACE.bzlmod @@ -0,0 +1,2 @@ +# Intentionally blank +# This file exists to force bzlmod into a strict mode. diff --git a/action.yaml b/action.yaml new file mode 100644 index 0000000..140a1a8 --- /dev/null +++ b/action.yaml @@ -0,0 +1,34 @@ +name: Run nix-shell scripts +description: Executes shell scripts using nix-shell. + +inputs: + run: + type: string + required: true + description: The path to a file that contains shell code or the actual shell code. + pure: + type: boolean + default: true + options: + type: string + description: Any parameters that are to be passed to nix-shell are specified here. + working-directory: + type: string + description: The path where the nix-shell execution should take place. + default: . + verbose: + type: boolean + description: Enable debug output written to stderr. + default: false + +runs: + using: composite + steps: + - shell: bash + env: + RNS_CWD: ${{ inputs.working-directory }} + RNS_OPTS: ${{ inputs.options }} + RNS_RUN: ${{ inputs.run }} + RNS_PURE: ${{ inputs.pure }} + RNS_VERBOSE: ${{ inputs.verbose }} + run: ${GITHUB_ACTION_PATH}/tools/run_nix_shell.sh diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e4ec50e --- /dev/null +++ b/flake.lock @@ -0,0 +1,78 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1701718080, + "narHash": "sha256-6ovz0pG76dE0P170pmmZex1wWcQoeiomUZGggfH9XPs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2c7f3c0fb7c08a0814627611d9d7d45ab6d75335", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..5d87423 --- /dev/null +++ b/flake.nix @@ -0,0 +1,33 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + # Library for working with tools that need default.nix and shell.nix. + # https://nixos.wiki/wiki/Flakes#Using_flakes_with_stable_Nix + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + }; + outputs = { nixpkgs, flake-utils, ... }: + # For every platform that Nix supports, we ... + flake-utils.lib.eachDefaultSystem (system: + # ... get the package set for this particular platform ... + let pkgs = import nixpkgs { inherit system; }; + in { + # ... and define a development shell for it ... + devShells.default = with pkgs; mkShell { + # BEGIN Config to build Bazel 6 + # do not use Xcode on macOS + BAZEL_USE_CPP_ONLY_TOOLCHAIN = "1"; + # for nixpkgs cc wrappers, select C++ explicitly (see https://github.com/NixOS/nixpkgs/issues/150655) + BAZEL_CXXOPTS = "-x:c++"; + buildInputs = lib.optional pkgs.stdenv.isDarwin darwin.cctools; + # END Config to build Bazel 6 + # Name for the shell + name = "run_nix_shell_shell"; + # ... which makes available the following dependencies, + packages = [ bazel_6 bazel-buildtools cacert gcc nix git openssh ]; + }; + }); +} diff --git a/nixpkgs.nix b/nixpkgs.nix new file mode 100644 index 0000000..83d5b3c --- /dev/null +++ b/nixpkgs.nix @@ -0,0 +1,8 @@ +# Ensure that our development shell uses the same Nixpkgs version as what +# rules_nixpkgs uses in Bazel. +let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + spec = lock.nodes.nixpkgs.locked; + nixpkgs = fetchTarball "https://github.com/${spec.owner}/${spec.repo}/archive/${spec.rev}.tar.gz"; +in +import nixpkgs diff --git a/tests/integration_tests/README.md b/tests/integration_tests/README.md new file mode 100644 index 0000000..b625e29 --- /dev/null +++ b/tests/integration_tests/README.md @@ -0,0 +1,4 @@ +# Integration Tests + +This directory contains the files needed to execute the integration tests as configured in the CI +GitHub workflow. diff --git a/tests/integration_tests/shell.nix b/tests/integration_tests/shell.nix new file mode 100644 index 0000000..22c1194 --- /dev/null +++ b/tests/integration_tests/shell.nix @@ -0,0 +1,25 @@ +{ + customVarBool ? false, + customVarStr ? "default", + lock ? builtins.fromJSON (builtins.readFile ./../../flake.lock), + flakeCompat ? fetchTarball { + url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + }, + nixpkgs ? fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/${lock.nodes.nixpkgs.locked.rev}.tar.gz"; + sha256 = lock.nodes.nixpkgs.locked.narHash; + }, +}: +( + # Call flakeCompat with src pointing to the location of the flake.nix. + import flakeCompat { src = ./../../.; } +).shellNix // ( + # This expression is generating a shell that is merged with the one provided by shellNix. + let pkgs = import nixpkgs {}; in + with pkgs; + mkShell { + CUSTOM_VAR_BOOL = if customVarBool then "true" else "false"; + CUSTOM_VAR_STR = customVarStr; + } +) diff --git a/tests/tools_tests/BUILD.bazel b/tests/tools_tests/BUILD.bazel new file mode 100644 index 0000000..504c356 --- /dev/null +++ b/tests/tools_tests/BUILD.bazel @@ -0,0 +1,19 @@ +load(":run_nix_shell_test.bzl", "run_nix_shell_test") + +run_nix_shell_test(name = "custom_var_bool_test") + +run_nix_shell_test(name = "custom_var_str_test") + +run_nix_shell_test(name = "multi_line_script_test") + +run_nix_shell_test(name = "pure_test") + +run_nix_shell_test(name = "rns_opts_test") + +run_nix_shell_test(name = "script_file_test") + +run_nix_shell_test(name = "simple_script_test") + +run_nix_shell_test(name = "verbose_test") + +run_nix_shell_test(name = "working_dir_test") diff --git a/tests/tools_tests/custom_var_bool_test.sh b/tests/tools_tests/custom_var_bool_test.sh new file mode 100644 index 0000000..246d201 --- /dev/null +++ b/tests/tools_tests/custom_var_bool_test.sh @@ -0,0 +1,8 @@ +assert_msg="default value for CUSTOM_VAR_BOOl" +output="$( "${run_nix_shell_sh}" 'echo "${CUSTOM_VAR_BOOL}"' )" +assert_equal "false" "${output}" "${assert_msg}" + +assert_msg="custom value for CUSTOM_VAR_BOOL via command-line arg" +output="$( "${run_nix_shell_sh}" --arg customVarBool true 'echo "${CUSTOM_VAR_BOOL}"' )" +assert_equal "true" "${output}" "${assert_msg}" + diff --git a/tests/tools_tests/custom_var_str_test.sh b/tests/tools_tests/custom_var_str_test.sh new file mode 100644 index 0000000..1149082 --- /dev/null +++ b/tests/tools_tests/custom_var_str_test.sh @@ -0,0 +1,8 @@ +assert_msg="default value for CUSTOM_VAR_STR" +output="$( "${run_nix_shell_sh}" 'echo "${CUSTOM_VAR_STR}"' )" +assert_equal "default" "${output}" "${assert_msg}" + +assert_msg="custom value for CUSTOM_VAR_STR via command-line arg" +expected="This is a custom value." +output="$( "${run_nix_shell_sh}" --argstr customVarStr "${expected}" 'echo "${CUSTOM_VAR_STR}"' )" +assert_equal "${expected}" "${output}" "${assert_msg}" diff --git a/tests/tools_tests/multi_line_script_test.sh b/tests/tools_tests/multi_line_script_test.sh new file mode 100644 index 0000000..7d6638e --- /dev/null +++ b/tests/tools_tests/multi_line_script_test.sh @@ -0,0 +1,9 @@ +assert_msg="multi-line script" +output="$( +"${run_nix_shell_sh}" ' +echo "Hello, World!" +echo "Second Line" +' +)" +assert_match "Hello, World" "${output}" "${assert_msg}" +assert_match "Second Line" "${output}" "${assert_msg}" diff --git a/tests/tools_tests/pure_test.sh b/tests/tools_tests/pure_test.sh new file mode 100644 index 0000000..ad5045d --- /dev/null +++ b/tests/tools_tests/pure_test.sh @@ -0,0 +1,31 @@ +assert_msg="pure by default" +output="$( + "${run_nix_shell_sh}" --verbose 'echo "Hello, World!"' 2>&1 1>/dev/null +)" +assert_match "nix-shell .*--pure " "${output}" "${assert_msg}" + +assert_msg="pure flag" +output="$( + "${run_nix_shell_sh}" --verbose --pure 'echo "Hello, World!"' 2>&1 1>/dev/null +)" +assert_match "nix-shell .*--pure " "${output}" "${assert_msg}" + +assert_msg="nopure flag" +output="$( + "${run_nix_shell_sh}" --verbose --nopure 'echo "Hello, World!"' 2>&1 1>/dev/null +)" +assert_no_match "nix-shell .*--pure " "${output}" "${assert_msg}" + +assert_msg="set RNS_PURE to true" +output="$( + RNS_PURE=true \ + "${run_nix_shell_sh}" --verbose 'echo "Hello, World!"' 2>&1 1>/dev/null +)" +assert_match "nix-shell .*--pure " "${output}" "${assert_msg}" + +assert_msg="set RNS_PURE to false" +output="$( + RNS_PURE=false \ + "${run_nix_shell_sh}" --verbose 'echo "Hello, World!"' 2>&1 1>/dev/null +)" +assert_no_match "nix-shell .*--pure " "${output}" "${assert_msg}" diff --git a/tests/tools_tests/rns_opts_test.sh b/tests/tools_tests/rns_opts_test.sh new file mode 100644 index 0000000..1b9b267 --- /dev/null +++ b/tests/tools_tests/rns_opts_test.sh @@ -0,0 +1,8 @@ +assert_msg="custom value for CUSTOM_VAR_BOOL via RNS_OPTS" +output="$( +RNS_OPTS='--arg customVarBool true --argstr customVarStr "This is a custom value."' \ + "${run_nix_shell_sh}" 'echo "${CUSTOM_VAR_BOOL}"; echo "${CUSTOM_VAR_STR}"' +)" +assert_match "true" "${output}" "${assert_msg}" +assert_match "This is a custom value" "${output}" "${assert_msg}" + diff --git a/tests/tools_tests/run_nix_shell_test.bzl b/tests/tools_tests/run_nix_shell_test.bzl new file mode 100644 index 0000000..395881d --- /dev/null +++ b/tests/tools_tests/run_nix_shell_test.bzl @@ -0,0 +1,30 @@ +def run_nix_shell_test(name, test_file = None, **kwargs): + if test_file == None: + test_file = "{}.sh".format(name) + + lib_name = name + "_library" + native.sh_library( + name = lib_name, + srcs = [test_file], + ) + + native.sh_test( + name = name, + srcs = ["@run_nix_shell//tests/tools_tests:run_nix_shell_test_runner.sh"], + args = [ + "$(location :{})".format(lib_name), + ], + data = [ + lib_name, + "//:flake_files", + "//tools:run_nix_shell", + ], + # MacOS sandbox fails this test with the following error: + # sandbox-exec: sandbox_apply: Operation not permitted + tags = ["no-sandbox"], + deps = [ + "@bazel_tools//tools/bash/runfiles", + "@cgrindel_bazel_starlib//shlib/lib:assertions", + ], + **kwargs + ) diff --git a/tests/tools_tests/run_nix_shell_test_runner.sh b/tests/tools_tests/run_nix_shell_test_runner.sh new file mode 100755 index 0000000..1f2536b --- /dev/null +++ b/tests/tools_tests/run_nix_shell_test_runner.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -o nounset -o pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +# shellcheck disable=SC1090 +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -o errexit +# --- end runfiles.bash initialization v2 --- + +# MARK - Locate Deps + +assertions_sh_location=cgrindel_bazel_starlib/shlib/lib/assertions.sh +assertions_sh="$(rlocation "${assertions_sh_location}")" || \ + (echo >&2 "Failed to locate ${assertions_sh_location}" && exit 1) +source "${assertions_sh}" + +run_nix_shell_sh_location=run_nix_shell/tools/run_nix_shell.sh +run_nix_shell_sh="$(rlocation "${run_nix_shell_sh_location}")" || \ + (echo >&2 "Failed to locate ${run_nix_shell_sh_location}" && exit 1) + +flake_lock_location=run_nix_shell/flake.lock +flake_lock="$(rlocation "${flake_lock_location}")" || \ + (echo >&2 "Failed to locate ${flake_lock_location}" && exit 1) + +# MARK - Setup + +cat >shell.nix <<-EOF +{ + pkgs ? import ( + let lock = builtins.fromJSON (builtins.readFile ${flake_lock}); in + fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/\${lock.nodes.nixpkgs.locked.rev}.tar.gz"; + sha256 = lock.nodes.nixpkgs.locked.narHash; + } + ) {}, + customVarBool ? false, + customVarStr ? "default", +}: +with pkgs; +mkShell { + CUSTOM_VAR_BOOL = if customVarBool then "true" else "false"; + CUSTOM_VAR_STR = customVarStr; + packages = [ nix ]; +} +EOF + +# MARK - Test + +source "${1}" diff --git a/tests/tools_tests/script_file_test.sh b/tests/tools_tests/script_file_test.sh new file mode 100644 index 0000000..2227558 --- /dev/null +++ b/tests/tools_tests/script_file_test.sh @@ -0,0 +1,11 @@ +assert_msg="path to script file" +custom_script_path="./custom_script.sh" +cat >"${custom_script_path}" <<-EOF +#!/usr/bin/env bash +set -o errexit -o nounset -o pipefail +echo "Hello from custom script" +EOF +chmod +x "${custom_script_path}" +output="$( "${run_nix_shell_sh}" "${custom_script_path}" )" +assert_equal "Hello from custom script" "${output}" "${assert_msg}" + diff --git a/tests/tools_tests/simple_script_test.sh b/tests/tools_tests/simple_script_test.sh new file mode 100644 index 0000000..0c9bd03 --- /dev/null +++ b/tests/tools_tests/simple_script_test.sh @@ -0,0 +1,3 @@ +assert_msg="simple script" +output="$( "${run_nix_shell_sh}" 'echo "Hello, World!"' )" +assert_equal "Hello, World!" "${output}" "${assert_msg}" diff --git a/tests/tools_tests/verbose_test.sh b/tests/tools_tests/verbose_test.sh new file mode 100644 index 0000000..8360061 --- /dev/null +++ b/tests/tools_tests/verbose_test.sh @@ -0,0 +1,25 @@ +# Set up a new working directory +cwd="${PWD}/working-dir" +rm -rf "${cwd}" +mkdir -p "${cwd}" +cp shell.nix "${cwd}/" + +assert_msg="set RNS_VERBOSE env var" +output="$( + RNS_VERBOSE="true" \ + RNS_CWD="${cwd}" \ + RNS_OPTS='--arg customVarBool true ' \ + RNS_PURE="true" \ + "${run_nix_shell_sh}" \ + --argstr customVarStr "This is a custom value." \ + 'echo HELLO' 2>&1 1>/dev/null +)" +assert_match "RNS_CWD: .*working-dir" "${output}" "${assert_msg}" +assert_match "RNS_OPTS: --arg customVarBool true" "${output}" "${assert_msg}" +assert_match "RNS_PURE: true" "${output}" "${assert_msg}" +assert_match "RNS_RUN: " "${output}" "${assert_msg}" +assert_match "cwd: .*working-dir" "${output}" "${assert_msg}" +assert_match 'nix_shell_opts: --argstr customVarStr This\\ is\\ a\\ custom\\ value\.' \ + "${output}" "${assert_msg}" +assert_match "pure: true" "${output}" "${assert_msg}" +assert_match "script: echo HELLO" "${output}" "${assert_msg}" diff --git a/tests/tools_tests/working_dir_test.sh b/tests/tools_tests/working_dir_test.sh new file mode 100644 index 0000000..2e62dfa --- /dev/null +++ b/tests/tools_tests/working_dir_test.sh @@ -0,0 +1,13 @@ +# Set up a new working directory +cwd="${PWD}/working-dir" +rm -rf "${cwd}" +mkdir -p "${cwd}" +cp shell.nix "${cwd}/" + +assert_msg="set working-directory flag" +output="$( "${run_nix_shell_sh}" --working-directory "${cwd}" 'echo "${PWD}"' )" +assert_equal "${cwd}" "${output}" "${assert_msg}" + +assert_msg="set RNS_CWD env var" +output="$( RNS_CWD="${cwd}" "${run_nix_shell_sh}" 'echo "${PWD}"' )" +assert_equal "${cwd}" "${output}" "${assert_msg}" diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel new file mode 100644 index 0000000..6442704 --- /dev/null +++ b/tools/BUILD.bazel @@ -0,0 +1,5 @@ +sh_binary( + name = "run_nix_shell", + srcs = ["run_nix_shell.sh"], + visibility = ["//:__subpackages__"], +) diff --git a/tools/run_nix_shell.sh b/tools/run_nix_shell.sh new file mode 100755 index 0000000..0ba203a --- /dev/null +++ b/tools/run_nix_shell.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash + +set -o errexit -o nounset -o pipefail + +# NOTE: This script has been implemented so that it can be executed without Bazel. Do not pull in +# external dependencies. + +warn() { + echo >&2 "$@" +} + +fail() { + warn "$@" + exit 1 +} + +# MARK - Arguments + +cwd="${RNS_CWD:-}" +script="${RNS_RUN:-}" + +pure="${RNS_PURE:-true}" +verbose="${RNS_VERBOSE:-false}" +nix_shell_opts=() +while (("$#")); do + case "${1}" in + # Supports: + # --arg docTools false + # --argstr ghcVersion "1.2.3" + --arg*) + nix_shell_opts+=( "${1}" "${2}" "${3}" ) + shift 3 + ;; + --pure) + pure="true" + shift 1 + ;; + --nopure) + pure="false" + shift 1 + ;; + --working-directory) + cwd="${2}" + shift 2 + ;; + --verbose) + verbose=true + shift 1 + ;; + *) + if [[ -z "${script:-}" ]]; then + script="${1}" + shift 1 + else + fail "Unexpected argument:" "${1}" + fi + ;; + esac +done + +# MARK - Verbose + +is_verbose() { + [[ "${verbose}" == "true" ]] +} + +if is_verbose; then + verbose_output="$(cat <<-EOF +=== run_nix_shell Inputs === +RNS_CWD: ${RNS_CWD:-} +RNS_OPTS: ${RNS_OPTS:-} +RNS_RUN: ${RNS_RUN:-} +RNS_PURE: ${RNS_PURE:-} +cwd: ${cwd:-} +nix_shell_opts: $( printf "%q " "${nix_shell_opts[@]}" ) +pure: ${pure:-} +script: ${script:-} +=== +EOF +)" + warn "${verbose_output}" +fi + +# MARK - Process Options and Arguments + +if [[ "${pure}" == "true" ]]; then + nix_shell_opts+=( --pure ) +fi + +# Look for any options passed via the RNS_OPTS environment variable. +# Add them to the nix_shell_opts. +if [[ -n "${RNS_OPTS:-}" ]]; then + # Parse the RNS_OPTS string. + while IFS=$'\0' read -r -d '' arg; do + more_nix_shell_opts+=( "${arg}" ) + done < <(xargs printf '%s\0' <<<"${RNS_OPTS}") + # Add to the nix_shell_opts if we found any + if [[ ${#more_nix_shell_opts[@]} ]]; then + nix_shell_opts+=( "${more_nix_shell_opts[@]}" ) + fi +fi + +if [[ -z "${script:-}" ]]; then + fail "A script for a path to a file must be provided." +fi + +# MARK - Execute script + +# Change to the specified working directory +if [[ -n "${cwd:-}" ]]; then + cd "${cwd}" +fi + +cmd=( nix-shell ) +if [[ ${#nix_shell_opts[@]} -gt 0 ]]; then + cmd+=( "${nix_shell_opts[@]}" ) +fi +cmd+=( --run "${script}" ) + +if is_verbose; then + verbose_output="$(cat <<-EOF +=== run_nix_shell command-line invocation === +$( printf "%q " "${cmd[@]}" ) +=== +EOF +)" + warn "${verbose_output}" +fi + +"${cmd[@]}"