diff --git a/bin/main.ml b/bin/main.ml index 35adfb5..d857031 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -3,7 +3,7 @@ open Ns.Cmd open Ns.Util let main - ({ installables; target_info; printcmd; force_experimental_features } : + ({ installables; target_info; printcmd; force_experimental_features; sh; _ } : Cli.strategy) = let ({ entrypoint; attribute; subshell_dir } : Cli.target_info) = target_info in @@ -17,19 +17,20 @@ let main | Some entrypoint -> if Unix.flake_exists_at entrypoint then - ( Cmd.nix_develop entrypoint attribute force_experimental_features + ( Cmd.nix_develop entrypoint attribute force_experimental_features sh , if Unix.shell_exists_at entrypoint - then Some (Cmd.legacy_nix_shell_from_entrypoint entrypoint attribute) + then Some (Cmd.legacy_nix_shell_from_entrypoint entrypoint attribute sh) else None ) else if Unix.shell_exists_at entrypoint - then Cmd.legacy_nix_shell_from_entrypoint entrypoint attribute, None + then Cmd.legacy_nix_shell_from_entrypoint entrypoint attribute sh, 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 + sh , Option.map - (fun installables -> Cmd.legacy_nix_shell_from_installables installables) + (fun installables -> Cmd.legacy_nix_shell_from_installables installables sh) (Uri.combine_installables_tr installables) ) in { workdir; primary; fallback } diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..7e713e7 --- /dev/null +++ b/default.nix @@ -0,0 +1 @@ +(builtins.getFlake (toString ./.)).outputs.packages.${builtins.currentSystem}.default diff --git a/lib/cli.ml b/lib/cli.ml index 703b8c6..67a9685 100644 --- a/lib/cli.ml +++ b/lib/cli.ml @@ -19,6 +19,9 @@ module Cli = struct ; target_info : target_info ; printcmd : bool ; force_experimental_features : string list option + ; sh : string + ; sharg : string list + ; impure : bool } (** Positional URI arguments passed to the program via the command line. *) @@ -39,9 +42,28 @@ module Cli = struct Arg.(value & flag & info [ "force"; "f" ] ~doc) ;; + let sh = + let doc = + "The shell to use in the subshell. Accepts a package name (e.g. zsh), a nix-store \ + path, or a local path (e.g. /bin/sh). Defaults to the current user's login shell \ + or bash if no user is logged in." + in + Arg.(value & opt filepath (Unix.eval_sh (Unix.pw_shell ())) & info [ "sh" ] ~doc) + ;; + + let sharg = + let doc = "Supply arguments to the shell." in + Arg.(value & opt_all string [] & info [ "sharg" ] ~doc) + ;; + + let impure = + let doc = "Allow the current shell environment to bleed into the subshell." in + Arg.(value & flag & info [ "impure" ] ~doc) + ;; + (** Processes the args so that the main function knows what to do with them. *) let make_strategy = - let build original_args printcmd force_experimental_features = + let build original_args printcmd force_experimental_features sh sharg impure = let installables, target_info = let default_installables = [] and default_target = @@ -111,9 +133,13 @@ module Cli = struct ; "nix-command" ] else None) + ; sh = Unix.eval_sh sh + ; sharg + ; impure } in - Term.(const build $ uris $ printcmd $ force_experimental_features) + Term.( + const build $ uris $ printcmd $ force_experimental_features $ sh $ sharg $ impure) ;; let cmd entrypoint = diff --git a/lib/cmd.ml b/lib/cmd.ml index f103e5b..2e5612b 100644 --- a/lib/cmd.ml +++ b/lib/cmd.ml @@ -15,32 +15,34 @@ module Cmd = struct | args -> List.fold_left (fun cmd arg -> Bos.Cmd.add_arg cmd arg) cmd args ;; - let nix_develop entrypoint attribute force_experimental_features = + (* TODO: nix develop --ignore-env behaves differently than nix-shell --pure + which is causing none of the packages to show up. *) + let nix_develop entrypoint attribute force_experimental_features sh = Bos.Cmd.v "nix" |>+ [ "develop" ] @ [ Uri.sprintf_uri_attr_opt entrypoint attribute ] |>+ Option.value ~default:[] force_experimental_features - |>+ [ "--command"; Unix.shell () ] + (* |>+ [ "--ignore-env"; "-k"; "TERM"; "-k"; "TERMINFO"; "-k"; "HOME"; "-k"; "USER"; "-k"; "DISPLAY" ] *) + |>+ [ "--command"; sh ] ;; - let legacy_nix_shell_from_entrypoint entrypoint attribute = + let legacy_nix_shell_from_entrypoint entrypoint attribute sh = Bos.Cmd.v "nix-shell" |>+ Option.value ~default:[] (Option.map (fun attr -> [ "--attr"; attr ]) attribute) |>+ [ entrypoint ] - |>+ [ "--command"; Unix.shell () ] + |>+ [ "--pure" ] + |>+ [ "--command"; sh ] ;; - let nix_shell installables force_experimental_features = + let nix_shell installables force_experimental_features sh = Bos.Cmd.v "nix" |>+ [ "shell" ] |>+ installables |>+ Option.value ~default:[] force_experimental_features + |>+ [ "--command"; sh ] ;; - 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 () ] + let legacy_nix_shell_from_installables installables sh = + Bos.Cmd.v "nix-shell" |>+ [ "--packages" ] @ installables |>+ [ "--command"; sh ] ;; let print_strategy ({ workdir; primary; fallback } : strategy) = diff --git a/lib/util.ml b/lib/util.ml index 5b420f9..6987956 100644 --- a/lib/util.ml +++ b/lib/util.ml @@ -23,7 +23,7 @@ module Util = struct ;; (** Get the user's login shell *) - let shell () = (Unix.getpwuid (Unix.getuid ())).Unix.pw_shell + let pw_shell () = (Unix.getpwuid (Unix.getuid ())).Unix.pw_shell (** Whether a flake.nix file exists at the given directory *) let flake_exists_at dir = Sys.file_exists (Filename.concat dir "flake.nix") @@ -33,6 +33,39 @@ module Util = struct Sys.file_exists (Filename.concat dir "shell.nix") || Sys.file_exists (Filename.concat dir "default.nix") ;; + + (** Determines which shell to use. If the user gives a package + we build it and evaluate the main program to get the store path. + If there is no login shell, default to nixpkgs#bash. *) + let eval_sh sh = + let default = "bash" + and get_bin pkg = + let run_cmd cmd = + let ic = Unix.open_process_in cmd in + let result = Option.get (In_channel.input_line ic) in + (* TODO: Handle this potential error *) + let _ = Unix.close_process_in ic in + result + in + let store_path = + run_cmd (Printf.sprintf "nix-build '' -A %s --no-out-link --quiet" pkg) + and program = + run_cmd + (Printf.sprintf + "nix-instantiate '' --eval --raw -A %s.meta.mainProgram" + pkg) + in + let bin_path = Printf.sprintf "%s/bin/%s" store_path program in + bin_path + in + if sh = "/noshell" || sh = "/sbin/nologin" + then get_bin default + else ( + match String.split_on_char '/' sh with + | [ "" ] -> Error.handle_ns_error "invalid argument: shell cannot be empty\n%!" + | [ pkg ] -> get_bin pkg + | _ -> sh) + ;; end (** Types and functions for parsing, interacting and formatting URIs. *) diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..a605591 --- /dev/null +++ b/shell.nix @@ -0,0 +1 @@ +(builtins.getFlake (toString ./.)).outputs.devShells.${builtins.currentSystem}.default