diff --git a/.github/scripts/depexts/generate-actions.sh b/.github/scripts/depexts/generate-actions.sh index 07bfb37d6a0..f35ba6bb68c 100644 --- a/.github/scripts/depexts/generate-actions.sh +++ b/.github/scripts/depexts/generate-actions.sh @@ -2,7 +2,7 @@ set -eu -#for target in alpine archlinux centos debian fedora gentoo opensuse oraclelinux ubuntu; do +#for target in alpine archlinux centos debian fedora gentoo opensuse oraclelinux ubuntu nix; do target=$1 dir=.github/actions/$target @@ -102,6 +102,12 @@ RUN apt install -y $mainlibs $ocaml RUN apt install -y g++ EOF ;; + nix) + cat >$dir/Dockerfile << EOF +FROM nixos/nix +RUN nix-channel --update +RUN nix-env -i gnum4 git rsync patch bzip2 gnumake wget ocaml ocaml5.1.1-ocaml-compiler-libs unzip gcc diffutils patch getconf-glibc gnused +EOF esac OCAML_INVARIANT="\"ocaml\" {>= \"4.09.0\"$OCAML_CONSTRAINT}" @@ -121,12 +127,34 @@ RUN echo 'default-invariant: [ $OCAML_INVARIANT ]' > /opam/opamrc RUN /usr/bin/opam init --no-setup --disable-sandboxing --bare --config /opam/opamrc git+$OPAM_REPO#$OPAM_REPO_SHA RUN echo 'archive-mirrors: "https://opam.ocaml.org/cache"' >> \$OPAMROOT/config RUN /usr/bin/opam switch create this-opam --formula='$OCAML_INVARIANT' +EOF + +# we can't `nix-env -i binutils` +# https://github.com/NixOS/nix/issues/10587 +if [ $target == "nix" ]; then + cat >>$dir/Dockerfile << EOF +RUN nix-shell -p binutils --run "/usr/bin/opam install opam-core opam-state opam-solver opam-repository opam-format opam-client --deps" +EOF +else + cat >>$dir/Dockerfile << EOF RUN /usr/bin/opam install opam-core opam-state opam-solver opam-repository opam-format opam-client --deps +EOF +fi + +cat >>$dir/Dockerfile << EOF RUN /usr/bin/opam clean -as --logs COPY entrypoint.sh /opam/entrypoint.sh -ENTRYPOINT ["/opam/entrypoint.sh"] EOF +if [ $target == "nix" ]; then + cat >>$dir/Dockerfile << EOF +ENTRYPOINT ["nix-shell", "-p", "binutils", "--run", "/opam/entrypoint.sh"] +EOF +else + cat >>$dir/Dockerfile << EOF +ENTRYPOINT ["/opam/entrypoint.sh"] +EOF +fi ### Generate the entrypoint cat >$dir/entrypoint.sh << EOF @@ -142,10 +170,23 @@ cd /github/workspace #git clone https://github.com/ocaml/opam --single-branch --branch 2.2 --depth 1 local-opam #cd local-opam -opam install . --deps -eval \$(opam env) +/usr/bin/opam install . --deps +eval \$(/usr/bin/opam env) ./configure make + +EOF + +if [ $target == "nix" ]; then + cat >>$dir/entrypoint.sh << EOF +./opam var --global os-family=nixos +./opam var --global os-distribution=nixos + +EOF +fi + + +cat >>$dir/entrypoint.sh << EOF ./opam config report ./opam switch create confs --empty EOF diff --git a/.github/workflows/depexts.yml b/.github/workflows/depexts.yml index cba05576148..6881d79a44b 100644 --- a/.github/workflows/depexts.yml +++ b/.github/workflows/depexts.yml @@ -199,3 +199,21 @@ jobs: - name: depexts actions ubuntu uses: ./.github/actions/ubuntu id: depexts-ubuntu + + depexts-nix: + needs: opam-cache + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: opam binary cache + uses: actions/cache@v3 + with: + path: binary/opam + key: binary-${{ env.OPAMVERSION }} + - name: generate action + run: | + bash .github/scripts/depexts/generate-actions.sh nix + - name: depexts actions nix + uses: ./.github/actions/nix + id: depexts-nix diff --git a/master_changes.md b/master_changes.md index 04f28e8c84d..104a35bb805 100644 --- a/master_changes.md +++ b/master_changes.md @@ -67,6 +67,7 @@ users) * Always pass --no-version-check and --no-write-registry to Cygwin setup [#6046 @dra27] * Use --quiet-mode noinput for the internal Cygwin installation (which is definitely a fully-specified command line) and --quiet-mode unattended for external Cygwin installations (in case the user does need to select something, e.g. a mirror) [#6046 @dra27] * [BUG] Fix apt/debian lookup for installed packages [#6054 @rjbou] + * [NEW] Support providing external dependencies with Nix [#5982 @RyanGibb] ## Format upgrade diff --git a/src/client/opamClient.ml b/src/client/opamClient.ml index 81cc8f6a4b1..3f8ac171727 100644 --- a/src/client/opamClient.ml +++ b/src/client/opamClient.ml @@ -1591,9 +1591,9 @@ let update_with_init_config ?(overwrite=false) config init_config = let check_for_sys_packages config system_packages = if system_packages <> [] then - let ((missing, _) as set) = + let (missing, required, available) = OpamSysInteract.packages_status config - (OpamSysPkg.Set.of_list system_packages) + (OpamSysPkg.Set.of_list system_packages) ~old_packages:OpamSysPkg.Set.empty in if not (OpamSysPkg.Set.is_empty missing) then let vars = OpamFile.Config.global_variables config in @@ -1602,8 +1602,8 @@ let check_for_sys_packages config system_packages = |> OpamVariable.Map.of_list in (*Lazy.force header;*) - OpamSolution.print_depext_msg set; - OpamSolution.install_sys_packages ~confirm:true env config missing () + OpamSolution.print_depext_msg (missing, available); + ignore @@ OpamSolution.install_sys_packages ~confirm:true ~sys_packages:missing ~required env config None let reinit ?(init_config=OpamInitDefaults.init_config()) ~interactive ?dot_profile ?update_config ?env_hook ?completion ?inplace @@ -2299,7 +2299,8 @@ let install_t t ?ask ?(ignore_conflicts=false) ?(depext_only=false) in if depext_only then (OpamSolution.install_depexts ~force_depext:true ~confirm:false t - (OpamSolver.all_packages solution)), None + ~new_packages:(OpamSolver.all_packages solution) + ~all_packages:t.installed), None else let add_roots = OpamStd.Option.map (function diff --git a/src/client/opamSolution.ml b/src/client/opamSolution.ml index 905d53cd82d..0bb6f091cd0 100644 --- a/src/client/opamSolution.ml +++ b/src/client/opamSolution.ml @@ -1114,11 +1114,12 @@ let print_depext_msg (avail, nf) = (* Gets depexts from the state, without checking again, unless [recover] is true. *) -let get_depexts ?(force=false) ?(recover=false) t packages = - if not force && OpamStateConfig.(!r.no_depexts) then OpamSysPkg.Set.empty else +let get_depexts ?(force=false) ?(recover=false) t ~new_packages ~all_packages = + if not force && OpamStateConfig.(!r.no_depexts) then OpamSysPkg.Set.empty, OpamSysPkg.Set.empty else let sys_packages = if recover then - OpamSwitchState.depexts_status_of_packages t packages + OpamSwitchState.depexts_status_of_packages t new_packages + ~old_packages:(OpamPackage.Set.diff all_packages new_packages) else let base = Lazy.force t.sys_packages in (* workaround: st.sys_packages is not always updated with added @@ -1128,37 +1129,38 @@ let get_depexts ?(force=false) ?(recover=false) t packages = (* dirty heuristic: recompute for all non-canonical packages *) OpamPackage.Map.find_opt nv t.repos_package_index <> OpamSwitchState.opam_opt t nv) - packages + new_packages in if OpamPackage.Set.is_empty more_pkgs then base else OpamPackage.Map.union (fun _ x -> x) base - (OpamSwitchState.depexts_status_of_packages t more_pkgs) + (OpamSwitchState.depexts_status_of_packages t more_pkgs ~old_packages:(OpamPackage.Set.diff all_packages more_pkgs)) in - let avail, nf = - OpamPackage.Set.fold (fun pkg (avail,nf) -> + let avail, required, nf = + OpamPackage.Set.fold (fun pkg (avail,req,nf) -> match OpamPackage.Map.find_opt pkg sys_packages with | Some sys -> OpamSysPkg.(Set.union avail sys.s_available), + OpamSysPkg.(Set.union req sys.s_required), OpamSysPkg.(Set.union nf sys.s_not_found) - | None -> avail, nf) - packages (OpamSysPkg.Set.empty, OpamSysPkg.Set.empty) + | None -> avail, req, nf) + all_packages (OpamSysPkg.Set.empty, OpamSysPkg.Set.empty, OpamSysPkg.Set.empty) in print_depext_msg (avail, nf); - avail + avail, required -let install_sys_packages ~map_sysmap ~confirm env config sys_packages t = - let rec entry_point t sys_packages = +let install_sys_packages ~map_sysmap ~confirm ~sys_packages ~required env config t = + let rec entry_point t sys_packages required = if OpamClientConfig.(!r.fake) then (print_command sys_packages; t) else if OpamFile.Config.depext_run_installs config then - if confirm then menu t sys_packages else auto_install t sys_packages + if confirm then menu t sys_packages required else auto_install t sys_packages required else manual_install t sys_packages - and menu t sys_packages = + and menu t sys_packages required = let answer = let pkgman = OpamConsole.colorise `yellow - (OpamSysInteract.package_manager_name ~env config) + (OpamSysInteract.package_manager_name ~env (Option.map (fun t -> t.switch) t) config) in OpamConsole.menu ~unsafe_yes:`Yes ~default:`Yes ~no:`Quit "opam believes some required external dependencies are missing. opam \ @@ -1177,7 +1179,7 @@ let install_sys_packages ~map_sysmap ~confirm env config sys_packages t = in OpamConsole.msg "\n"; match answer with - | `Yes -> auto_install t sys_packages + | `Yes -> auto_install t sys_packages required | `No -> OpamConsole.note "Use 'opam option depext-run-installs=false' \ if you don't want to be prompted again."; @@ -1194,7 +1196,7 @@ let install_sys_packages ~map_sysmap ~confirm env config sys_packages t = if OpamSysPoll.os_distribution env = Some "cygwin" then OpamSysInteract.Cygwin.check_setup ~update:false; let commands = - OpamSysInteract.install_packages_commands ~env config sys_packages + OpamSysInteract.install_packages_commands ~env (Option.map (fun t -> t.switch) t) config sys_packages ~required |> List.map (fun ((`AsAdmin c | `AsUser c), a) -> c::a) in OpamConsole.formatted_msg @@ -1221,19 +1223,19 @@ let install_sys_packages ~map_sysmap ~confirm env config sys_packages t = | `Continue -> check_again t sys_packages | `Ignore -> bypass t | `Quit -> give_up () - and auto_install t sys_packages = + and auto_install t sys_packages required = try if OpamSysPoll.os_distribution env = Some "cygwin" then OpamSysInteract.Cygwin.check_setup ~update:true; - OpamSysInteract.install ~env config sys_packages; (* handles dry_run *) + OpamSysInteract.install ~env (Option.map (fun t -> t.switch) t) config sys_packages ~required; (* handles dry_run *) map_sysmap (fun _ -> OpamSysPkg.Set.empty) t with Failure msg -> OpamConsole.error "%s" msg; check_again t sys_packages and check_again t sys_packages = let open OpamSysPkg.Set.Op in - let needed, notfound = - OpamSysInteract.packages_status ~env config sys_packages + let needed, required, notfound = + OpamSysInteract.packages_status ~env config sys_packages ~old_packages:required in let still_missing = needed ++ notfound in let installed = sys_packages -- still_missing in @@ -1250,7 +1252,7 @@ let install_sys_packages ~map_sysmap ~confirm env config sys_packages t = else (OpamConsole.error "These packages are still missing: %s\n" (syspkgs_to_string sys_packages); - if OpamStd.Sys.tty_in then entry_point t sys_packages + if OpamStd.Sys.tty_in then entry_point t sys_packages required else give_up ()) and bypass t = OpamConsole.note @@ -1269,7 +1271,7 @@ let install_sys_packages ~map_sysmap ~confirm env config sys_packages t = give_up_msg (); OpamStd.Sys.exit_because `Aborted in - if OpamSysPkg.Set.is_empty sys_packages || + if (OpamSysPkg.Set.is_empty sys_packages && OpamSysPkg.Set.is_empty required) || OpamClientConfig.(!r.show) || OpamClientConfig.(!r.assume_depexts) then t @@ -1277,11 +1279,12 @@ let install_sys_packages ~map_sysmap ~confirm env config sys_packages t = try OpamConsole.header_msg "Handling external dependencies"; OpamConsole.msg "\n"; - entry_point t sys_packages + entry_point t sys_packages required with Sys.Break as e -> OpamStd.Exn.finalise e give_up_msg -let install_depexts ?(force_depext=false) ?(confirm=true) t packages = +let install_depexts ?(force_depext=false) ?(confirm=true) t ~new_packages ~all_packages = let map_sysmap f t = + let t = Option.get t in let sys_packages = OpamPackage.Set.fold (fun nv sys_map -> match OpamPackage.Map.find_opt nv sys_map with @@ -1291,23 +1294,23 @@ let install_depexts ?(force_depext=false) ?(confirm=true) t packages = f status.OpamSysPkg.s_available } sys_map | None -> sys_map) - packages + new_packages (Lazy.force t.sys_packages) in - { t with sys_packages = lazy sys_packages } + Some { t with sys_packages = lazy sys_packages } in let confirm = confirm && not (OpamSysInteract.Cygwin.is_internal t.switch_global.config) in - let sys_packages = - get_depexts ~force:force_depext ~recover:force_depext t packages + let sys_packages, required = + get_depexts ~force:force_depext ~recover:force_depext t ~new_packages ~all_packages in let env = t.switch_global.global_variables in let config = t.switch_global.config in - install_sys_packages ~map_sysmap ~confirm env config sys_packages t + Option.get @@ install_sys_packages ~map_sysmap ~confirm env config ~sys_packages ~required (Some t) let install_sys_packages ~confirm = - install_sys_packages ~map_sysmap:(fun _ () -> ()) ~confirm + install_sys_packages ~map_sysmap:(fun _ t -> t) ~confirm (* Apply a solution *) let apply ?ask t ~requested ?print_requested ?add_roots @@ -1330,9 +1333,9 @@ let apply ?ask t ~requested ?print_requested ?add_roots in let t = if OpamClientConfig.(!r.show) then - let _ = get_depexts t virt_inst in t + let _ = get_depexts t ~new_packages:virt_inst ~all_packages:t.installed in t (* Prints the msg about additional depexts to install *) - else install_depexts t virt_inst + else install_depexts t ~new_packages:virt_inst ~all_packages:t.installed in t, Nothing_to_do else ( @@ -1386,13 +1389,14 @@ let apply ?ask t ~requested ?print_requested ?add_roots solution0; ); if OpamClientConfig.(!r.show) then - let _ = get_depexts t new_state0.installed in + let _ = get_depexts t ~new_packages:new_state0.installed ~all_packages:new_state0.installed in (* Prints the msg about additional depexts to install *) t, Aborted else if download_only || confirmation ?ask names solution then ( let t = - install_depexts t @@ OpamPackage.Set.inter - new_state0.installed (OpamSolver.all_packages solution0) + install_depexts t + ~new_packages:(OpamPackage.Set.inter new_state0.installed (OpamSolver.all_packages solution0)) + ~all_packages:new_state0.installed in let requested = OpamPackage.packages_of_names new_state.installed names diff --git a/src/client/opamSolution.mli b/src/client/opamSolution.mli index d1ac9d17764..933ce2dbedc 100644 --- a/src/client/opamSolution.mli +++ b/src/client/opamSolution.mli @@ -82,15 +82,15 @@ val print_depext_msg : OpamSysPkg.Set.t * OpamSysPkg.Set.t -> unit (** As {!install_depexts}, but supplied with a set of system packages to be installed. *) -val install_sys_packages: confirm:bool -> OpamStateTypes.gt_variables -> - OpamFile.Config.t -> OpamSysPkg.Set.t -> unit -> unit +val install_sys_packages: confirm:bool -> sys_packages:OpamSysPkg.Set.t -> required:OpamSysPkg.Set.t -> + OpamStateTypes.gt_variables -> OpamFile.Config.t -> rw switch_state option -> rw switch_state option (* Install external dependencies of the given package set, according the depext configuration. If [confirm] is false, install commands are directly launched, without asking user (used by the `--depext-only` option). If [force_depext] is true, it overrides [OpamFile.Config.depext] value. *) val install_depexts: ?force_depext:bool -> ?confirm:bool -> rw switch_state -> - package_set -> rw switch_state + new_packages:package_set -> all_packages:package_set -> rw switch_state (** {2 Atoms} *) diff --git a/src/format/opamSysPkg.ml b/src/format/opamSysPkg.ml index 0aec85940a0..25eba1e5fbc 100644 --- a/src/format/opamSysPkg.ml +++ b/src/format/opamSysPkg.ml @@ -49,6 +49,9 @@ type status = s_available : Set.t; (** Package available but not installed *) + s_required : Set.t; + (** Package installed but also needs to be passed to the installation *) + s_not_found : Set.t; (** Package unavailable on this system *) } @@ -57,10 +60,12 @@ type status = let status_empty = { s_available = Set.empty; + s_required = Set.empty; s_not_found = Set.empty; } let string_of_status sp = - Printf.sprintf "available: %s; not_found: %s" + Printf.sprintf "available: %s; required: %s, not_found: %s" (Set.to_string sp.s_available) + (Set.to_string sp.s_required) (Set.to_string sp.s_not_found) diff --git a/src/format/opamSysPkg.mli b/src/format/opamSysPkg.mli index d0239df4bfd..c76e635b852 100644 --- a/src/format/opamSysPkg.mli +++ b/src/format/opamSysPkg.mli @@ -20,6 +20,9 @@ type status = s_available : Set.t; (** Package available but not installed *) + s_required : Set.t; + (** Package installed but also needs to be passed to the installation *) + s_not_found : Set.t; (** Package unavailable on this system *) } diff --git a/src/state/opamEnv.ml b/src/state/opamEnv.ml index 9b6e0990387..a17a8ce7796 100644 --- a/src/state/opamEnv.ml +++ b/src/state/opamEnv.ml @@ -675,11 +675,20 @@ let updates_common ~set_opamroot ~set_opamswitch root switch = else [] in root @ switch +let updates_nix st = + match OpamSysPoll.os_family st.switch_global.global_variables with + | Some "nixos" -> + OpamFilename.create (OpamPath.Switch.meta OpamStateConfig.(!r.root_dir) st.switch) (OpamFilename.basename (OpamFilename.raw "nix.env")) + |> OpamFile.make + |> OpamFile.Environment.read_opt + |> Option.fold ~none:[] ~some:(List.map resolve_separator_and_format) + | _ -> [] + let updates ~set_opamroot ~set_opamswitch ?force_path st = let common = updates_common ~set_opamroot ~set_opamswitch st.switch_global.root st.switch in - common @ compute_updates ?force_path st + common @ compute_updates ?force_path st @ updates_nix st let get_pure ?(updates=[]) () = let env = List.map (fun (v,va) -> v,va,None) (OpamStd.Env.list ()) in diff --git a/src/state/opamSwitchState.ml b/src/state/opamSwitchState.ml index 6d4b3e33883..39285365565 100644 --- a/src/state/opamSwitchState.ml +++ b/src/state/opamSwitchState.ml @@ -179,7 +179,7 @@ module Installed_cache = OpamCached.Make(struct end) let depexts_status_of_packages_raw - ~depexts ?env global_config switch_config packages = + ~depexts ?env global_config switch_config packages ~old_packages = if OpamPackage.Set.is_empty packages then OpamPackage.Map.empty else let open OpamSysPkg.Set.Op in let syspkg_set, syspkg_map = @@ -190,6 +190,10 @@ let depexts_status_of_packages_raw else OpamPackage.Map.add nv s map) packages (OpamSysPkg.Set.empty, OpamPackage.Map.empty) in + let old_syspkg_set = + OpamPackage.Set.fold (fun nv set -> depexts nv ++ set) + old_packages OpamSysPkg.Set.empty + in let chronos = OpamConsole.timer () in let bypass = OpamFile.Config.depext_bypass global_config ++ @@ -197,8 +201,8 @@ let depexts_status_of_packages_raw in let syspkg_set = syspkg_set -- bypass in let ret = - match OpamSysInteract.packages_status ?env global_config syspkg_set with - | avail, not_found -> + match OpamSysInteract.packages_status ?env global_config syspkg_set ~old_packages:old_syspkg_set with + | avail, required, not_found -> let avail, not_found = if OpamStateConfig.(!r.no_depexts) then (* Mark all as available. This is necessary to store the exceptions @@ -211,6 +215,7 @@ let depexts_status_of_packages_raw in OpamPackage.Map.map (fun set -> { OpamSysPkg.s_available = set %% avail; + OpamSysPkg.s_required = set %% required; OpamSysPkg.s_not_found = set %% not_found} ) syspkg_map | exception (Failure msg) -> @@ -517,6 +522,7 @@ let load lock_kind gt rt switch = depexts_status_of_packages_raw gt.config switch_config ~env:gt.global_variables (Lazy.force available_packages) + ~old_packages:(Lazy.force available_packages) ~depexts:(fun package -> let env = OpamPackageVar.resolve_switch_raw ~package gt switch switch_config @@ -1259,7 +1265,7 @@ let update_pin nv opam st = let sys_packages = lazy ( OpamPackage.Map.union (fun _ n -> n) (Lazy.force st.sys_packages) - (depexts_status_of_packages st (OpamPackage.Set.singleton nv)) + (depexts_status_of_packages st (OpamPackage.Set.singleton nv) ~old_packages:(OpamPackage.Set.singleton nv)) ) in let available_packages = lazy ( OpamPackage.Set.filter (fun nv -> depexts_unavailable st nv = None) diff --git a/src/state/opamSwitchState.mli b/src/state/opamSwitchState.mli index 186cd45da10..f8381013942 100644 --- a/src/state/opamSwitchState.mli +++ b/src/state/opamSwitchState.mli @@ -185,7 +185,8 @@ val reverse_dependencies: (** Returns required system packages of each of the given packages (elements are not added to the map if they don't have system dependencies) *) val depexts_status_of_packages: - 'a switch_state -> package_set -> OpamSysPkg.status package_map + 'a switch_state -> package_set -> old_packages:package_set -> + OpamSysPkg.status package_map (** Returns not found depexts for the package *) val depexts_unavailable: 'a switch_state -> package -> OpamSysPkg.Set.t option diff --git a/src/state/opamSysInteract.ml b/src/state/opamSysInteract.ml index dfbcdb2cb79..0f3bcb75124 100644 --- a/src/state/opamSysInteract.ml +++ b/src/state/opamSysInteract.ml @@ -126,6 +126,7 @@ type families = | Macports | Msys2 | Netbsd + | Nix | Openbsd | Suse @@ -190,6 +191,7 @@ let family ~env () = | "gentoo" -> Gentoo | "homebrew" -> Homebrew | "macports" -> Macports + | "nixos" -> Nix | "macos" -> failwith "External dependency handling for macOS requires either \ @@ -469,24 +471,24 @@ let yum_cmd = lazy begin raise (OpamSystem.Command_not_found "yum or dnf") end -let packages_status ?(env=OpamVariable.Map.empty) config packages = +let packages_status ?(env=OpamVariable.Map.empty) config packages ~old_packages = let (+++) pkg set = OpamSysPkg.Set.add (OpamSysPkg.of_string pkg) set in (* Some package managers don't permit to request on available packages. In this case, we consider all non installed packages as [available]. *) let open OpamSysPkg.Set.Op in let compute_sets ?sys_available sys_installed = let installed = packages %% sys_installed in - let available, not_found = + let available, required, not_found = match sys_available with | Some sys_available -> let available = (packages -- installed) %% sys_available in let not_found = packages -- installed -- available in - available, not_found + available, OpamSysPkg.Set.empty, not_found | None -> let available = packages -- installed in - available, OpamSysPkg.Set.empty + available, OpamSysPkg.Set.empty, OpamSysPkg.Set.empty in - available, not_found + available, required, not_found in let to_string_list pkgs = OpamSysPkg.(Set.fold (fun p acc -> to_string p :: acc) pkgs []) @@ -970,6 +972,16 @@ let packages_status ?(env=OpamVariable.Map.empty) config packages = |> package_set_of_pkgpath in compute_sets sys_installed + | Nix -> + (* We say all requested packages are available but uninstalled. + We could check that these packages are available in Nixpkgs, + but that would involve an expensive Nixpkgs evaluation. + Saying no packages are installed results in a warning that + conf packages depend on a 'system package that can no longer + be found.' But omitting them will mean that they won't be + added to the Nix derivation. + *) + OpamSysPkg.Set.diff packages old_packages, old_packages, OpamSysPkg.Set.empty | Openbsd -> let sys_installed = run_query_command "pkg_info" ["-mqP"] @@ -1005,7 +1017,7 @@ let packages_status ?(env=OpamVariable.Map.empty) config packages = (* Install *) -let install_packages_commands_t ?(env=OpamVariable.Map.empty) config sys_packages = +let install_packages_commands_t ?(env=OpamVariable.Map.empty) switch config sys_packages ~required = let unsafe_yes = OpamCoreConfig.answer_is `unsafe_yes in let yes ?(no=[]) yes r = if unsafe_yes then @@ -1093,14 +1105,50 @@ let install_packages_commands_t ?(env=OpamVariable.Map.empty) config sys_package [`AsUser (Commands.msys2 config), "-Su"::"--noconfirm"::packages], None | Netbsd -> [`AsAdmin "pkgin", yes ["-y"] ("install" :: packages)], None + | Nix -> + (match switch with + | None -> + log "Nix depext must be passed switch"; + [], None + | Some switch -> + let dir = OpamPath.Switch.meta OpamStateConfig.(!r.root_dir) switch in + let drvFile = OpamFilename.create dir (OpamFilename.basename (OpamFilename.raw "env.nix")) in + let packages = String.concat " " + (OpamSysPkg.Set.fold (fun p l -> OpamSysPkg.to_string p :: l) + OpamSysPkg.Set.Op.(sys_packages ++ required) []) + in + let contents = +{|{ pkgs ? import {} }: +with pkgs; +stdenv.mkDerivation { + name = "opam-nix-env"; + nativeBuildInputs = with buildPackages; [ |} ^ packages ^ {| ]; + + phases = [ "buildPhase" ]; + + buildPhase = '' +vars=("NIX_CC" "NIX_CC_FLAGS" "NIX_CFLAGS_COMPILE" "NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu" "NIX_LDFLAGS" "PKG_CONFIG_PATH") +for var in "''${vars[@]}"; do + escaped="$(echo "''${!var}" | sed -e 's/^$/@/' -e 's/ /\\ /g')" + echo "$var = $escaped Nix" >> "$out" +done +echo "PATH += $PATH Nix" >> "$out" + ''; + + preferLocalBuild = true; +} +|} in + OpamFilename.write drvFile contents; + let envFile = OpamFilename.create dir (OpamFilename.basename (OpamFilename.raw "nix.env")) |> OpamFilename.to_string in + [`AsUser "nix-build", [ OpamFilename.to_string drvFile; "--out-link"; envFile ] ], None) | Openbsd -> [`AsAdmin "pkg_add", yes ~no:["-i"] ["-I"] packages], None | Suse -> [`AsAdmin "zypper", yes ["--non-interactive"] ("install"::packages)], None -let install_packages_commands ?env config sys_packages = - fst (install_packages_commands_t ?env config sys_packages) +let install_packages_commands ?env switch config sys_packages ~required = + fst (install_packages_commands_t ?env switch config sys_packages ~required) -let package_manager_name ?env config = - match install_packages_commands ?env config OpamSysPkg.Set.empty with +let package_manager_name ?env switch config = + match install_packages_commands ?env switch config OpamSysPkg.Set.empty ~required:OpamSysPkg.Set.empty with | ((`AsAdmin pkgman | `AsUser pkgman), _) :: _ -> pkgman | [] -> assert false @@ -1125,11 +1173,11 @@ let sudo_run_command ?(env=OpamVariable.Map.empty) ?vars cmd args = "failed with exit code %d at command:\n %s" code (String.concat " " (cmd::args)) -let install ?env config packages = - if OpamSysPkg.Set.is_empty packages then +let install ?env switch config packages ~required = + if OpamSysPkg.Set.is_empty packages && OpamSysPkg.Set.is_empty required then log "Nothing to install" else - let commands, vars = install_packages_commands_t ?env config packages in + let commands, vars = install_packages_commands_t ?env switch config packages ~required in let vars = OpamStd.Option.map (List.map (fun x -> `add, x)) vars in List.iter (fun (cmd, args) -> @@ -1154,6 +1202,7 @@ let update ?(env=OpamVariable.Map.empty) config = | Macports -> Some (`AsAdmin "port", ["sync"]) | Msys2 -> Some (`AsUser (Commands.msys2 config), ["-Sy"]) | Netbsd -> None + | Nix -> None | Openbsd -> None | Suse -> Some (`AsAdmin "zypper", ["--non-interactive"; "refresh"]) in @@ -1176,9 +1225,9 @@ let update ?(env=OpamVariable.Map.empty) config = let repo_enablers ?(env=OpamVariable.Map.empty) config = if family ~env () <> Centos then None else - let (needed, _) = + let (needed, _, _) = packages_status ~env config (OpamSysPkg.raw_set - (OpamStd.String.Set.singleton "epel-release")) + (OpamStd.String.Set.singleton "epel-release")) ~old_packages:OpamSysPkg.Set.empty in if OpamSysPkg.Set.is_empty needed then None else diff --git a/src/state/opamSysInteract.mli b/src/state/opamSysInteract.mli index 9254c9a00f6..ae412d7c4f2 100644 --- a/src/state/opamSysInteract.mli +++ b/src/state/opamSysInteract.mli @@ -14,25 +14,28 @@ open OpamStateTypes system and returns a pair of [sys_package] set: * first one is available set: package that exist on the default repositories, but not installed) - * second one, not found set: packages not found on the defined repositories + * second one, also required set: the list of packages which also need to be + passed to the installation + * third one, not found set: packages not found on the defined repositories [env] is used to determine host specification. *) val packages_status: ?env:gt_variables -> OpamFile.Config.t -> OpamSysPkg.Set.t -> - OpamSysPkg.Set.t * OpamSysPkg.Set.t + old_packages:OpamSysPkg.Set.t -> OpamSysPkg.Set.t * OpamSysPkg.Set.t * OpamSysPkg.Set.t (* Return the commands to run to install given system packages. [env] is used to determine host specification. *) val install_packages_commands: - ?env:gt_variables -> OpamFile.Config.t -> OpamSysPkg.Set.t -> - ([`AsAdmin of string | `AsUser of string] * string list) list + ?env:gt_variables -> OpamSwitch.t option -> OpamFile.Config.t -> OpamSysPkg.Set.t -> + required:OpamSysPkg.Set.t -> ([`AsAdmin of string | `AsUser of string] * string list) list (* Install given system packages, by calling local system package manager. [env] is used to determine host specification. *) -val install: ?env:gt_variables -> OpamFile.Config.t -> OpamSysPkg.Set.t -> unit +val install: ?env:gt_variables -> OpamSwitch.t option -> OpamFile.Config.t -> + OpamSysPkg.Set.t -> required:OpamSysPkg.Set.t -> unit val update: ?env:gt_variables -> OpamFile.Config.t -> unit -val package_manager_name: ?env:gt_variables -> OpamFile.Config.t -> string +val package_manager_name: ?env:gt_variables -> OpamSwitch.t option -> OpamFile.Config.t -> string (* Determine if special packages may need installing to enable other repositories.