From 2ac834a2c68f7e73cb5459f1d9840e1068fd5e74 Mon Sep 17 00:00:00 2001 From: eureka-cpu Date: Sat, 7 Feb 2026 14:17:15 -0800 Subject: [PATCH 1/3] feat: Support `nixpkgs#` and `pkgs#` URIs --- lib/util.ml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/util.ml b/lib/util.ml index e466eca..8dfb6d8 100644 --- a/lib/util.ml +++ b/lib/util.ml @@ -71,6 +71,8 @@ module Util = struct let parse_target target = let parse_uri uri attr_opt = match String.split_on_char ':' uri with + | [ "nixpkgs" ] | [ "pkgs" ] -> + RemoteResource (sprintf_uri_attr_opt "nixpkgs" attr_opt) | [ path ] -> let path = Unix.realpath path and is_multiattr maybe_multiattr = From b2cc09cb875446d5212ec5083ff0de2d93f7daa6 Mon Sep 17 00:00:00 2001 From: eureka-cpu Date: Sun, 8 Feb 2026 12:39:03 -0800 Subject: [PATCH 2/3] update uri parsing strategy to accomodate for local nixpkgs references, fix tests --- bin/main.ml | 54 ++--- lib/cli.ml | 19 +- lib/cmd.ml | 89 ++++++-- lib/util.ml | 52 ++++- test/test_ns.rs | 546 +++++++++++++++++++++++++++++++----------------- 5 files changed, 513 insertions(+), 247 deletions(-) diff --git a/bin/main.ml b/bin/main.ml index c016d04..35adfb5 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -7,34 +7,34 @@ let main Cli.strategy) = let ({ entrypoint; attribute; subshell_dir } : Cli.target_info) = target_info in - Option.value ~default:(Option.value ~default:(Sys.getcwd ()) entrypoint) subshell_dir - |> Unix.cd; - let cmd = - match entrypoint with - | Some entrypoint -> - (match Unix.flake_exists_at entrypoint with - | true -> - Cmd.builder "nix" - |>+ [ "develop" ] @ [ Uri.sprintf_uri_attr_opt entrypoint attribute ] - |>+ Option.value ~default:[] force_experimental_features - | false -> - (match Unix.shell_exists_at entrypoint with - | true -> - Cmd.builder "nix-shell" - |>+ Option.value - ~default:[] - (Option.map (fun attr -> [ "--attr"; attr ]) attribute) - |>+ [ entrypoint ] - | false -> - Error.handle_ns_error "no available devshell entrypoint: %s\n%!" entrypoint)) - |>+ [ "--command"; Unix.shell ] - | None -> - Cmd.builder "nix" - |>+ [ "shell" ] - |>+ installables - |>+ Option.value ~default:[] force_experimental_features + let strategy = + let workdir = + Option.value + ~default:(Option.value ~default:(Sys.getcwd ()) entrypoint) + subshell_dir + and primary, fallback = + match entrypoint with + | Some entrypoint -> + if Unix.flake_exists_at entrypoint + then + ( Cmd.nix_develop entrypoint attribute force_experimental_features + , if Unix.shell_exists_at entrypoint + then Some (Cmd.legacy_nix_shell_from_entrypoint entrypoint attribute) + else None ) + else if Unix.shell_exists_at entrypoint + then Cmd.legacy_nix_shell_from_entrypoint entrypoint attribute, None + else Error.handle_ns_error "no available devshell entrypoint: %s\n%!" entrypoint + | None -> + ( Cmd.nix_shell + (List.map Uri.uri_to_string installables) + force_experimental_features + , Option.map + (fun installables -> Cmd.legacy_nix_shell_from_installables installables) + (Uri.combine_installables_tr installables) ) + in + { workdir; primary; fallback } in - if printcmd then print_endline (Cmd.to_string cmd) else ignore (Cmd.run cmd) + if printcmd then print_strategy strategy else execute_strategy strategy ;; Cli.eval main diff --git a/lib/cli.ml b/lib/cli.ml index 116dea6..703b8c6 100644 --- a/lib/cli.ml +++ b/lib/cli.ml @@ -15,7 +15,7 @@ module Cli = struct (** Processed args that will be consumed by the main function to determine the behavior of the program. *) type strategy = - { installables : string list + { installables : Uri.uri list ; target_info : target_info ; printcmd : bool ; force_experimental_features : string list option @@ -25,7 +25,12 @@ module Cli = struct let uris = Arg.(value & pos_all dirpath [] & info []) let printcmd = - let doc = "Print the command to stdout instead of executing it." in + let doc = + "Print the command strategy as newline separated values to stdout instead of \ + executing it. The first command changes to the working directory, the second is \ + the primary nix command and the last is an optional fallback nix command in the \ + event that flake/nix-command features fail." + in Arg.(value & flag & info [ "printcmd" ] ~doc) ;; @@ -64,7 +69,9 @@ module Cli = struct (* Two args passed *) (*******************) | [ maybe_subshell_dir; target ] -> - (match Uri.parse_target maybe_subshell_dir, Uri.parse_target target with + let maybe_subshell_dir = Uri.parse_target maybe_subshell_dir + and target = Uri.parse_target target in + (match maybe_subshell_dir, target with (* If maybe_subshell_dir does not have any attributes we know the user wants to change directories. *) | ( LocalResourceMaybeAttr (subshell_dir, None) , LocalResourceMaybeAttr (entrypoint, attribute) ) -> @@ -73,12 +80,10 @@ module Cli = struct ; attribute ; subshell_dir = Some subshell_dir } ) - | LocalResourceMaybeAttr (subshell_dir, None), LocalResourceMultiAttr _ -> + | ( LocalResourceMaybeAttr (subshell_dir, None) + , (LocalResourceMultiAttr _ | RemoteResource _) ) -> ( [ target ] , { entrypoint = None; attribute = None; subshell_dir = Some subshell_dir } ) - | LocalResourceMaybeAttr (subshell_dir, None), RemoteResource uri -> - ( [ uri ] - , { entrypoint = None; attribute = None; subshell_dir = Some subshell_dir } ) | _ -> Uri.parse_targets_tr original_args, default_target) (*****************************) (* Three or more args passed *) diff --git a/lib/cmd.ml b/lib/cmd.ml index 828847c..236317c 100644 --- a/lib/cmd.ml +++ b/lib/cmd.ml @@ -1,28 +1,85 @@ (** Functions and operators used in running commands *) module Cmd = struct - open Bos + open Util.Util - (** Construct a command builder - - Example: - Cmd.builder "nix" |>+ \[ "flake" "show" "--json" \] - *) - let builder cmd = Cmd.v cmd + type strategy = + { workdir : string + ; primary : Bos.Cmd.t + ; fallback : Bos.Cmd.t option + } (** Pipe-arg operator, used in adding arguments to a command *) let ( |>+ ) cmd = function | [] -> cmd - | [ arg ] -> Cmd.add_arg cmd arg - | args -> List.fold_left (fun cmd arg -> Cmd.add_arg cmd arg) cmd args + | [ arg ] -> Bos.Cmd.add_arg cmd arg + | args -> List.fold_left (fun cmd arg -> Bos.Cmd.add_arg cmd arg) cmd args + ;; + + let nix_develop entrypoint attribute force_experimental_features = + Bos.Cmd.v "nix" + |>+ [ "develop" ] @ [ Uri.sprintf_uri_attr_opt entrypoint attribute ] + |>+ Option.value ~default:[] force_experimental_features + |>+ [ "--command"; Unix.shell ] + ;; + + let legacy_nix_shell_from_entrypoint entrypoint attribute = + Bos.Cmd.v "nix-shell" + |>+ Option.value ~default:[] (Option.map (fun attr -> [ "--attr"; attr ]) attribute) + |>+ [ entrypoint ] + |>+ [ "--command"; Unix.shell ] ;; - (** Convert a command into a string *) - let to_string cmd = Cmd.to_string cmd + let nix_shell installables force_experimental_features = + Bos.Cmd.v "nix" + |>+ [ "shell" ] + |>+ installables + |>+ Option.value ~default:[] force_experimental_features + ;; - (** Run a command + let legacy_nix_shell_from_installables installables = + (* Need to combine/validate that the installables given are all Nixpkgs *) + Bos.Cmd.v "nix-shell" + |>+ [ "--packages" ] @ installables + |>+ [ "--command"; Unix.shell ] + ;; - Example: - Cmd.run (Cmd.builder "nix" |>+ \[ "show-config" "--json" \]) - *) - let run cmd = OS.Cmd.run cmd + let print_strategy ({ workdir; primary; fallback } : strategy) = + print_endline + (Printf.sprintf + "'cd' '%s'\n%s\n%s" + workdir + (Bos.Cmd.to_string primary) + (Option.value ~default:"'None'" (Option.map Bos.Cmd.to_string fallback))) + ;; + + let execute_strategy ({ workdir; primary; fallback } : strategy) = + Unix.cd workdir; + match Bos.OS.Cmd.run_status primary with + | Ok (`Exited 0) -> () (* success, nothing else to do *) + | Ok (`Exited code | `Signaled code) -> + (* primary failed *) + (match fallback with + | Some fallback -> + (* show primary error but don't exit *) + Printf.eprintf "primary command failed (exit %d), trying fallback\n%!" code; + (match Bos.OS.Cmd.run_status fallback with + | Ok (`Exited 0) -> () + | Ok (`Exited code | `Signaled code) -> exit code + | Error (`Msg msg) -> + prerr_endline msg; + exit 1) + | None -> exit code) + | Error (`Msg msg) -> + (* primary couldn't even be spawned *) + prerr_endline msg; + (match fallback with + | Some fallback -> + (match Bos.OS.Cmd.run_status fallback with + | Ok (`Exited 0) -> () + | Ok (`Exited code | `Signaled code) -> exit code + | Error (`Msg msg) -> + prerr_endline msg; + exit 1) + | None -> exit 1) + ;; end diff --git a/lib/util.ml b/lib/util.ml index 8dfb6d8..a5b02a2 100644 --- a/lib/util.ml +++ b/lib/util.ml @@ -40,6 +40,12 @@ module Util = struct module Uri = struct (* *) + (** A local reference to nixpkgs, eg. {nixpkgs#...} or {pkgs#...} (which resolves to the former) *) + type nixpkgs = + { uri : string + ; installables : string list + } + (** URI variants, like {/path/to/flake#drv} or {github:NixOS/nixpkgs#drv} *) type uri = (* A path with up to one attribute, eg. /path/to/resouce or /path/to/resource#... *) @@ -48,6 +54,8 @@ module Util = struct | LocalResourceMultiAttr of string * string (* A remote resource, eg. github:NixOS/nixpkgs *) | RemoteResource of string + (* A local reference to nixpkgs, eg. nixpkgs#... or pkgs#... (which resolves to the former) *) + | Nixpkgs of nixpkgs (** Format a uri and attribute into a string *) let sprintf_uri_attr path attr = Printf.sprintf "%s#%s" path attr @@ -65,19 +73,38 @@ module Util = struct | LocalResourceMaybeAttr (path, attr_opt) -> sprintf_uri_attr_opt path attr_opt | LocalResourceMultiAttr (path, attr) -> sprintf_uri_attr path attr | RemoteResource uri -> uri + | Nixpkgs { uri; _ } -> uri ;; (** Parse target argument for optional attribute selection *) let parse_target target = let parse_uri uri attr_opt = + let is_multiattr maybe_multiattr = + String.starts_with ~prefix:"{" maybe_multiattr + && String.ends_with ~suffix:"}" maybe_multiattr + in match String.split_on_char ':' uri with | [ "nixpkgs" ] | [ "pkgs" ] -> - RemoteResource (sprintf_uri_attr_opt "nixpkgs" attr_opt) - | [ path ] -> - let path = Unix.realpath path - and is_multiattr maybe_multiattr = - String.starts_with ~prefix:"{" maybe_multiattr + let parse_installables attr_opt = + match attr_opt with + | Some attr -> + if is_multiattr attr + then ( + match String.split_on_char '{' attr with + | [ _; rhs ] -> + (match String.split_on_char '}' rhs with + | [ lhs; _ ] -> String.split_on_char ',' lhs + | _ -> []) + | _ -> []) + else [ attr ] + | None -> [] in + Nixpkgs + { uri = sprintf_uri_attr_opt "nixpkgs" attr_opt + ; installables = parse_installables attr_opt + } + | [ path ] -> + let path = Unix.realpath path in (match attr_opt with | Some attr -> if is_multiattr attr @@ -93,15 +120,24 @@ module Util = struct Error.handle_ns_error "invalid uri or attribute selection syntax: %s\n%!" target ;; - (** Parse target arguments recursively, returning a list of strings. + (** Parse target arguments recursively, returning a list of {!type:uri}. This function is tail recursive optimized. *) let parse_targets_tr targets = let rec parse_targets acc = function | [] -> acc - | target :: remaining -> - parse_targets (uri_to_string (parse_target target) :: acc) remaining + | target :: remaining -> parse_targets (parse_target target :: acc) remaining in parse_targets [] targets ;; + + let combine_installables_tr installables = + let rec combine_installables acc = function + | [] -> Some acc + | Nixpkgs { installables; _ } :: remaining -> + combine_installables (acc @ installables) remaining + | _ -> None + in + combine_installables [] installables + ;; end end diff --git a/test/test_ns.rs b/test/test_ns.rs index d4f8ff1..868476c 100644 --- a/test/test_ns.rs +++ b/test/test_ns.rs @@ -33,10 +33,16 @@ fn shell_dir() -> path::PathBuf { .expect("failed to canonicalize path to ns binary") } -fn assert_stdout_eq(stdout: &[u8], expected: &[&str]) { +fn assert_stdout_eq(stdout: &[u8], expected: &[&[&str]]) { let stdout = String::from_utf8_lossy(stdout); - let stdout = stdout.replace('\'', ""); - let stdout: Vec<&str> = stdout.split_whitespace().collect(); + let stdout: Vec> = stdout + .trim_end() + .split('\n') + .map(|cmd| { + let cmd = cmd.replace('\'', ""); + cmd.split_whitespace().map(|cmd| cmd.to_string()).collect() + }) + .collect(); if expected != stdout.as_slice() { panic!( @@ -61,11 +67,15 @@ mod nix_develop_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "develop", - &flake_dir().to_string_lossy(), - "--command", - env_shell(), + &["cd", &flake_dir().to_string_lossy()], + &[ + "nix", + "develop", + &flake_dir().to_string_lossy(), + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -85,15 +95,19 @@ mod nix_develop_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "develop", - &flake_dir().to_string_lossy(), - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", - "--command", - env_shell(), + &["cd", &flake_dir().to_string_lossy()], + &[ + "nix", + "develop", + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -109,7 +123,11 @@ mod nix_develop_tests { assert!(output.status.success()); assert_stdout_eq( &output.stdout, - &["nix", "develop", &flake_uri, "--command", env_shell()], + &[ + &["cd", &flake_dir().to_string_lossy()], + &["nix", "develop", &flake_uri, "--command", env_shell()], + &["None"], + ], ); } @@ -125,15 +143,19 @@ mod nix_develop_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "develop", - &flake_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", - "--command", - env_shell(), + &["cd", &flake_dir().to_string_lossy()], + &[ + "nix", + "develop", + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -149,11 +171,15 @@ mod nix_develop_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "develop", - &flake_dir().to_string_lossy(), - "--command", - env_shell(), + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "develop", + &flake_dir().to_string_lossy(), + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -174,15 +200,19 @@ mod nix_develop_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "develop", - &flake_dir().to_string_lossy(), - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", - "--command", - env_shell(), + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "develop", + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -198,7 +228,11 @@ mod nix_develop_tests { assert!(output.status.success()); assert_stdout_eq( &output.stdout, - &["nix", "develop", &flake_uri, "--command", env_shell()], + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "develop", &flake_uri, "--command", env_shell()], + &["None"], + ], ); } @@ -214,15 +248,19 @@ mod nix_develop_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "develop", - &flake_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", - "--command", - env_shell(), + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "develop", + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -242,10 +280,14 @@ mod legacy_nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix-shell", - &shell_dir().to_string_lossy(), - "--command", - env_shell(), + &["cd", &shell_dir().to_string_lossy()], + &[ + "nix-shell", + &shell_dir().to_string_lossy(), + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -265,10 +307,14 @@ mod legacy_nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix-shell", - &shell_dir().to_string_lossy(), - "--command", - env_shell(), + &["cd", &shell_dir().to_string_lossy()], + &[ + "nix-shell", + &shell_dir().to_string_lossy(), + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -285,12 +331,16 @@ mod legacy_nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix-shell", - "--attr", - "default", - &shell_dir().to_string_lossy(), - "--command", - env_shell(), + &["cd", &shell_dir().to_string_lossy()], + &[ + "nix-shell", + "--attr", + "default", + &shell_dir().to_string_lossy(), + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -307,12 +357,16 @@ mod legacy_nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix-shell", - "--attr", - "default", - &shell_dir().to_string_lossy(), - "--command", - env_shell(), + &["cd", &shell_dir().to_string_lossy()], + &[ + "nix-shell", + "--attr", + "default", + &shell_dir().to_string_lossy(), + "--command", + env_shell(), + ], + &["None"], ], ); } @@ -330,7 +384,14 @@ mod nix_shell_tests { .expect("failed to get command output"); assert!(output.status.success()); - assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri]); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", &flake_uri], + &["None"], + ], + ); } #[test] @@ -345,13 +406,17 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - &flake_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -371,7 +436,11 @@ mod nix_shell_tests { assert!(output.status.success()); assert_stdout_eq( &output.stdout, - &["nix", "shell", &flake_uri, &flake_dir().to_string_lossy()], + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", &flake_uri, &flake_dir().to_string_lossy()], + &["None"], + ], ); } @@ -392,14 +461,18 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - &flake_uri, - &flake_dir().to_string_lossy(), - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + &flake_uri, + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -413,7 +486,14 @@ mod nix_shell_tests { .expect("failed to get command output"); assert!(output.status.success()); - assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri, &flake_uri]); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", &flake_uri, &flake_uri], + &["None"], + ], + ); } #[test] @@ -428,14 +508,18 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - &flake_uri, - &flake_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + &flake_uri, + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -449,7 +533,14 @@ mod nix_shell_tests { .expect("failed to get command output"); assert!(output.status.success()); - assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri]); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", &flake_uri], + &["None"], + ], + ); } #[test] @@ -464,13 +555,17 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - &flake_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -490,7 +585,11 @@ mod nix_shell_tests { assert!(output.status.success()); assert_stdout_eq( &output.stdout, - &["nix", "shell", &flake_uri, &flake_dir().to_string_lossy()], + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", &flake_uri, &flake_dir().to_string_lossy()], + &["None"], + ], ); } @@ -511,14 +610,18 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - &flake_uri, - &flake_dir().to_string_lossy(), - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + &flake_uri, + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -532,7 +635,14 @@ mod nix_shell_tests { .expect("failed to get command output"); assert!(output.status.success()); - assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri, &flake_uri]); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", &flake_uri, &flake_uri], + &["None"], + ], + ); } #[test] @@ -547,14 +657,18 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - &flake_uri, - &flake_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + &flake_uri, + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -569,7 +683,14 @@ mod nix_shell_tests { .expect("failed to get command output"); assert!(output.status.success()); - assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri, remote_uri]); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", &flake_uri, remote_uri], + &["None"], + ], + ); } #[test] @@ -585,14 +706,18 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - &flake_uri, - remote_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + &flake_uri, + remote_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -615,11 +740,15 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - remote_uri, - &flake_uri, - &flake_dir().to_string_lossy(), + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_dir().to_string_lossy(), + ], + &["None"], ], ); } @@ -643,15 +772,19 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - remote_uri, - &flake_uri, - &flake_dir().to_string_lossy(), - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -668,7 +801,11 @@ mod nix_shell_tests { assert!(output.status.success()); assert_stdout_eq( &output.stdout, - &["nix", "shell", remote_uri, &flake_uri, &flake_uri], + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", remote_uri, &flake_uri, &flake_uri], + &["None"], + ], ); } @@ -685,15 +822,19 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - remote_uri, - &flake_uri, - &flake_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -708,7 +849,14 @@ mod nix_shell_tests { .expect("failed to get command output"); assert!(output.status.success()); - assert_stdout_eq(&output.stdout, &["nix", "shell", &flake_uri, remote_uri]); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", &flake_uri, remote_uri], + &["None"], + ], + ); } #[test] @@ -724,14 +872,18 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - &flake_uri, - remote_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + &flake_uri, + remote_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -754,11 +906,15 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - remote_uri, - &flake_uri, - &flake_dir().to_string_lossy(), + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_dir().to_string_lossy(), + ], + &["None"], ], ); } @@ -782,15 +938,19 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - remote_uri, - &flake_uri, - &flake_dir().to_string_lossy(), - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_dir().to_string_lossy(), + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } @@ -807,7 +967,11 @@ mod nix_shell_tests { assert!(output.status.success()); assert_stdout_eq( &output.stdout, - &["nix", "shell", remote_uri, &flake_uri, &flake_uri], + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", remote_uri, &flake_uri, &flake_uri], + &["None"], + ], ); } @@ -824,15 +988,19 @@ mod nix_shell_tests { assert_stdout_eq( &output.stdout, &[ - "nix", - "shell", - remote_uri, - &flake_uri, - &flake_uri, - "--extra-experimental-features", - "flakes", - "--extra-experimental-features", - "nix-command", + &["cd", &dune_test_dir().to_string_lossy()], + &[ + "nix", + "shell", + remote_uri, + &flake_uri, + &flake_uri, + "--extra-experimental-features", + "flakes", + "--extra-experimental-features", + "nix-command", + ], + &["None"], ], ); } From abaee25c509ab98b7568742e94724c28456007f4 Mon Sep 17 00:00:00 2001 From: eureka-cpu Date: Sun, 8 Feb 2026 21:27:49 -0600 Subject: [PATCH 3/3] add nixpkgs tests --- lib/cmd.ml | 4 +- test/dune | 7 +- test/src/flake_shell/flake.nix | 12 +++ test/src/flake_shell/shell.nix | 6 ++ test/test_ns.rs | 159 +++++++++++++++++++++++++-------- 5 files changed, 146 insertions(+), 42 deletions(-) create mode 100644 test/src/flake_shell/flake.nix create mode 100644 test/src/flake_shell/shell.nix diff --git a/lib/cmd.ml b/lib/cmd.ml index 236317c..a8b3e7c 100644 --- a/lib/cmd.ml +++ b/lib/cmd.ml @@ -44,12 +44,12 @@ module Cmd = struct ;; let print_strategy ({ workdir; primary; fallback } : strategy) = - print_endline + print_string (Printf.sprintf "'cd' '%s'\n%s\n%s" workdir (Bos.Cmd.to_string primary) - (Option.value ~default:"'None'" (Option.map Bos.Cmd.to_string fallback))) + (Option.value ~default:"" (Option.map Bos.Cmd.to_string fallback))) ;; let execute_strategy ({ workdir; primary; fallback } : strategy) = diff --git a/test/dune b/test/dune index 979cd4b..3acffcd 100644 --- a/test/dune +++ b/test/dune @@ -2,7 +2,12 @@ (test (name test_ns) - (deps test_ns src/flake/flake.nix src/shell/shell.nix) + (deps + test_ns + src/flake/flake.nix + src/shell/shell.nix + src/flake_shell/flake.nix + src/flake_shell/shell.nix) (flags (:standard))) ;; Rule to compile the Rust test file into a binary diff --git a/test/src/flake_shell/flake.nix b/test/src/flake_shell/flake.nix new file mode 100644 index 0000000..2550caa --- /dev/null +++ b/test/src/flake_shell/flake.nix @@ -0,0 +1,12 @@ +{ + inputs.nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + outputs = { self, nixpkgs }: + let + inherit (nixpkgs.lib) genAttrs; + inherit (nixpkgs.lib.systems) flakeExposed; + eachSystem = f: genAttrs flakeExposed (system: f (import nixpkgs { inherit system; })); + in + { + packages = eachSystem (pkgs: { default = pkgs.hello; }); + }; +} diff --git a/test/src/flake_shell/shell.nix b/test/src/flake_shell/shell.nix new file mode 100644 index 0000000..e586841 --- /dev/null +++ b/test/src/flake_shell/shell.nix @@ -0,0 +1,6 @@ +{ pkgs ? import { } }: +pkgs.mkShell { + buildInputs = builtins.attrValues { + inherit (pkgs) hello; + }; +} diff --git a/test/test_ns.rs b/test/test_ns.rs index 868476c..edaa50b 100644 --- a/test/test_ns.rs +++ b/test/test_ns.rs @@ -23,20 +23,26 @@ fn flake_dir() -> path::PathBuf { .join("src") .join("flake") .canonicalize() - .expect("failed to canonicalize path to ns binary") + .expect("failed to canonicalize path to flake") } fn shell_dir() -> path::PathBuf { dune_test_dir() .join("src") .join("shell") .canonicalize() - .expect("failed to canonicalize path to ns binary") + .expect("failed to canonicalize path to shell") +} +fn flake_shell_dir() -> path::PathBuf { + dune_test_dir() + .join("src") + .join("flake_shell") + .canonicalize() + .expect("failed to canonicalize path to flake_shell") } fn assert_stdout_eq(stdout: &[u8], expected: &[&[&str]]) { let stdout = String::from_utf8_lossy(stdout); let stdout: Vec> = stdout - .trim_end() .split('\n') .map(|cmd| { let cmd = cmd.replace('\'', ""); @@ -75,7 +81,7 @@ mod nix_develop_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -107,7 +113,7 @@ mod nix_develop_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -126,7 +132,7 @@ mod nix_develop_tests { &[ &["cd", &flake_dir().to_string_lossy()], &["nix", "develop", &flake_uri, "--command", env_shell()], - &["None"], + &[], ], ); } @@ -155,7 +161,7 @@ mod nix_develop_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -179,7 +185,7 @@ mod nix_develop_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -212,7 +218,7 @@ mod nix_develop_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -231,7 +237,7 @@ mod nix_develop_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "develop", &flake_uri, "--command", env_shell()], - &["None"], + &[], ], ); } @@ -260,7 +266,36 @@ mod nix_develop_tests { "--command", env_shell(), ], - &["None"], + &[], + ], + ); + } + + #[test] + fn flake_and_shell_fallback() { + let output = process::Command::new(ns()) + .args([flake_shell_dir().as_os_str(), "--printcmd".as_ref()]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &flake_shell_dir().to_string_lossy()], + &[ + "nix", + "develop", + &flake_shell_dir().to_string_lossy(), + "--command", + env_shell(), + ], + &[ + "nix-shell", + &flake_shell_dir().to_string_lossy(), + "--command", + env_shell(), + ], ], ); } @@ -287,7 +322,7 @@ mod legacy_nix_shell_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -314,7 +349,7 @@ mod legacy_nix_shell_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -340,7 +375,7 @@ mod legacy_nix_shell_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -366,7 +401,7 @@ mod legacy_nix_shell_tests { "--command", env_shell(), ], - &["None"], + &[], ], ); } @@ -389,7 +424,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", &flake_uri], - &["None"], + &[], ], ); } @@ -416,7 +451,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -439,7 +474,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", &flake_uri, &flake_dir().to_string_lossy()], - &["None"], + &[], ], ); } @@ -472,7 +507,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -491,7 +526,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", &flake_uri, &flake_uri], - &["None"], + &[], ], ); } @@ -519,7 +554,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -538,7 +573,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", &flake_uri], - &["None"], + &[], ], ); } @@ -565,7 +600,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -588,7 +623,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", &flake_uri, &flake_dir().to_string_lossy()], - &["None"], + &[], ], ); } @@ -621,7 +656,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -640,7 +675,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", &flake_uri, &flake_uri], - &["None"], + &[], ], ); } @@ -668,7 +703,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -688,7 +723,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", &flake_uri, remote_uri], - &["None"], + &[], ], ); } @@ -717,7 +752,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -748,7 +783,7 @@ mod nix_shell_tests { &flake_uri, &flake_dir().to_string_lossy(), ], - &["None"], + &[], ], ); } @@ -784,7 +819,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -804,7 +839,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", remote_uri, &flake_uri, &flake_uri], - &["None"], + &[], ], ); } @@ -834,7 +869,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -854,7 +889,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", &flake_uri, remote_uri], - &["None"], + &[], ], ); } @@ -883,7 +918,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -914,7 +949,7 @@ mod nix_shell_tests { &flake_uri, &flake_dir().to_string_lossy(), ], - &["None"], + &[], ], ); } @@ -950,7 +985,7 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], ], ); } @@ -970,7 +1005,7 @@ mod nix_shell_tests { &[ &["cd", &dune_test_dir().to_string_lossy()], &["nix", "shell", remote_uri, &flake_uri, &flake_uri], - &["None"], + &[], ], ); } @@ -1000,7 +1035,53 @@ mod nix_shell_tests { "--extra-experimental-features", "nix-command", ], - &["None"], + &[], + ], + ); + } + + #[test] + fn nixpkgs_single_attr() { + let nixpkgs = "nixpkgs#hello"; + let output = process::Command::new(ns()) + .args([nixpkgs, "--printcmd"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", nixpkgs], + &["nix-shell", "--packages", "hello", "--command", env_shell()], + ], + ); + } + + #[test] + fn nixpkgs_multi_attr() { + let nixpkgs = "nixpkgs#{hello,cowsay,pipes-rs}"; + let output = process::Command::new(ns()) + .args([nixpkgs, "--printcmd"]) + .output() + .expect("failed to get command output"); + + assert!(output.status.success()); + assert_stdout_eq( + &output.stdout, + &[ + &["cd", &dune_test_dir().to_string_lossy()], + &["nix", "shell", nixpkgs], + &[ + "nix-shell", + "--packages", + "hello", + "cowsay", + "pipes-rs", + "--command", + env_shell(), + ], ], ); }