diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b40d8b8..b276cc4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,3 +76,15 @@ jobs: id: yard run: | bundle exec yard stats --list-undoc + nix: + runs-on: ubuntu-latest + # Don't fail the build if this job fails + continue-on-error: true + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v22 + with: + extra_nix_config: | + system-features = benchmark big-parallel kvm nixos-test uid-range + - name: run flake checks + run: nix flake check -L diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml new file mode 100644 index 0000000..ebde41b --- /dev/null +++ b/.github/workflows/deps.yml @@ -0,0 +1,35 @@ +name: Update Nix dependencies + +on: + schedule: + # Sunday and Wednesday + - cron: '42 4 * * 0,3' + workflow_dispatch: + +jobs: + update-deps-conservative: + runs-on: ubuntu-latest + permissions: + # So that `create-pull-request` can... create a pull request :) + # https://github.com/peter-evans/create-pull-request#action-inputs + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v22 + with: + extra_nix_config: | + system-features = benchmark big-parallel kvm nixos-test uid-range + - name: update lockfiles + run: nix run '.#devshell' -- update-deps-conservative + - name: create PR + uses: peter-evans/create-pull-request@v5 + with: + branch: create-pull-request/update-nix-deps + title: '[create-pull-request] Update Nix dependencies' + body: | + Update Nix dependencies in `Gemfile.nix.lock` and `gemset.nix`. + + Auto-generated by [create-pull-request][1]. + + [1]: https://github.com/peter-evans/create-pull-request diff --git a/.gitignore b/.gitignore index 950ed5b..c7ac408 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,11 @@ spec/reports test/tmp test/version_tmp tmp + +# Nix output links +result +result-* +repl-result-* + +# Bundler-installed gems +/vendor/ diff --git a/.rubocop.yml b/.rubocop.yml index f50d8ae..1a7853f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,6 +5,26 @@ require: AllCops: TargetRubyVersion: 3.0 NewCops: enable + # Ignore files ignored by Git. Improved version of the template shown here: + # https://docs.rubocop.org/rubocop/configuration.html#pre-processing + # Properly handles symlinks-to-directories, which `git status --ignored + # --porcelain` displays without a trailing slash. + Exclude: + <% git_ignored_file_status = `git status --ignored --porcelain 2>/dev/null` %> + <% if $? == 0 %> + <% git_ignored_file_status.each_line(chomp: true).grep(/^!! /).map { |p| p.sub(/^!! /, '') }.each do |path| %> + <% if File.directory?(path) && ! path.end_with?('/') %> + - <%= path + '/**/*' %> + <% else %> + - <%= path.sub(/\/$/, '/**/*') %> + <% end %> + <% end %> + <% else %> + - 'vendor/**/*' + - 'result/**/*' + - 'result-*/**/*' + - 'repl-result-*/**/*' + <% end %> Metrics: Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e8d89..1c0725a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Unreleased ([changes](https://github.com/infertux/bashcov/compare/v3.1.2...master)) - * TBD + * [FEATURE] Support building Bashcov with [the Nix package manager](https://nixos.org) (#78) ## v3.1.2, 2024-02-29 ([changes](https://github.com/infertux/bashcov/compare/v3.1.1...v3.1.2)) diff --git a/Gemfile.nix.lock b/Gemfile.nix.lock new file mode 100644 index 0000000..cb7d726 --- /dev/null +++ b/Gemfile.nix.lock @@ -0,0 +1,132 @@ +PATH + remote: . + specs: + bashcov (3.1.2) + simplecov (~> 0.22.0) + +GEM + remote: https://rubygems.org/ + specs: + aruba (2.2.0) + bundler (>= 1.17, < 3.0) + contracts (>= 0.16.0, < 0.18.0) + cucumber (>= 8.0, < 10.0) + rspec-expectations (~> 3.4) + thor (~> 1.0) + ast (2.4.2) + bigdecimal (3.1.7) + builder (3.2.4) + bundler-audit (0.9.1) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) + contracts (0.17) + cucumber (9.2.0) + builder (~> 3.2) + cucumber-ci-environment (> 9, < 11) + cucumber-core (> 13, < 14) + cucumber-cucumber-expressions (~> 17.0) + cucumber-gherkin (> 24, < 28) + cucumber-html-formatter (> 20.3, < 22) + cucumber-messages (> 19, < 25) + diff-lcs (~> 1.5) + mini_mime (~> 1.1) + multi_test (~> 1.1) + sys-uname (~> 1.2) + cucumber-ci-environment (10.0.1) + cucumber-core (13.0.2) + cucumber-gherkin (>= 27, < 28) + cucumber-messages (>= 20, < 23) + cucumber-tag-expressions (> 5, < 7) + cucumber-cucumber-expressions (17.1.0) + bigdecimal + cucumber-gherkin (27.0.0) + cucumber-messages (>= 19.1.4, < 23) + cucumber-html-formatter (21.3.1) + cucumber-messages (> 19, < 25) + cucumber-messages (22.0.0) + cucumber-tag-expressions (6.1.0) + diff-lcs (1.5.1) + docile (1.4.0) + ffi (1.16.3) + json (2.7.2) + language_server-protocol (3.17.0.3) + mini_mime (1.1.5) + multi_test (1.1.0) + parallel (1.24.0) + parser (3.3.0.5) + ast (~> 2.4.1) + racc + racc (1.7.3) + rainbow (3.1.1) + rake (13.2.1) + regexp_parser (2.9.0) + rexml (3.2.6) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) + rubocop (1.63.2) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.2) + parser (>= 3.3.0.4) + rubocop-capybara (2.20.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.25.1) + rubocop (~> 1.41) + rubocop-rake (0.6.0) + rubocop (~> 1.0) + rubocop-rspec (2.29.1) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + rubocop-rspec_rails (~> 2.28) + rubocop-rspec_rails (2.28.3) + rubocop (~> 1.40) + ruby-progressbar (1.13.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.4) + sys-uname (1.2.3) + ffi (~> 1.1) + thor (1.3.1) + unicode-display_width (2.5.0) + yard (0.9.36) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + aruba + bashcov! + bundler-audit + cucumber + rake + rspec + rubocop + rubocop-rake + rubocop-rspec + yard + +BUNDLED WITH + 2.4.10 diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 0000000..085426a --- /dev/null +++ b/HACKING.md @@ -0,0 +1,166 @@ +# Hacking on Bashcov + +[Nix development shell]: #entering-the-nix-development-shell +[`Gemfile.nix.lock`]: ./Gemfile.nix.lock +[`gemset.nix`]: ./gemset.nix +[`bashcov.gemspec`]: ./bashcov.gemspec + +## Nix flake usage + +This project supplies a [`flake.nix`](./flake.nix) file defining a Nix +flake[^nix-flakes] that makes it possible to build, test, run, and hack on +Bashcov using the [Nix package manager](https://nixos.org) + +[^nix-flakes]: See the [NixOS wiki](https://nixos.wiki/wiki/Flakes) and the + [`nix flake` page in the Nix package manager reference manual](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html) + for background on Nix flakes. + +This Nix flake defines three important important outputs: + +1. A [Nix package for Bashcov](#building-the-bashcov-package), +2. A [Nix flake check](#nix-flake-checks) (test) that runs Bashcov's + unit and feature tests, +3. A [Nix application](#running-the-nix-application),[^app] and +4. A [Nix development shell],[^devshell]. + +[^devshell]: Based on the [`numtide/devshell`](https://github.com/numtide/devshell) project. +[^app]: Runnable with `nix run`. + +In order to work on the Bashcov project's Nix features, +you'll need to [install the Nix package manager](https://nixos.org/download.html) and +[ensure that the `flakes` and `nix-command` experimental features are enabled](https://nixos.wiki/wiki/Flakes#Enable_flakes). + +### Building the Bashcov package + +To build the Bashcov package exposed by this flake, run the +following command:[^verbose-output] + +[^verbose-output]: Note that the `-L` flag can be omitted for terser output. + +```shell-session +$ nix build -L '.#' +``` + +Or: + +```shell-session +$ nix build -L '.#bashcov' +``` + +These two forms are functionally equivalent because the +Bashcov package is the default package. + +In addition to building the package, `nix build` will place a symbolic link to +its output path at `./result` (`ls -lAR ./result/`, `tree ./result/`, or +similar to see what the package contains). + +### Nix flake checks + +This project includes a test of Bashcov's functionality and features, exposed +as a Nix flake check. In essence, this runs the Bashcov test suite, but inside +the Nix build environment[^nix-builder-execution] (which may be +sandboxed[^nix-sandbox]). + +[^nix-builder-execution]: The Nix build environment is described [here](https://nixos.org/manual/nix/stable/language/derivations#builder-execution). +[^nix-sandbox]: The Nix sandbox is described [here](https://nixos.org/manual/nix/stable/command-ref/conf-file.html#conf-sandbox). + +This project also includes a test that Nix source files are properly +formatted.[^treefmt-nix-check] + +[^treefmt-nix-check]: Defined by [`treefmt-nix`](https://github.com/numtide/treefmt-nix#flakes). + +#### Running Nix flake checks + +To run Nix flake checks, execute the following command:[^verbose-output] + +```shell-session +$ nix flake check -L +``` + +If a check fails, `nix` will print a diagnostic message and exit with nonzero +status. + +##### Running a check for a specific system + +Running `nix flake check` will execute Nix flake checks for all supported +systems.[^supported-systems] To run a check for a particular system, instead +use the `nix build` command. For instance, to execute the Bashcov unit and +feature tests with Nix on the `x86_64-linux` system, run:[^verbose-output] + +```shell-session +$ nix build -L '.#checks.x86_64-linux.bashcov' +``` + +[^supported-systems]: Run `nix flake show` to view flake outputs namespaced by + all supported systems. + +### Running the Nix application + +To run Bashcov itself: + +```shell-session +$ nix run '.#' -- +``` + +To run commands from [the Nix development shell](#entering-the-nix-development-shell) +but without entering the shell: + +```shell-session +$ nix run '.#devshell' -- +``` + +For instance, to run [the `update-deps` shell command](#summary-of-available-commands): + +```shell-session +$ nix run '.#devshell' -- update-deps +``` + +### Entering the Nix development shell + +To enter the Nix development shell, run the following command: + +```shell-session +$ nix develop +``` + +You will be presented with a menu of commands available within the development +shell. + +#### Summary of available commands + +- `fmt`: format all Nix code in this project using + [`alejandra`](https://github.com/kamadorueda/alejandra). +- `bundix`: tool for managing Nix <=> Ruby integration assets (Bundix lives + [here](https://github.com/nix-community/bundix)). +- `update-deps`: update [the Nix-specific lockfile][`Gemfile.nix.lock`] and + [Nix gemset][`gemset.nix`]. +- `update-deps-conservative`: update [the Nix-specific lockfile][`Gemfile.nix.lock`] + and [Nix gemset][`gemset.nix`] if (and only if) `nix build` fails _without_ + updates to those assets **and** `nix build` succeeds _with_ updates to them. + +### Maintenance of Nix assets + +The Bashcov Nix package depends on [`nixpkgs`'s Ruby +integration](https://nixos.org/manual/nixpkgs/stable/#developing-with-ruby); +specifically, it uses the `bundlerEnv` function to create an environment with +all of Bashcov's Ruby gem dependencies present. `bundlerEnv` requires a +Bundler lockfile (here, [`Gemfile.nix.lock`]) and a Nix-specific [`gemset.nix`] +that acts as a sort of translation layer between Bundler and Nix. + +Both of these files must be updated from time to time in order to reflect +changes in [`bashcov.gemspec`], including certain changes to Bashcov itself +(e.g. version bumps). + +> **Note** +> If [`bashcov.gemspec`] is updated without updating the Bundler lockfile and +> [`gemset.nix`], the Bashcov Nix package will fail to build. + +The [Nix development shell] includes two convenience commands for managing +these assets: + +- `update-deps` unconditionally updates [`Gemfile.nix.lock`] with + [`bundle lock`](https://bundler.io/v2.4/man/bundle-lock.1.html), then updates + [`gemset.nix`] to reflect any changes to the Bundler lockfile. +- `update-deps-conservative` does the same, but if (and only if) doing so fixes + failures running `nix build`. That is, it updates the assets if it looks + like problems with those assets have broken the Bashcov Nix package. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..0b7797a --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,115 @@ +# Installing Bashcov + +## Installation as a Ruby gem + +Bashcov is distributed as [a Ruby gem](https://guides.rubygems.org/) -- that +is, as a software package for [the Ruby programming language](https://www.ruby-lang.org/en/). It is hosted on +https://rubygems.org/ and is installable with tools distributed with Ruby +itself. + +### Prerequisites + +- Ruby (installation instructions [here](https://www.ruby-lang.org/en/documentation/installation/)). +- Development tools (primarily, a C compiler and `make`). These are needed + because certain of Bashcov's Ruby gem dependencies include native extensions + that must be compiled for your host platform. Installation instructions are + OS- and distribution-specific; please consult your OS and/or distribution's + documentation. + +### Installation with the `gem` command + +The `gem` executable is included with the Ruby distribution. To install +Bashcov for your current user, run: + +```shell-session +$ gem install bashcov +``` + +Now you can run Bashcov with: + +```shell-session +$ bashcov -- +``` + +### Installation with Bundler + +[Bundler](https://bundler.io/), an environment manager for Ruby, is included in +(quoting the https://bundler.io/ landing page) "[a]ny modern distribution of +Ruby". To install Bashcov with Bundler, create a file named `Gemfile` in your +project's top-level directory and ensure it contains the following: + +```ruby +source 'https://rubygems.org' +gem 'bashcov' +``` + +Then, run this to install Bashcov (and the other gems specified in your +`Gemfile`): + +```shell-session +$ bundle install +``` + +Finally, to run Bashcov, execute: + +```shell-session +$ bundle exec bashcov -- +``` + +For more on Bundler, please see [its "Getting Started" guide](https://bundler.io/guides/getting_started.html#getting-started). + +## Installation with the Nix package manager + +Bashcov is available using [the Nix package manager](https://nixos.org/). +Specifically, Bashcov exposes a [Nix flake](https://nixos.org/) (a sort of +supercharged package) consumable via various subcommands of the `nix` command +line tool. + +### Running Bashcov as [a Nix application](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-run.html) + +You can use Nix to run Bashcov without first explicitly installing it: + +```shell-session +$ nix run 'github:infertux/bashcov' -- +``` + +### Adding Bashcov to [a Nix shell environment](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-shell) + +You can start a shell with Bashcov available like so: + +```shell-session +$ command -v bashcov || echo ':(' 1>&2 +:( +$ nix shell 'github:infertux/bashcov' +$ command -v bashcov || echo ':(' 1>&2 +/nix/store/ns3phdbmfxkf6xqbz0lzha0846ngbmwc-bashcov-3.0.2/bin/bashcov +``` + +### Incorporating Bashcov into your Nix flake + +You can incorporate Bashcov into your own flake by declaring it as an input and +then referencing its output attribute `packages..bashcov`. For +instance, to include Bashcov in a [`nix develop` environment](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-develop), +you could do something like the following: + +```nix +# flake.nix + +{ + inputs = { + bashcov.url = "github:infertux/bashcov"; + bashcov.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = inputs @ { nixpkgs, bashcov, ... }: let + system = "x86_64-linux"; + in { + devShells.${system}.default = nixpkgs.legacyPackages.${system}.mkShell { + packages = [inputs.bashcov.packages.${system}.bashcov]; + }; + }; +} +``` + +Now, when you execute `nix develop` from within your flake project, the +`bashcov` command will be available in your environment. diff --git a/README.md b/README.md index 6b637ac..99f4eb5 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ Here are example coverages generated by Bashcov: If the `gem` command is unavailable, you need to [install Ruby](https://www.ruby-lang.org/en/documentation/installation/) first. +For more information, including other installation methods, see [`INSTALL.md`](./INSTALL.md). + ## Usage `bashcov --help` prints all available options. Here are some examples: @@ -55,6 +57,8 @@ See [advanced usage](./USAGE.md) for more information. Bug reports and patches are most welcome. See the [contribution guidelines](https://github.com/infertux/bashcov/blob/master/CONTRIBUTING.md). +For development tips, see [the hacking guide](./HACKING.md). + ## Sponsorship Bashcov was [created in 2012](https://github.com/infertux/bashcov/commit/f65e65e5aa3377beb334beee9924136a34a913e8) and it needs your help. I have been maintaining the project for over a decade and keeping it working with new releases of Bash and Ruby takes time. If you use Bashcov professionally, please considerer supporting it on [Liberapay](https://liberapay.com/infertux) through your employer or directly. Thank you for supporting *Free and Open-Source Software*. diff --git a/bashcov.gemspec b/bashcov.gemspec index 219e2ad..17eb131 100644 --- a/bashcov.gemspec +++ b/bashcov.gemspec @@ -23,7 +23,14 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(__dir__) do - `git ls-files -z`.split("\x0").reject do |f| + git_ls_files_z = `git ls-files -z 2>/dev/null` + + # Handle contexts (like the Nix build sandbox) where we are not in a git + # repository -- in such cases, include the entire current directory + # hierarchy. + files = $? == 0 ? git_ls_files_z.split("\x0") : Dir["**/*"] + + files.reject do |f| (File.expand_path(f) == __FILE__) || f.start_with?(*%w[test/ spec/ features/ .git]) end end diff --git a/compat.nix b/compat.nix new file mode 100644 index 0000000..1e32e28 --- /dev/null +++ b/compat.nix @@ -0,0 +1,26 @@ +let + haveFlakeLock = builtins.pathExists ./flake.lock; + + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + + ref = + if haveFlakeLock + then lock.nodes.flake-compat.locked.rev + else "master"; + + checksum = + if haveFlakeLock + then { + sha256 = lock.nodes.flake-compat.locked.narHash; + } + else {}; + + args = + { + url = "https://github.com/edolstra/flake-compat/archive/${ref}.tar.gz"; + } + // checksum; + + flakeCompat = fetchTarball args; +in + import flakeCompat {src = ./.;} diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..10bfcb5 --- /dev/null +++ b/default.nix @@ -0,0 +1 @@ +(import ./compat.nix).defaultNix diff --git a/features/command_name.feature b/features/command_name.feature index 4594624..03a491c 100644 --- a/features/command_name.feature +++ b/features/command_name.feature @@ -17,13 +17,14 @@ Feature: And a file named "test.sh" with mode "0755" and with: """ - #!/bin/bash + #!/usr/bin/env bash date """ - Scenario: no explicit command name is provided + Scenario: no explicit command name is provided and /bin/bash is executable + When `/bin/bash` is executable - When I run the following commands with bashcov: + And I run the following commands with bashcov: """ ./test.sh """ @@ -31,6 +32,17 @@ Feature: Then the results should contain the commands: | /bin/bash ./test.sh | + Scenario: no explicit command name is provided and /bin/bash is not executable + When `/bin/bash` is not executable + + And I run the following commands with bashcov: + """ + ./test.sh + """ + + Then the results should contain the commands: + | bash ./test.sh | + Scenario: the command name is set with `--command-name` When I run the following commands with bashcov using `--command-name hey-i-am-a-command`: diff --git a/features/result_merging.feature b/features/result_merging.feature index 839fda4..852643b 100644 --- a/features/result_merging.feature +++ b/features/result_merging.feature @@ -18,7 +18,7 @@ Feature: And a file named "simple.sh" with mode "0755" and with: """ - #!/bin/bash + #!/usr/bin/env bash tr '[[:lower:]]' '[[:upper:]]' <<<'shhh' """ diff --git a/features/step_definitions/bashcov_steps.rb b/features/step_definitions/bashcov_steps.rb index 53baf66..f46d980 100644 --- a/features/step_definitions/bashcov_steps.rb +++ b/features/step_definitions/bashcov_steps.rb @@ -59,14 +59,21 @@ def simplecov_merged_result options << " --root ." end + # Use `/bin/sh` as the interpreter for maximum portability. None of the + # generated `bashcov` commands should require Bash-specific features. steps %( When I run the following commands: """ + #!/bin/sh #{commands.each_line.map { |command| "bashcov #{options} -- #{command}" }.join("\n")} """ ) end +When(/`([^`]+)` is (not )?executable/) do |command, negation| + skip_this_scenario unless !negation.nil? ^ File.executable?(command) +end + Then(/^the results should contain the commands:$/) do |table| commands = table.raw.flatten result_command_names = simplecov_results.map(&:command_name).map { |name| name.split(", ") }.flatten diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..29b233b --- /dev/null +++ b/flake.lock @@ -0,0 +1,139 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1688380630, + "narHash": "sha256-8ilApWVb1mAi4439zS3iFeIT0ODlbrifm/fegWwgHjA=", + "owner": "numtide", + "repo": "devshell", + "rev": "f9238ec3d75cefbb2b42a44948c4e8fb1ae9a205", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1688466019, + "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1689137672, + "narHash": "sha256-QZoHxr0a73x6rQcAo5CiwYpysHbSnk7lAR8/16um7mM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "98da3dd0de6660d4abed7bb74e748694bd803413", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1688049487, + "narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "flake-compat": "flake-compat", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1689070229, + "narHash": "sha256-99VU2FTkEdO3/1Qr78fHWWlN5GaOGLaXDi26PNiUf+I=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "3c54278bf7b8642eba174a22ca02d5552c21dc0b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b8b9991 --- /dev/null +++ b/flake.nix @@ -0,0 +1,225 @@ +{ + description = "Code coverage tool for Bash"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; + + devshell.url = "github:numtide/devshell"; + devshell.inputs.nixpkgs.follows = "nixpkgs"; + + flake-compat.url = "github:edolstra/flake-compat"; + flake-compat.flake = false; + + flake-parts.url = "github:hercules-ci/flake-parts"; + + treefmt-nix.url = "github:numtide/treefmt-nix"; + treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = inputs @ { + self, + devshell, + flake-parts, + treefmt-nix, + ... + }: + flake-parts.lib.mkFlake {inherit inputs;} ({lib, ...}: { + systems = [ + "aarch64-darwin" + "aarch64-linux" + "x86_64-darwin" + "x86_64-linux" + ]; + + imports = [ + devshell.flakeModule + flake-parts.flakeModules.easyOverlay + treefmt-nix.flakeModule + ]; + + perSystem = { + config, + pkgs, + self', + system, + ... + }: let + newestRuby = let + rubies = lib.filterAttrs (name: value: let + hasRubyEngine = builtins.tryEval (value ? rubyEngine); + in + lib.hasPrefix "ruby" name && hasRubyEngine.success && hasRubyEngine.value) + pkgs; + + recentRubies = + lib.foldlAttrs ( + acc: name: value: let + v = toString value.version; + in + acc ++ (lib.optional (lib.versionAtLeast v "3" && lib.versionOlder v "4") {inherit name value;}) + ) [] + rubies; + + sortedRubies = lib.sort (x: y: lib.versionAtLeast (toString x.value.version) (toString y.value.version)) recentRubies; + in + if (lib.length sortedRubies) > 0 + then lib.head sortedRubies + else { + name = "ruby"; + value = pkgs.ruby; + }; + + ruby = newestRuby.value; + rubyName = newestRuby.name; + in { + # `nix run '.#devshell' -- update-deps`, etc. + apps.devshell = self'.devShells.default.flakeApp; + + checks = { + inherit (config.packages) bashcov; + }; + + devshells.default = { + imports = [ + # Ruby support, including prerequisites for compiling native + # extensions. + "${inputs.devshell}/extra/language/ruby.nix" + ]; + + commands = [ + { + name = "fmt"; + category = "linting"; + help = "Format the Nix code in this project"; + command = '' + exec ${config.treefmt.build.wrapper}/bin/treefmt "$@" + ''; + } + + { + package = ruby; + category = "development"; + } + + { + package = pkgs.bundix; + category = "maintenance"; + } + + { + name = "update-deps"; + category = "maintenance"; + help = "Update dependencies with Bundler and Bundix"; + command = '' + export NIX_PATH="nixpkgs=${toString pkgs.path}''${NIX_PATH:+:''${NIX_PATH}}" + lockfile="''${PRJ_ROOT:-.}/Gemfile.nix.lock" + bundle lock --lockfile "$lockfile" + exec bundix --ruby ${lib.escapeShellArg rubyName} --lockfile="$lockfile" "$@" + ''; + } + + { + name = "update-deps-conservative"; + category = "maintenance"; + help = "Update dependencies with Bundler and Bundix (if necessary)"; + + # If `nix build` succeeds, then presume that the lockfiles do not + # need to be updated. If `nix build` fails after updating the + # lockfile, out-of-date dependencies weren't to blame for the + # build failure. + command = '' + export NIX_PATH="nixpkgs=${toString pkgs.path}''${NIX_PATH:+:''${NIX_PATH}}" + nix build && exit + if update-deps; then + nix build || { + rc="$?" + git restore "''${PRJ_ROOT}/Gemfile.nix.lock" "''${PRJ_ROOT}/gemset.nix" || rc="$?" + exit "$rc" + } + fi + ''; + } + ]; + }; + + overlayAttrs = { + inherit (config.packages) bashcov; + }; + + packages = { + bashcov = pkgs.callPackage ({ + lib, + bash, + buildRubyGem, + makeWrapper, + ruby, + gems, + doCheck ? true, # Whether to run RSpec and Cucumber Rake tasks. + }: let + inherit (gems'.gems.bashcov) version; + gems' = gems.override {inherit ruby;}; + gemName = gems'.gems.bashcov.name; + in + assert lib.assertMsg (gems'.gems ? bashcov) "bashcov: gem set must contain `bashcov`"; + buildRubyGem { + inherit doCheck gemName version ruby; + + name = "${gemName}-${version}"; + + src = self; + + nativeBuildInputs = [bash makeWrapper]; + propagatedBuildInputs = [gems']; + + checkPhase = '' + ${config.packages.gems.wrappedRuby}/bin/rake cucumber spec + ''; + + # Replace shebangs like "#!/usr/bin/env bash" with Nix store + # paths. Note that we need to do this for `./bin/bashcov` + # itself, as otherwise running `bashcov` from Cucumber features + # during the check phase will fail because `/usr/bin/env` does + # not exist in the build sandbox. + doPatch = true; + postPatch = '' + patchShebangs ./bin ./features ./spec + ''; + + # Ensure that Bashcov can find Bash, but make this Bash + # low-precedence by placing its /bin/ at the end of PATH. + postInstall = '' + wrapProgram $out/bin/bashcov --suffix PATH : ${bash}/bin + ''; + }) { + inherit ruby; + inherit (config.packages) gems; + }; + + default = config.packages.bashcov; + + gems = pkgs.callPackage ({ + bundlerEnv, + ruby, + }: + bundlerEnv { + inherit ruby; + pname = "bashcov"; + gemdir = self; + lockfile = ./Gemfile.nix.lock; + version = "3.0.2"; + postBuild = '' + for gem in $out/${ruby.gemPath}/bundler/gems/*; do + ln -sfrT "$gem" $out/${ruby.gemPath}/gems/"''${gem##*/}" + done + ''; + }) {inherit ruby;}; + }; + + treefmt = { + programs.alejandra.enable = true; + flakeFormatter = true; + projectRootFile = "flake.nix"; + }; + }; + }); +} diff --git a/gemset.nix b/gemset.nix new file mode 100644 index 0000000..1a270a2 --- /dev/null +++ b/gemset.nix @@ -0,0 +1,513 @@ +{ + aruba = { + dependencies = ["contracts" "cucumber" "rspec-expectations" "thor"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "08rvdags0a0ipadiwg2xqn5ksqnf12jp2768r6g7cm0lhj5wlsdb"; + type = "gem"; + }; + version = "2.2.0"; + }; + ast = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "04nc8x27hlzlrr5c2gn7mar4vdr0apw5xg22wp6m8dx3wqr04a0y"; + type = "gem"; + }; + version = "2.4.2"; + }; + bashcov = { + dependencies = ["simplecov"]; + groups = ["default"]; + platforms = []; + source = { + path = ./.; + type = "path"; + }; + version = "3.1.2"; + }; + bigdecimal = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0cq1c29zbkcxgdihqisirhcw76xc768z2zpd5vbccpq0l1lv76g7"; + type = "gem"; + }; + version = "3.1.7"; + }; + builder = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "045wzckxpwcqzrjr353cxnyaxgf0qg22jh00dcx7z38cys5g1jlr"; + type = "gem"; + }; + version = "3.2.4"; + }; + bundler-audit = { + dependencies = ["thor"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0gdx0019vj04n1512shhdx7hwphzqmdpw4vva2k551nd47y1dixx"; + type = "gem"; + }; + version = "0.9.1"; + }; + contracts = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0gfybfsb6kqxvvcrv1q7bfjaxmq73pf3vqy4bbzarkbajil05ii5"; + type = "gem"; + }; + version = "0.17"; + }; + cucumber = { + dependencies = ["builder" "cucumber-ci-environment" "cucumber-core" "cucumber-cucumber-expressions" "cucumber-gherkin" "cucumber-html-formatter" "cucumber-messages" "diff-lcs" "mini_mime" "multi_test" "sys-uname"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "19qsfgahkah4k0pajxc04mjn8pig7g4n9nkcarg1nzs2612c29s8"; + type = "gem"; + }; + version = "9.2.0"; + }; + cucumber-ci-environment = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0cc6w7dqlmnp59ymi7pyspm3w4m7fn37x6b18pziv62wr373yvmv"; + type = "gem"; + }; + version = "10.0.1"; + }; + cucumber-core = { + dependencies = ["cucumber-gherkin" "cucumber-messages" "cucumber-tag-expressions"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0r00zcgr4lx1vimg7wz9q590bsyylx9i4nfzqzqj92jkp832pdpf"; + type = "gem"; + }; + version = "13.0.2"; + }; + cucumber-cucumber-expressions = { + dependencies = ["bigdecimal"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "14fkk7bfzm9cyacgcyzgkjc3nblflz4rcnlyz0pzd1ypwpqrvgm1"; + type = "gem"; + }; + version = "17.1.0"; + }; + cucumber-gherkin = { + dependencies = ["cucumber-messages"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "063p0slf6fvigdn3jynp5pjf9b05byyyi0jhsyapy46hq4984sif"; + type = "gem"; + }; + version = "27.0.0"; + }; + cucumber-html-formatter = { + dependencies = ["cucumber-messages"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0bg0gskpmvhf91g01ywr03ihkv75jr2mk51hcp61wxpvvwp8z8dx"; + type = "gem"; + }; + version = "21.3.1"; + }; + cucumber-messages = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "06d7dnixz68ivngf6qflmi6xrjshjyi85gmyjrl07pbmhqi6r2nh"; + type = "gem"; + }; + version = "22.0.0"; + }; + cucumber-tag-expressions = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1g0fl6v1677q71nkaib2g3p03jdzrwgfanpi96srb1743qd54bk1"; + type = "gem"; + }; + version = "6.1.0"; + }; + diff-lcs = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1znxccz83m4xgpd239nyqxlifdb7m8rlfayk6s259186nkgj6ci7"; + type = "gem"; + }; + version = "1.5.1"; + }; + docile = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1lxqxgq71rqwj1lpl9q1mbhhhhhhdkkj7my341f2889pwayk85sz"; + type = "gem"; + }; + version = "1.4.0"; + }; + ffi = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1yvii03hcgqj30maavddqamqy50h7y6xcn2wcyq72wn823zl4ckd"; + type = "gem"; + }; + version = "1.16.3"; + }; + json = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0b4qsi8gay7ncmigr0pnbxyb17y3h8kavdyhsh7nrlqwr35vb60q"; + type = "gem"; + }; + version = "2.7.2"; + }; + language_server-protocol = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0gvb1j8xsqxms9mww01rmdl78zkd72zgxaap56bhv8j45z05hp1x"; + type = "gem"; + }; + version = "3.17.0.3"; + }; + mini_mime = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1vycif7pjzkr29mfk4dlqv3disc5dn0va04lkwajlpr1wkibg0c6"; + type = "gem"; + }; + version = "1.1.5"; + }; + multi_test = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "042d6a1416h3di57z107ygmjdgacrpyswi73ryz75yv3v36m1rg9"; + type = "gem"; + }; + version = "1.1.0"; + }; + parallel = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "15wkxrg1sj3n1h2g8jcrn7gcapwcgxr659ypjf75z1ipkgxqxwsv"; + type = "gem"; + }; + version = "1.24.0"; + }; + parser = { + dependencies = ["ast" "racc"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "11r6kp8wam0nkfvnwyc1fmvky102r1vcfr84vi2p1a2wa0z32j3p"; + type = "gem"; + }; + version = "3.3.0.5"; + }; + racc = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "01b9662zd2x9bp4rdjfid07h09zxj7kvn7f5fghbqhzc625ap1dp"; + type = "gem"; + }; + version = "1.7.3"; + }; + rainbow = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0smwg4mii0fm38pyb5fddbmrdpifwv22zv3d3px2xx497am93503"; + type = "gem"; + }; + version = "3.1.1"; + }; + rake = { + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "17850wcwkgi30p7yqh60960ypn7yibacjjha0av78zaxwvd3ijs6"; + type = "gem"; + }; + version = "13.2.1"; + }; + regexp_parser = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1ndxm0xnv27p4gv6xynk6q41irckj76q1jsqpysd9h6f86hhp841"; + type = "gem"; + }; + version = "2.9.0"; + }; + rexml = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "05i8518ay14kjbma550mv0jm8a6di8yp5phzrd8rj44z9qnrlrp0"; + type = "gem"; + }; + version = "3.2.6"; + }; + rspec = { + dependencies = ["rspec-core" "rspec-expectations" "rspec-mocks"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "14xrp8vq6i9zx37vh0yp4h9m0anx9paw200l1r5ad9fmq559346l"; + type = "gem"; + }; + version = "3.13.0"; + }; + rspec-core = { + dependencies = ["rspec-support"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0k252n7s80bvjvpskgfm285a3djjjqyjcarlh3aq7a4dx2s94xsm"; + type = "gem"; + }; + version = "3.13.0"; + }; + rspec-expectations = { + dependencies = ["diff-lcs" "rspec-support"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0bhhjzwdk96vf3gq3rs7mln80q27fhq82hda3r15byb24b34h7b2"; + type = "gem"; + }; + version = "3.13.0"; + }; + rspec-mocks = { + dependencies = ["diff-lcs" "rspec-support"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0rkzkcfk2x0qjr5fxw6ib4wpjy0hqbziywplnp6pg3bm2l98jnkk"; + type = "gem"; + }; + version = "3.13.0"; + }; + rspec-support = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "03z7gpqz5xkw9rf53835pa8a9vgj4lic54rnix9vfwmp2m7pv1s8"; + type = "gem"; + }; + version = "3.13.1"; + }; + rubocop = { + dependencies = ["json" "language_server-protocol" "parallel" "parser" "rainbow" "regexp_parser" "rexml" "rubocop-ast" "ruby-progressbar" "unicode-display_width"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1dmj955zbvhlrzzag6hc77xdyyzn8ihvffpjalnzn5asxhz7jcdl"; + type = "gem"; + }; + version = "1.63.2"; + }; + rubocop-ast = { + dependencies = ["parser"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1v3q8n48w8h809rqbgzihkikr4g3xk72m1na7s97jdsmjjq6y83w"; + type = "gem"; + }; + version = "1.31.2"; + }; + rubocop-capybara = { + dependencies = ["rubocop"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0f5r9di123hc4x2h453a143986plfzz9935bwc7267wj8awl8s1a"; + type = "gem"; + }; + version = "2.20.0"; + }; + rubocop-factory_bot = { + dependencies = ["rubocop"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0d012phc7z5h1j1d2aisnbkmqlb95sld5jriia5qg2gpgbg1nxb2"; + type = "gem"; + }; + version = "2.25.1"; + }; + rubocop-rake = { + dependencies = ["rubocop"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1nyq07sfb3vf3ykc6j2d5yq824lzq1asb474yka36jxgi4hz5djn"; + type = "gem"; + }; + version = "0.6.0"; + }; + rubocop-rspec = { + dependencies = ["rubocop" "rubocop-capybara" "rubocop-factory_bot" "rubocop-rspec_rails"]; + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "04rfx0f0ns3vfz16fvbxgc9ivjh6gkpqfdi0qsg3grq660dfhkjk"; + type = "gem"; + }; + version = "2.29.1"; + }; + rubocop-rspec_rails = { + dependencies = ["rubocop"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0618lfncmvnvkwa1jb0kga1f2yiiw1809flkj4kg52nagh3z4scp"; + type = "gem"; + }; + version = "2.28.3"; + }; + ruby-progressbar = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0cwvyb7j47m7wihpfaq7rc47zwwx9k4v7iqd9s1xch5nm53rrz40"; + type = "gem"; + }; + version = "1.13.0"; + }; + simplecov = { + dependencies = ["docile" "simplecov-html" "simplecov_json_formatter"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "198kcbrjxhhzca19yrdcd6jjj9sb51aaic3b0sc3pwjghg3j49py"; + type = "gem"; + }; + version = "0.22.0"; + }; + simplecov-html = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0yx01bxa8pbf9ip4hagqkp5m0mqfnwnw2xk8kjraiywz4lrss6jb"; + type = "gem"; + }; + version = "0.12.3"; + }; + simplecov_json_formatter = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0a5l0733hj7sk51j81ykfmlk2vd5vaijlq9d5fn165yyx3xii52j"; + type = "gem"; + }; + version = "0.1.4"; + }; + sys-uname = { + dependencies = ["ffi"]; + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "03j9qpqip89a0vk6s0gvhxzhbvafjcj5rss7i3jwha0831aivib3"; + type = "gem"; + }; + version = "1.2.3"; + }; + thor = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1vq1fjp45az9hfp6fxljhdrkv75cvbab1jfrwcw738pnsiqk8zps"; + type = "gem"; + }; + version = "1.3.1"; + }; + unicode-display_width = { + groups = ["default" "development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1d0azx233nags5jx3fqyr23qa2rhgzbhv8pxp46dgbg1mpf82xky"; + type = "gem"; + }; + version = "2.5.0"; + }; + yard = { + groups = ["development"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1r0b8w58p7gy06wph1qdjv2p087hfnmhd9jk23vjdj803dn761am"; + type = "gem"; + }; + version = "0.9.36"; + }; +} diff --git a/lib/bashcov.rb b/lib/bashcov.rb index df83421..536ffeb 100644 --- a/lib/bashcov.rb +++ b/lib/bashcov.rb @@ -78,8 +78,11 @@ def bash_path # Support the same `BASHCOV_BASH_PATH` environment variable used in the spec tests. return ENV.fetch("BASHCOV_BASH_PATH", nil) unless ENV.fetch("BASHCOV_BASH_PATH", "").empty? - # Fall back to standard Bash location. - "/bin/bash" + # Fall back to standard Bash location, if available. + return "/bin/bash" if File.executable?("/bin/bash") + + # Otherwise, try to execute a Bash from `PATH`. + "bash" end def bash_version diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..f248e88 --- /dev/null +++ b/shell.nix @@ -0,0 +1 @@ +(import ./compat.nix).shellNix diff --git a/spec/bashcov/runner_spec.rb b/spec/bashcov/runner_spec.rb index 0b8a254..8d295bf 100644 --- a/spec/bashcov/runner_spec.rb +++ b/spec/bashcov/runner_spec.rb @@ -86,7 +86,7 @@ # @note "with a temporary script" context expects +script_text+ to be defined. let(:script_text) do <<-BASH.gsub(/\A\s+/, "") - #!/bin/bash + #!/usr/bin/env bash echo "Hello, world!" LINENO= echo "What line is this?" diff --git a/spec/bashcov_spec.rb b/spec/bashcov_spec.rb index 5195ff2..7bf3bbc 100644 --- a/spec/bashcov_spec.rb +++ b/spec/bashcov_spec.rb @@ -81,6 +81,7 @@ before { @args += ["--bash-path", "/bin/bash"] } it "sets it properly" do + skip("/bin/bash does not exist") unless File.executable?("/bin/bash") subject expect(described_class.bash_path).to eq("/bin/bash") end diff --git a/spec/test_app/never_called.sh b/spec/test_app/never_called.sh index 38c8e58..fbf795e 100755 --- a/spec/test_app/never_called.sh +++ b/spec/test_app/never_called.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash echo "I'm never executed by any script" diff --git a/spec/test_app/scripts/array.sh b/spec/test_app/scripts/array.sh index 3e23bf7..5ff1fd0 100755 --- a/spec/test_app/scripts/array.sh +++ b/spec/test_app/scripts/array.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash arr=( foo diff --git a/spec/test_app/scripts/case.sh b/spec/test_app/scripts/case.sh index 46b404f..891614b 100755 --- a/spec/test_app/scripts/case.sh +++ b/spec/test_app/scripts/case.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash function switch() { case $1 in diff --git a/spec/test_app/scripts/cd.sh b/spec/test_app/scripts/cd.sh index 7b92a4a..a16aedf 100755 --- a/spec/test_app/scripts/cd.sh +++ b/spec/test_app/scripts/cd.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash : "${this:=that}" : "${that:=thing}" && touch /dev/fd/1 @@ -7,7 +7,7 @@ dir="$(cd "${BASH_SOURCE%/*}" || : ; pwd -P)" todir='' printf -v todir -- '%s' "$(find "$dir" -type d | head -n 1)" -(cd ../.. || : ; cd "$HOME" || : ) +(cd ../.. || : ; cd "$OLDPWD" || :) cd ../.. || : diff --git a/spec/test_app/scripts/comments.sh b/spec/test_app/scripts/comments.sh index dd4b7c3..8242879 100755 --- a/spec/test_app/scripts/comments.sh +++ b/spec/test_app/scripts/comments.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash case "space" in space) # comment diff --git a/spec/test_app/scripts/delete.sh b/spec/test_app/scripts/delete.sh index df6e55c..ff47db8 100755 --- a/spec/test_app/scripts/delete.sh +++ b/spec/test_app/scripts/delete.sh @@ -1,8 +1,8 @@ -#!/bin/bash +#!/usr/bin/env bash -cat > tmp.sh <<'BASH' -#!/bin/bash -rm -v $0 +cat > tmp.sh <&2 diff --git a/spec/test_app/scripts/simple.sh b/spec/test_app/scripts/simple.sh index 145ae81..4f0cdf8 100755 --- a/spec/test_app/scripts/simple.sh +++ b/spec/test_app/scripts/simple.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # basic test program diff --git a/spec/test_app/scripts/source.sh b/spec/test_app/scripts/source.sh index f3fd913..7d5b2fe 100755 --- a/spec/test_app/scripts/source.sh +++ b/spec/test_app/scripts/source.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash echo "I'm sourcing sourced.txt" diff --git a/spec/test_app/scripts/sourced.txt b/spec/test_app/scripts/sourced.txt index 3348904..4e25b56 100644 --- a/spec/test_app/scripts/sourced.txt +++ b/spec/test_app/scripts/sourced.txt @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash echo "I'm sourced.txt" diff --git a/spec/test_app/scripts/unicode.sh b/spec/test_app/scripts/unicode.sh index 1b57fc2..6c3993b 100755 --- a/spec/test_app/scripts/unicode.sh +++ b/spec/test_app/scripts/unicode.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # ¹²³ echo 'I am full of unicode characters! äéíóúü¿æ' diff --git a/spec/test_app/test_suite.sh b/spec/test_app/test_suite.sh index 61a071c..c17d9dd 100755 --- a/spec/test_app/test_suite.sh +++ b/spec/test_app/test_suite.sh @@ -1,8 +1,8 @@ -#!/bin/bash +#!/usr/bin/env bash echo "UID=${UID}" >&2 echo "PS4=${PS4}" >&2 cd $(dirname $0) -find scripts -type f -perm -111 -print -exec '{}' \; +find scripts -type f -perm -111 -print -execdir '{}' \; diff --git a/test.sh b/test.sh index 4f8fb58..36ae173 100755 --- a/test.sh +++ b/test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail