├── docs
├── .nojekyll
└── index.html
├── dune
├── .gitignore
├── src
├── onix_core
│ ├── Lib.ml
│ ├── Lock_file.mli
│ ├── Pin_depends.mli
│ ├── dune
│ ├── Solver.mli
│ ├── Opam_actions.mli
│ ├── Resolutions.mli
│ ├── Lock_file.ml
│ ├── Solver_context.mli
│ ├── System.ml
│ ├── Repo.ml
│ ├── Lock_pkg.mli
│ ├── Nix_utils.mli
│ ├── Paths.ml
│ ├── Overrides.ml
│ ├── Scope.mli
│ ├── Resolutions.ml
│ ├── Pin_depends.ml
│ ├── Utils.ml
│ ├── Solver_context.ml
│ ├── Solver.ml
│ ├── Opam_actions.ml
│ ├── Filter.ml
│ ├── Opam_utils.ml
│ ├── Nix_utils.ml
│ └── Scope.ml
├── onix_lock_graphviz
│ ├── dune
│ └── Onix_lock_graphviz.ml
├── onix_lock_json
│ ├── dune
│ ├── Pp.mli
│ ├── Onix_lock_json.ml
│ └── Pp.ml
├── onix_lock_opam
│ ├── dune
│ ├── Onix_lock_opam.ml
│ └── Pp.ml
├── onix_lock_nix
│ ├── Prelude.ml
│ ├── dune
│ ├── Onix_lock_nix.ml
│ ├── Nix_filter.ml
│ ├── Subst_and_patch.ml
│ ├── Nix_pkg.ml
│ └── Pp.ml
└── onix
│ ├── dune
│ └── Main.ml
├── .history
├── dune-project
├── tests
├── opam-roots
│ ├── pkg_opam_dir
│ │ ├── Makefile
│ │ ├── opam
│ │ │ ├── aaa.opam
│ │ │ └── bbb.opam
│ │ ├── default.nix
│ │ └── onix-lock.json
│ └── pkg_opam_file
│ │ ├── Makefile
│ │ ├── opam
│ │ ├── default.nix
│ │ └── onix-lock.json
├── dune
├── multiple-opam-repos.nix
├── transitive-deps
│ └── default.nix
├── zarith.nix
├── Opam_to_nix.ml
├── zarith.opam
├── Test_lock.ml
└── Test_vars.ml
├── dune-workspace
├── vendor
└── opam-installer
│ └── dune
├── CHANGELOG.md
├── Makefile.template
├── shell.nix
├── Makefile
├── .ocamlformat
├── bootstrap.nix
├── nix
├── overlay
│ ├── ocb-stubblr
│ │ └── onix_disable_opam.patch
│ ├── ocamlfind
│ │ ├── onix_install_topfind_194.patch
│ │ ├── onix_install_topfind_193.patch
│ │ ├── onix_install_topfind_192.patch
│ │ ├── onix_install_topfind_198.patch
│ │ └── onix_install_topfind_195.patch
│ └── default.nix
├── onixPackages
│ ├── default.nix
│ ├── opam-0install.nix
│ └── cmdliner.nix
└── experiments
│ ├── paths.nix
│ ├── subst.nix
│ ├── test-vars.nix
│ ├── test-subst.nix
│ └── vars.nix
├── flake.nix
├── onix.opam
├── playground
├── cmdline.md
├── propagated.nix
└── nix-api.md
├── default.nix
├── flake.lock
├── NOTES.md
└── TODO.md
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dune:
--------------------------------------------------------------------------------
1 | (dirs src tests vendor)
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _build
2 | _tmp
3 | result*
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | Sat 3 Dec 2022 14:43:55 GMT
2 |
--------------------------------------------------------------------------------
/src/onix_core/Lib.ml:
--------------------------------------------------------------------------------
1 | let version = "0.0.5"
2 |
--------------------------------------------------------------------------------
/.history:
--------------------------------------------------------------------------------
1 | nix --offline -L build -f default.nix scope.hello-with-test-doc-tools
2 |
--------------------------------------------------------------------------------
/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
3 | (using menhir 2.0)
4 |
5 | (implicit_transitive_deps false)
6 |
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_dir/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: lock
2 | lock:
3 | nix develop -f default.nix lock
4 |
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_file/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: lock
2 | lock:
3 | nix develop -f default.nix lock
4 |
--------------------------------------------------------------------------------
/dune-workspace:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 |
3 | (env
4 | (dev
5 | (flags
6 | (:standard -warn-error -A))))
7 |
--------------------------------------------------------------------------------
/src/onix_lock_graphviz/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name onix_lock_graphviz)
3 | (libraries onix_core fmt logs opam-format))
4 |
--------------------------------------------------------------------------------
/src/onix_lock_json/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name onix_lock_json)
3 | (libraries onix_core fmt logs opam-core opam-format))
4 |
--------------------------------------------------------------------------------
/src/onix_lock_opam/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name onix_lock_opam)
3 | (libraries onix_core fmt logs opam-core opam-format))
4 |
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_file/opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 |
3 | depends: [
4 | "ocaml" {>= "4.08" & < "5.0"}
5 | "dune" {build}
6 | "fmt"
7 | ]
8 |
--------------------------------------------------------------------------------
/src/onix_lock_nix/Prelude.ml:
--------------------------------------------------------------------------------
1 | let ( > ) = OpamFilename.Op.( / )
2 | let ( /> ) = OpamFilename.Op.( // )
3 |
4 | include Onix_core
5 | include Onix_core.Utils
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_dir/opam/aaa.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 |
3 | depends: [
4 | "ocaml" {>= "4.08" & < "5.0"}
5 | "dune" {build}
6 | "fmt"
7 | ]
8 |
--------------------------------------------------------------------------------
/vendor/opam-installer/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name opam_installer)
3 | (public_name onix-opam-installer)
4 | (libraries opam-format opam-core cmdliner))
5 |
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_dir/opam/bbb.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 |
3 | depends: [
4 | "ocaml" {>= "4.08" & < "5.0"}
5 | "dune" {build}
6 | "yojson"
7 | ]
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | - Fix dep resolution issue in solver where `post` was not set to [true].
2 | - Fix `./opam` and `./opam/$pkg.opam` file lookup (#15). Thanks to @dbuenzli
3 | for suggestions.
4 |
--------------------------------------------------------------------------------
/Makefile.template:
--------------------------------------------------------------------------------
1 |
2 | .PHONY: shell
3 | shell:
4 | nix develop -f default.nix -j auto -i -k TERM -k PATH -k HOME -v shell
5 |
6 | .PHONY: lock
7 | lock:
8 | nix develop -f default.nix lock
9 |
--------------------------------------------------------------------------------
/src/onix_core/Lock_file.mli:
--------------------------------------------------------------------------------
1 | type t = {
2 | repository_urls : OpamUrl.t list;
3 | packages : Lock_pkg.t list;
4 | }
5 |
6 | val make : repository_urls:OpamUrl.t list -> Lock_pkg.t list -> t
7 |
--------------------------------------------------------------------------------
/src/onix_core/Pin_depends.mli:
--------------------------------------------------------------------------------
1 | val collect_from_opam_files :
2 | Opam_utils.opam_details OpamTypes.name_map ->
3 | Opam_utils.opam_details OpamTypes.name_map
4 | (** Given all root packages find their pins. *)
5 |
--------------------------------------------------------------------------------
/src/onix_core/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name onix_core)
3 | (libraries
4 | fmt
5 | bos
6 | logs
7 | yojson
8 | re
9 | unix
10 | opam-0install
11 | opam-format
12 | opam-file-format
13 | opam-state
14 | opam-core
15 | opam-client))
16 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import { }, ocamlPackages ? pkgs.ocaml-ng.ocamlPackages_5_3
2 | }:
3 | let onix = import ./. { inherit pkgs ocamlPackages; };
4 | in pkgs.mkShell {
5 | inputsFrom = [ onix ];
6 | buildInputs = [ pkgs.nixfmt ocamlPackages.ocaml-lsp pkgs.ocamlformat ];
7 | }
8 |
--------------------------------------------------------------------------------
/src/onix_lock_json/Pp.mli:
--------------------------------------------------------------------------------
1 | val pp_pkg : Format.formatter -> Onix_core.Lock_pkg.t -> unit
2 |
3 | val pp : Format.formatter -> Onix_core.Lock_file.t -> unit
4 | (** Pretty-printer for the nix lock file.
5 |
6 | [~ignore_file] is the optional file path to be used to filter root sources. *)
7 |
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_dir/default.nix:
--------------------------------------------------------------------------------
1 | let onix = import ../../../default.nix { verbosity = "info"; };
2 |
3 | in onix.env {
4 | path = ./.;
5 | repos = [{
6 | url = "https://github.com/ocaml/opam-repository.git";
7 | rev = "9e6ae0a9398cf087ec2b3fbcd62cb6072ccf95ce";
8 | }];
9 | }
10 |
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_file/default.nix:
--------------------------------------------------------------------------------
1 | let onix = import ../../../default.nix { verbosity = "info"; };
2 |
3 | in onix.env {
4 | path = ./.;
5 | repos = [{
6 | url = "https://github.com/ocaml/opam-repository.git";
7 | rev = "9e6ae0a9398cf087ec2b3fbcd62cb6072ccf95ce";
8 | }];
9 | }
10 |
--------------------------------------------------------------------------------
/src/onix_core/Solver.mli:
--------------------------------------------------------------------------------
1 | val solve :
2 | ?resolutions:OpamFormula.atom list ->
3 | repository_urls:OpamUrl.t list ->
4 | with_test:bool ->
5 | with_doc:bool ->
6 | with_dev_setup:bool ->
7 | OpamFilename.t list ->
8 | Lock_file.t
9 | (** Find a package solution for provided root opam files given repo URL. *)
10 |
--------------------------------------------------------------------------------
/src/onix_core/Opam_actions.mli:
--------------------------------------------------------------------------------
1 | val patch : Scope.t -> unit
2 |
3 | val build :
4 | with_test:bool ->
5 | with_doc:bool ->
6 | with_dev_setup:bool ->
7 | Scope.t ->
8 | string list list
9 |
10 | val install :
11 | with_test:bool ->
12 | with_doc:bool ->
13 | with_dev_setup:bool ->
14 | Scope.t ->
15 | string list list
16 |
--------------------------------------------------------------------------------
/src/onix_lock_opam/Onix_lock_opam.ml:
--------------------------------------------------------------------------------
1 | let gen ~opam_lock_file_path lock_file =
2 | Onix_core.Utils.Out_channel.with_open_text opam_lock_file_path (fun chan ->
3 | let out = Format.formatter_of_out_channel chan in
4 | Fmt.pf out "%a" Pp.pp lock_file);
5 | Logs.info (fun log ->
6 | log "Created an opam lock file at %S." opam_lock_file_path)
7 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | .PHONY: build
3 | build:
4 | dune build
5 |
6 | .PHONY: watch
7 | watch:
8 | dune build -w
9 |
10 | .PHONY: nix-build
11 | nix-build:
12 | nix-build
13 |
14 | .PHONY: nix-shell
15 | nix-shell:
16 | nix-shell
17 |
18 | .PHONY: nix-bootstrap
19 | nix-bootstrap:
20 | nix-build ./bootstrap.nix
21 |
22 | .PHONY: test
23 | test:
24 | dune runtest
25 |
--------------------------------------------------------------------------------
/src/onix_core/Resolutions.mli:
--------------------------------------------------------------------------------
1 | type t
2 |
3 | val make : OpamFormula.atom list -> t
4 | val constraints : t -> OpamFormula.version_constraint OpamTypes.name_map
5 | val all : t -> OpamTypes.name list
6 | val debug : t -> unit
7 | val parse_resolution : string -> [`Ok of OpamFormula.atom | `Error of string]
8 | val pp_resolution : Format.formatter -> OpamFormula.atom -> unit
9 |
--------------------------------------------------------------------------------
/src/onix_lock_json/Onix_lock_json.ml:
--------------------------------------------------------------------------------
1 | open Onix_core
2 |
3 | let gen ~lock_file_path (lock_file : Lock_file.t) =
4 | Onix_core.Utils.Out_channel.with_open_text lock_file_path (fun chan ->
5 | let out = Format.formatter_of_out_channel chan in
6 | Fmt.pf out "%a" Pp.pp lock_file);
7 |
8 | Logs.info (fun log -> log "Created a lock file at %S." lock_file_path)
9 |
10 | module Pp = Pp
--------------------------------------------------------------------------------
/src/onix/dune:
--------------------------------------------------------------------------------
1 | (dirs onix_core onix_lock_graphviz onix_lock_json onix_lock_opam)
2 |
3 | (executable
4 | (name Main)
5 | (public_name onix)
6 | (modules Main)
7 | (libraries
8 | fpath
9 | logs
10 | logs.fmt
11 | logs.cli
12 | fmt
13 | fmt.cli
14 | fmt.tty
15 | cmdliner
16 | onix_core
17 | onix_lock_json
18 | onix_lock_opam
19 | onix_lock_graphviz
20 | opam-core
21 | opam-format))
22 |
--------------------------------------------------------------------------------
/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = conventional
2 |
3 | leading-nested-match-parens = false
4 | space-around-variants = false
5 | space-around-arrays = false
6 | space-around-lists = false
7 | space-around-records = false
8 | break-infix = fit-or-vertical
9 | break-separators = after
10 | space-around-records = true
11 | break-cases = fit-or-vertical
12 | cases-exp-indent = 2
13 | exp-grouping = preserve
14 | let-and = sparse
15 | type-decl = sparse
16 |
--------------------------------------------------------------------------------
/src/onix_core/Lock_file.ml:
--------------------------------------------------------------------------------
1 | type t = {
2 | repository_urls : OpamUrl.t list;
3 | packages : Lock_pkg.t list;
4 | }
5 |
6 | let make ~repository_urls packages =
7 | (* TODO: Move validation up. *)
8 | List.iter
9 | (fun url ->
10 | if Option.is_none url.OpamUrl.hash then
11 | Fmt.failwith "Repo URI without rev when creating a lock file: %a"
12 | Opam_utils.pp_url url)
13 | repository_urls;
14 |
15 | { repository_urls; packages }
16 |
--------------------------------------------------------------------------------
/bootstrap.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import { } }:
2 |
3 | let
4 | onix = import ./default.nix {
5 | inherit pkgs;
6 | ocamlPackages = pkgs.ocaml-ng.ocamlPackages_4_14;
7 | verbosity = "debug";
8 | };
9 | in onix.env {
10 | repos = [{
11 | url = "https://github.com/ocaml/opam-repository.git";
12 | rev = "f3dcd527e82e83facb92cd2727651938cb9fecf9";
13 | }];
14 | path = ./.;
15 | deps = { "ocaml-system" = "*"; };
16 | vars = { "with-dev-setup" = true; };
17 | }
18 |
--------------------------------------------------------------------------------
/tests/dune:
--------------------------------------------------------------------------------
1 | (test
2 | (name Test_vars)
3 | (modules Test_vars)
4 | (libraries yojson onix_core opam-format fmt))
5 |
6 | (test
7 | (name Test_lock)
8 | (modules Test_lock)
9 | (libraries onix_core onix_lock_json opam-format opam-core fmt))
10 |
11 | ; (test
12 | ; (name Opam_to_nix)
13 | ; (modules Opam_to_nix)
14 | ; (deps zarith.opam)
15 | ; (libraries
16 | ; onix_core
17 | ; onix_lock_nix
18 | ; opam-core
19 | ; opam-file-format
20 | ; opam-format
21 | ; fmt))
22 |
--------------------------------------------------------------------------------
/src/onix_core/Solver_context.mli:
--------------------------------------------------------------------------------
1 | type t
2 |
3 | val make :
4 | ?prefer_oldest:bool ->
5 | ?fixed_opam_details:Opam_utils.opam_details OpamTypes.name_map ->
6 | constraints:OpamFormula.version_constraint OpamTypes.name_map ->
7 | package_dep_vars:Opam_utils.package_dep_vars ->
8 | repo:Repo.t ->
9 | unit ->
10 | t
11 |
12 | (* Input solver context for Opam_0install.Solver.Make. *)
13 | include Opam_0install.S.CONTEXT with type t := t
14 |
15 | (* val get_opam_file : t -> OpamPackage.t -> OpamFile.OPAM.t *)
16 |
--------------------------------------------------------------------------------
/src/onix_lock_nix/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name onix_lock_nix)
3 | (libraries onix_core fmt logs opam-format opam-file-format opam-core))
4 |
5 | ; Static files
6 |
7 | (rule
8 | (alias static)
9 | (deps
10 | (:output Overlay_files.ml)
11 | (source_tree ../nix/overlay))
12 | (action
13 | (setenv
14 | SOURCE_DATE_EPOCH
15 | 0
16 | (progn
17 | (run
18 | %{bin:ocaml-crunch}
19 | --mode=plain
20 | -o
21 | %{output}.corrected
22 | ../../../../nix/overlay)
23 | (diff? %{output} %{output}.corrected)))))
24 |
--------------------------------------------------------------------------------
/tests/multiple-opam-repos.nix:
--------------------------------------------------------------------------------
1 | let
2 | pkgs = import { };
3 | repos = [
4 | { url = "https://github.com/kit-ty-kate/opam-alpha-repository.git"; }
5 | {
6 | url = "https://github.com/ocaml/opam-repository.git";
7 | rev = "fe53d261c062c23d8271f6887702b9bc7459ad2e";
8 | }
9 | ];
10 | paths = map (repo: (builtins.fetchGit repo) // { inherit (repo) url; }) repos;
11 | path = pkgs.symlinkJoin {
12 | name = "onix-opam-repos";
13 | inherit paths;
14 | };
15 | json = builtins.toJSON {
16 | repos = map (x: "${x.url}#${x.rev}") paths;
17 | path = path;
18 | };
19 | in path
20 |
--------------------------------------------------------------------------------
/nix/overlay/ocb-stubblr/onix_disable_opam.patch:
--------------------------------------------------------------------------------
1 | diff --git a/src/ocb_stubblr.ml b/src/ocb_stubblr.ml
2 | index b68c37a..ba716fe 100644
3 | --- a/src/ocb_stubblr.ml
4 | +++ b/src/ocb_stubblr.ml
5 | @@ -39,9 +39,8 @@ module Pkg_config = struct
6 | let var = "PKG_CONFIG_PATH"
7 |
8 | let path () =
9 | - let opam = Lazy.force opam_prefix
10 | - and rest = try [Sys.getenv var] with Not_found -> [] in
11 | - opam/"lib"/"pkgconfig" :: opam/"share"/"pkgconfig" :: rest
12 | + let rest = try [Sys.getenv var] with Not_found -> [] in
13 | + rest
14 | |> String.concat ~sep:":"
15 |
16 | let run ~flags package =
17 |
--------------------------------------------------------------------------------
/nix/onixPackages/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import { }, ocamlPackages ? pkgs.ocamlPackages }:
2 | ocamlPackages.overrideScope (self: super: {
3 | cmdliner = self.callPackage ./cmdliner.nix { };
4 | opam-0install = self.callPackage ./opam-0install.nix { };
5 | alcotest = super.alcotest.overrideAttrs (_: { doCheck = false; });
6 | yojson = super.yojson.overrideAttrs (_: { doCheck = false; });
7 | uri = super.uri.overrideAttrs (_: { doCheck = false; });
8 | angstrom = super.angstrom.overrideAttrs (_: { doCheck = false; });
9 | opam-repository = super.opam-repository.overrideAttrs (_: {
10 | configureFlags = [
11 | "--disable-checks"
12 | ];
13 | doCheck = false;
14 | });
15 | logs = super.logs.override { jsooSupport = false; };
16 | })
17 |
--------------------------------------------------------------------------------
/nix/onixPackages/opam-0install.nix:
--------------------------------------------------------------------------------
1 | { lib, fetchurl, buildDunePackage, fmt, cmdliner, opam-state, opam-file-format
2 | , _0install-solver }:
3 |
4 | buildDunePackage rec {
5 | pname = "opam-0install";
6 | version = "0.4.3";
7 | useDune2 = true;
8 |
9 | src = fetchurl {
10 | url =
11 | "https://github.com/ocaml-opam/opam-0install-solver/releases/download/v0.4.3/opam-0install-cudf-0.4.3.tbz";
12 | sha256 = "d59e0ebddda58f798ff50ebe213c83893b5a7c340c38c20950574d67e6145b8a";
13 | };
14 |
15 | propagatedBuildInputs =
16 | [ fmt cmdliner opam-state opam-file-format _0install-solver ];
17 |
18 | meta = with lib; {
19 | description = "Opam solver using 0install backend";
20 | homepage = "https://github.com/ocaml-opam/opam-0install-solver";
21 | license = licenses.isc;
22 | maintainers = [ ];
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "Build OCaml projects with Nix";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:nixos/nixpkgs";
6 | flake-utils.url = "github:numtide/flake-utils";
7 | };
8 |
9 | outputs = { self, nixpkgs, flake-utils }:
10 | flake-utils.lib.eachDefaultSystem (system:
11 |
12 | let
13 | pkgs = nixpkgs.legacyPackages.${system};
14 | mkOnix = ocamlPackages:
15 | import ./default.nix { inherit pkgs ocamlPackages; };
16 |
17 | in rec {
18 | packages = {
19 | "4_12" = mkOnix pkgs.ocaml-ng.ocamlPackages_4_12;
20 | "4_13" = mkOnix pkgs.ocaml-ng.ocamlPackages_4_13;
21 | "4_14" = mkOnix pkgs.ocaml-ng.ocamlPackages_4_14;
22 | latest = mkOnix pkgs.ocaml-ng.ocamlPackages_latest;
23 | };
24 |
25 | defaultPackage = packages.latest;
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/onix.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 | synopsis: "OCaml project manager based on Nix"
3 | maintainer: ["Rizo I. "]
4 | license: "ISC"
5 | homepage: "https://github.com/odis-labs/onix"
6 | bug-reports: "https://github.com/odis-labs/onix/issues"
7 | dev-repo: "git+https://github.com/odis-labs/onix.git"
8 | build: [
9 | ["dune" "subst"] {pinned}
10 | [
11 | "dune"
12 | "build"
13 | "-p"
14 | name
15 | "-j"
16 | jobs
17 | "@install"
18 | "@runtest" {with-test}
19 | "@doc" {with-doc}
20 | ]
21 | ]
22 | depends: [
23 | "ocaml" {>= "4.08" & < "5.0.0"}
24 | "dune" {>= "2.0" & build}
25 | "odoc" {with-doc}
26 | "bos"
27 | "cmdliner"
28 | "logs"
29 | "fmt"
30 | "fpath"
31 | "opam-0install"
32 | "yojson"
33 | "easy-format" {="1.3.2"}
34 | "ocaml-lsp-server" {with-dev-setup}
35 | "ocamlformat" {with-dev-setup}
36 | ]
37 |
--------------------------------------------------------------------------------
/playground/cmdline.md:
--------------------------------------------------------------------------------
1 | # Commandline Interface
2 |
3 |
4 | Build a single package:
5 | ```
6 | $ onix build utop
7 | $ ls ./result/bin/utop
8 | ```
9 |
10 | Build a single package, provide a repo URL:
11 | ```
12 | $ onix build --repo=https://github.com/ocaml/opam-repository.git#52c72e08d7782967837955f1c50c330a6131721f utop
13 | $ ls ./result/bin/utop
14 | ```
15 |
16 | Build multiple packages with specific version constraints:
17 | ```
18 | $ onix build bos.0.2.1 utop
19 | $ ls ./result/bin/utop
20 | ```
21 |
22 | Build a local package from opam file:
23 | ```
24 | $ onix build ./pkg.opam
25 | $ ls ./result/bin/utop
26 | ```
27 |
28 | Build a remtoe package:
29 | ```
30 | $ onix build git+https://github.com/odis-labs/streaming.git
31 | $ ls ./result/bin/utop
32 | ```
33 |
34 | Run a package:
35 | ```
36 | $ onix run utop
37 | # Runs ./bin/utop
38 | ```
39 |
40 | Start a shell for a package:
41 | ```
42 | $ onix shell utop
43 | ```
--------------------------------------------------------------------------------
/nix/overlay/ocamlfind/onix_install_topfind_194.patch:
--------------------------------------------------------------------------------
1 | Install topfind into OCAML_SITELIB instead of OCAML_CORE_STDLIB.
2 | diff --git a/src/findlib/Makefile b/src/findlib/Makefile
3 | index 84514b6..12e4ef6 100644
4 | --- a/src/findlib/Makefile
5 | +++ b/src/findlib/Makefile
6 | @@ -123,8 +123,7 @@ clean:
7 | install: all
8 | $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAML_SITELIB)/$(NAME)"
9 | $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAMLFIND_BIN)"
10 | - $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAML_CORE_STDLIB)"
11 | - test $(INSTALL_TOPFIND) -eq 0 || $(INSTALLFILE) topfind "$(DESTDIR)$(prefix)$(OCAML_CORE_STDLIB)/"
12 | + test $(INSTALL_TOPFIND) -eq 0 || $(INSTALLFILE) topfind "$(DESTDIR)$(prefix)$(OCAML_SITELIB)/"
13 | files=`$(SH) $(TOP)/tools/collect_files $(TOP)/Makefile.config \
14 | findlib.cmi findlib.mli findlib.cma findlib.cmxa findlib$(LIB_SUFFIX) findlib.cmxs \
15 | findlib_config.cmi findlib_config.ml topfind.cmi topfind.mli \
16 |
--------------------------------------------------------------------------------
/src/onix_core/System.ml:
--------------------------------------------------------------------------------
1 | type t = {
2 | arch : string;
3 | os : string;
4 | }
5 |
6 | (* opam uses macos and arm64, while nix used darwin and aarch64 *)
7 | let aarch64_linux = { arch = "arm64"; os = "linux" }
8 | let aarch64_darwin = { arch = "arm64"; os = "macos" }
9 | let x86_64_linux = { arch = "x86_64"; os = "linux" }
10 | let x86_64_darwin = { arch = "x86_64"; os = "macos" }
11 | let all = [aarch64_linux; aarch64_darwin; x86_64_darwin; x86_64_linux]
12 | let os_list = ["linux"; "macos"]
13 | let arch_list = ["x86_64"; "arm64"]
14 |
15 | let host =
16 | let arch = OpamSysPoll.arch OpamVariable.Map.empty in
17 | let os = OpamSysPoll.os OpamVariable.Map.empty in
18 | match (arch, os) with
19 | | Some arch, Some os -> { arch; os }
20 | | Some _, None -> failwith "could not get host's 'os'"
21 | | None, Some _ -> failwith "could not get host's 'arch'"
22 | | None, None -> failwith "could not get host's 'arch' and 'os'"
23 |
24 | let to_string t = String.concat "-" [t.arch; t.os]
25 | let pp f x = Fmt.string f (to_string x)
26 |
--------------------------------------------------------------------------------
/nix/overlay/ocamlfind/onix_install_topfind_193.patch:
--------------------------------------------------------------------------------
1 | Install topfind into OCAML_SITELIB instead of OCAML_CORE_STDLIB.
2 | diff --git a/findlib.conf.in b/findlib.conf.in
3 | index 261d2c8..461bafc 100644
4 | --- a/findlib.conf.in
5 | +++ b/findlib.conf.in
6 | @@ -1,2 +1,3 @@
7 | destdir="@SITELIB@"
8 | path="@SITELIB@"
9 | +ldconf="ignore"
10 | \ No newline at end of file
11 | diff --git a/src/findlib/Makefile b/src/findlib/Makefile
12 | index 4fd3f81..5b9a81e 100644
13 | --- a/src/findlib/Makefile
14 | +++ b/src/findlib/Makefile
15 | @@ -123,7 +123,7 @@ clean:
16 | install: all
17 | mkdir -p "$(prefix)$(OCAML_SITELIB)/$(NAME)"
18 | mkdir -p "$(prefix)$(OCAMLFIND_BIN)"
19 | - test $(INSTALL_TOPFIND) -eq 0 || cp topfind "$(prefix)$(OCAML_CORE_STDLIB)"
20 | + test $(INSTALL_TOPFIND) -eq 0 || cp topfind "$(prefix)$(OCAML_SITELIB)"
21 | files=`$(SH) $(TOP)/tools/collect_files $(TOP)/Makefile.config \
22 | findlib.cmi findlib.mli findlib.cma findlib.cmxa findlib$(LIB_SUFFIX) findlib.cmxs \
23 | findlib_config.cmi findlib_config.ml topfind.cmi topfind.mli \
24 |
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import { }, ocamlPackages ? pkgs.ocaml-ng.ocamlPackages_5_3
2 | , verbosity ? "warning" }:
3 |
4 | let
5 | onixPackages = import ./nix/onixPackages { inherit pkgs ocamlPackages; };
6 | api = import ./nix/api.nix { inherit pkgs onix verbosity; };
7 | onix = ocamlPackages.buildDunePackage {
8 | pname = "onix";
9 | version = "0.0.6";
10 | duneVersion = "3";
11 |
12 | passthru = { inherit (api) env; };
13 |
14 | src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
15 |
16 | nativeBuildInputs = [ pkgs.git onixPackages.crunch ];
17 | propagatedBuildInputs = with onixPackages; [
18 | bos
19 | cmdliner
20 | fpath
21 | yojson
22 | _0install-solver
23 | opam-0install
24 | opam-core
25 | opam-client
26 | opam-state
27 | ];
28 |
29 | meta = {
30 | description = "Build OCaml projects with Nix.";
31 | homepage = "https://github.com/odis-labs/onix";
32 | license = pkgs.lib.licenses.isc;
33 | maintainers = [ ];
34 | };
35 | };
36 | in onix
37 |
--------------------------------------------------------------------------------
/nix/overlay/ocamlfind/onix_install_topfind_192.patch:
--------------------------------------------------------------------------------
1 | Install topfind into OCAML_SITELIB instead of OCAML_CORE_STDLIB.
2 | --- a/src/findlib/Makefile
3 | +++ b/src/findlib/Makefile
4 | @@ -123,7 +123,7 @@ clean:
5 | install: all
6 | mkdir -p "$(prefix)$(OCAML_SITELIB)/$(NAME)"
7 | mkdir -p "$(prefix)$(OCAMLFIND_BIN)"
8 | - test $(INSTALL_TOPFIND) -eq 0 || cp topfind "$(prefix)$(OCAML_CORE_STDLIB)"
9 | + test $(INSTALL_TOPFIND) -eq 0 || cp topfind "$(prefix)$(OCAML_SITELIB)"
10 | files=`$(SH) $(TOP)/tools/collect_files $(TOP)/Makefile.config findlib.cmi findlib.mli findlib.cma findlib.cmxa findlib$(LIB_SUFFIX) findlib.cmxs topfind.cmi topfind.mli fl_package_base.mli fl_package_base.cmi fl_metascanner.mli fl_metascanner.cmi fl_metatoken.cmi findlib_top.cma findlib_top.cmxa findlib_top$(LIB_SUFFIX) findlib_top.cmxs findlib_dynload.cma findlib_dynload.cmxa findlib_dynload$(LIB_SUFFIX) findlib_dynload.cmxs fl_dynload.mli fl_dynload.cmi META` && \
11 | cp $$files "$(prefix)$(OCAML_SITELIB)/$(NAME)"
12 | f="ocamlfind$(EXEC_SUFFIX)"; { test -f ocamlfind_opt$(EXEC_SUFFIX) && f="ocamlfind_opt$(EXEC_SUFFIX)"; }; \
--------------------------------------------------------------------------------
/playground/propagated.nix:
--------------------------------------------------------------------------------
1 | let pkgs = import { };
2 | in rec {
3 | conf-pkg-config = pkgs.stdenv.mkDerivation {
4 | pname = "my-conf-pkg-config";
5 | version = "0.0.1";
6 | dontUnpack = true;
7 | installPhase = ''
8 | mkdir -p $out/bin
9 | echo "echo conf-pkg-config" > $out/bin/exe
10 | chmod +x $out/bin/exe
11 | '';
12 | propagatedNativeBuildInputs = [ pkgs.pkg-config ];
13 | };
14 |
15 | ctypes-foreign = pkgs.stdenv.mkDerivation {
16 | pname = "my-ctypes-foreign";
17 | version = "0.0.1";
18 | dontUnpack = true;
19 | installPhase = ''
20 | mkdir -p $out/bin
21 | echo "echo ctypes-foreign" > $out/bin/exe
22 | chmod +x $out/bin/exe
23 | '';
24 | buildInputs = [ conf-pkg-config ];
25 | };
26 |
27 | ctypes = pkgs.stdenv.mkDerivation {
28 | pname = "my-ctypes";
29 | version = "0.0.1";
30 | dontUnpack = true;
31 | installPhase = ''
32 | mkdir -p $out/bin
33 | echo "echo ctypes" > $out/bin/exe
34 | chmod +x $out/bin/exe
35 | '';
36 | buildInputs = [ ctypes-foreign ];
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/onix_core/Repo.ml:
--------------------------------------------------------------------------------
1 | module Paths = struct
2 | open OpamFilename.Op
3 |
4 | let packages ~root = root / "packages"
5 | let package_versions ~root n = packages ~root / OpamPackage.Name.to_string n
6 |
7 | let package ~root nv =
8 | packages ~root / OpamPackage.name_to_string nv / OpamPackage.to_string nv
9 |
10 | let opam ~root nv = package ~root nv // "opam" |> OpamFile.make
11 | let files ~root nv = package ~root nv / "files"
12 | end
13 |
14 | type t = {
15 | root : OpamTypes.dirname;
16 | urls : OpamUrl.t list;
17 | }
18 |
19 | let make ~root ~urls = { root; urls }
20 |
21 | let read_opam t nv =
22 | let path = Paths.opam ~root:t.root nv in
23 | OpamFile.OPAM.read path
24 |
25 | let get_opam_filename t nv = Paths.opam ~root:t.root nv |> OpamFile.filename
26 |
27 | let read_package_versions t name =
28 | let path = Paths.package_versions ~root:t.root name in
29 | Utils.Filesystem.fold_dir
30 | (fun acc subdir ->
31 | let nv = OpamPackage.of_string subdir in
32 | let v = OpamPackage.version nv in
33 | v :: acc)
34 | []
35 | (OpamFilename.Dir.to_string path)
36 |
--------------------------------------------------------------------------------
/nix/experiments/paths.nix:
--------------------------------------------------------------------------------
1 | { ocaml-version }:
2 | let
3 | lib = { pkg-name ? null, subdir ? null, prefix }:
4 | let
5 | segments =
6 | [ prefix "lib/ocaml" ocaml-version "site-lib" subdir pkg-name ];
7 | segments' = builtins.filter (seg: !(isNull seg)) segments;
8 | in builtins.concatStringsSep "/" segments';
9 |
10 | out = { pkg-name ? null, subdir ? null, prefix }:
11 | let
12 | segments = [ prefix subdir pkg-name ];
13 | segments' = builtins.filter (seg: !(isNull seg)) segments;
14 | in builtins.concatStringsSep "/" segments';
15 |
16 | in {
17 | inherit lib;
18 |
19 | stublibs = args: lib (args // { subdir = "stublibs"; });
20 | toplevel = args: lib (args // { subdir = "toplevel"; });
21 |
22 | bin = args: out (args // { subdir = "bin"; });
23 | sbin = args: out (args // { subdir = "sbin"; });
24 | share = args: out (args // { subdir = "share"; });
25 | etc = args: out (args // { subdir = "etc"; });
26 | doc = args: out (args // { subdir = "doc"; });
27 |
28 | man = args:
29 | out (args // {
30 | pkg-name = null;
31 | subdir = "man";
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/src/onix_core/Lock_pkg.mli:
--------------------------------------------------------------------------------
1 | open Utils
2 |
3 | type http_src = {
4 | url : OpamUrl.t;
5 | hash : OpamHash.kind * string;
6 | }
7 |
8 | type src =
9 | | Git of {
10 | url : string;
11 | rev : string;
12 | }
13 | | Http of http_src
14 |
15 | type t = {
16 | src : src option;
17 | opam_details : Opam_utils.opam_details;
18 | depends : Name_set.t;
19 | depends_build : Name_set.t;
20 | depends_test : Name_set.t;
21 | depends_doc : Name_set.t;
22 | depends_dev_setup : Name_set.t;
23 | depexts_nix : String_set.t;
24 | depexts_unknown : String_set.t;
25 | vars : Opam_utils.dep_vars;
26 | flags : string list;
27 | extra_src : (string * http_src) list;
28 | }
29 |
30 | val src_is_git : src -> bool
31 | val src_is_http : src -> bool
32 | val name : t -> OpamTypes.name
33 |
34 | val of_opam :
35 | installed:(OpamPackage.Name.t -> bool) ->
36 | with_test:bool ->
37 | with_doc:bool ->
38 | with_dev_setup:bool ->
39 | Opam_utils.opam_details ->
40 | t option
41 | (** Create a lock package from an opam representation.
42 |
43 | [installed] is used to filter out optional dependencies not installed in the scope. *)
44 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "locked": {
5 | "lastModified": 1652776076,
6 | "narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=",
7 | "owner": "numtide",
8 | "repo": "flake-utils",
9 | "rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "numtide",
14 | "repo": "flake-utils",
15 | "type": "github"
16 | }
17 | },
18 | "nixpkgs": {
19 | "locked": {
20 | "lastModified": 1673383595,
21 | "narHash": "sha256-+2IyRZpvtUlp3aBmGVWxfGK09iXnhZF+dLVx3TlVU1c=",
22 | "owner": "nixos",
23 | "repo": "nixpkgs",
24 | "rev": "23776053143c7cf4ac6f5c68c1779d278748cbd9",
25 | "type": "github"
26 | },
27 | "original": {
28 | "owner": "nixos",
29 | "repo": "nixpkgs",
30 | "type": "github"
31 | }
32 | },
33 | "root": {
34 | "inputs": {
35 | "flake-utils": "flake-utils",
36 | "nixpkgs": "nixpkgs"
37 | }
38 | }
39 | },
40 | "root": "root",
41 | "version": 7
42 | }
43 |
--------------------------------------------------------------------------------
/nix/onixPackages/cmdliner.nix:
--------------------------------------------------------------------------------
1 | { lib, stdenv, fetchurl, ocaml, findlib, ocamlbuild, topkg, result }:
2 |
3 | assert (lib.versionAtLeast ocaml.version "4.03");
4 |
5 | stdenv.mkDerivation rec {
6 | pname = "cmdliner";
7 | version = "1.1.1";
8 |
9 | src = fetchurl {
10 | url =
11 | "https://erratique.ch/software/${pname}/releases/${pname}-${version}.tbz";
12 | sha256 = "sha256-oa6Hw6eZQO+NHdWfdED3dtHckm4BmEbdMiAuRkYntfs=";
13 | };
14 |
15 | nativeBuildInputs = [ ocaml ];
16 |
17 | makeFlags = [ "PREFIX=$(out)" ];
18 | installTargets = "install install-doc";
19 | installFlags = [
20 | "LIBDIR=$(out)/lib/ocaml/${ocaml.version}/site-lib/${pname}"
21 | "DOCDIR=$(out)/share/doc/${pname}"
22 | ];
23 | postInstall = ''
24 | mv $out/lib/ocaml/${ocaml.version}/site-lib/${pname}/{opam,${pname}.opam}
25 | '';
26 |
27 | meta = with lib; {
28 | homepage = "https://erratique.ch/software/cmdliner";
29 | description =
30 | "An OCaml module for the declarative definition of command line interfaces";
31 | license = licenses.bsd3;
32 | inherit (ocaml.meta) platforms;
33 | maintainers = [ maintainers.vbgl ];
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/src/onix_core/Nix_utils.mli:
--------------------------------------------------------------------------------
1 | val get_nix_build_jobs : unit -> string
2 | val nix_build_jobs_var : string
3 | val fetch_git : OpamUrl.t -> OpamFilename.Dir.t
4 | val fetch_git_resolve : OpamUrl.t -> string * OpamFilename.Dir.t
5 | val fetch_resolve_many : OpamUrl.t list -> (OpamUrl.t * OpamFilename.Dir.t) list
6 | val symlink_join : name:string -> OpamFilename.Dir.t list -> OpamFilename.Dir.t
7 |
8 | val prefetch_url_with_path :
9 | ?hash_type:[< `sha256 | `sha512 > `sha256] ->
10 | ?hash:string ->
11 | string ->
12 | string * OpamFilename.Dir.t
13 |
14 | val prefetch_url :
15 | ?hash_type:[< `sha256 | `sha512 > `sha256] -> ?hash:string -> string -> string
16 |
17 | val prefetch_git_with_path : OpamUrl.t -> string * OpamFilename.Dir.t
18 | val resolve_repos : OpamUrl.t list -> OpamFilename.Dir.t * OpamUrl.t list
19 |
20 | type store_path = {
21 | hash : string;
22 | pkg_name : string;
23 | pkg_version : string;
24 | prefix : string;
25 | suffix : string;
26 | }
27 |
28 | val pp_store_path : Format.formatter -> store_path -> unit
29 | val parse_store_path : string -> store_path
30 | val make_ocaml_packages_path : OpamPackage.Version.t -> string
31 | val check_ocaml_packages_version : OpamPackage.Version.t -> bool
32 |
--------------------------------------------------------------------------------
/playground/nix-api.md:
--------------------------------------------------------------------------------
1 | # Nix API
2 |
3 | ```
4 | let env = onix.env : {
5 | # The repo to use for resolution.
6 | repo : { url : String; },
7 |
8 | # List of additional or alternative repos.
9 | repos : List { url : String },
10 |
11 | # The root of the project where opam files are looked up.
12 | root : Path?,
13 |
14 | # List of additional or alternative deps.
15 | # A deps value can be:
16 | # - a version constraint string;
17 | # - a local opam file path;
18 | # - a git source (an attrset with "url" name).
19 | deps : { String : String | Path | { url : String } },
20 |
21 | # The path of the onix lock file.
22 | lock : Path ? ./onix-lock.json,
23 |
24 | # The path of the opam "locked" file to be generated.
25 | opam-lock : Path?,
26 |
27 | # A nix overlay to be applied to the built scope.
28 | overlay : Scope -> Scope -> Scope
29 | } -> {
30 | lock : Derivation,
31 | pkgs : Scope,
32 | shell : Derivation
33 | }
34 | ```
35 |
36 |
37 | ```
38 | onix.env
39 | onix.project
40 | onix.workspace
41 | onix.scope
42 |
43 | onix.build
44 | onix.init
45 | onix.mk
46 |
47 | onix.env.init
48 | onix.env.mk
49 | onix.mkEnv
50 |
51 | {
52 | deps,
53 | lock,
54 | opam-lock,
55 | shell
56 | }
57 | ```
58 |
--------------------------------------------------------------------------------
/src/onix_core/Paths.ml:
--------------------------------------------------------------------------------
1 | open struct
2 | let join = String.concat "/"
3 | end
4 |
5 | let lib ?pkg_name ?subdir ~ocaml_version prefix =
6 | let pkg_name = Option.map OpamPackage.Name.to_string pkg_name in
7 | let ocaml_version = OpamPackage.Version.to_string ocaml_version in
8 | match (pkg_name, subdir) with
9 | | None, None -> join [prefix; "lib/ocaml"; ocaml_version; "site-lib"]
10 | | None, Some subdir ->
11 | join [prefix; "lib/ocaml"; ocaml_version; "site-lib"; subdir]
12 | | Some name, None ->
13 | join [prefix; "lib/ocaml"; ocaml_version; "site-lib"; name]
14 | | Some name, Some subdir ->
15 | join [prefix; "lib/ocaml"; ocaml_version; "site-lib"; subdir; name]
16 |
17 | let stublibs ?pkg_name = lib ?pkg_name ~subdir:"stublibs"
18 | let toplevel ?pkg_name = lib ?pkg_name ~subdir:"toplevel"
19 |
20 | let out ?pkg_name ~subdir prefix =
21 | let pkg_name = Option.map OpamPackage.Name.to_string pkg_name in
22 | match pkg_name with
23 | | None -> join [prefix; subdir]
24 | | Some name -> join [prefix; subdir; name]
25 |
26 | let bin = out ~subdir:"bin"
27 | let sbin = out ~subdir:"sbin"
28 | let share = out ~subdir:"share"
29 | let etc = out ~subdir:"etc"
30 | let doc = out ~subdir:"doc"
31 | let man = out ?pkg_name:None ~subdir:"man"
32 |
--------------------------------------------------------------------------------
/nix/overlay/ocamlfind/onix_install_topfind_198.patch:
--------------------------------------------------------------------------------
1 | Install topfind into OCAML_SITELIB instead of OCAML_CORE_STDLIB.
2 | See also: https://github.com/ocaml/opam-repository/blob/master/packages/ocamlfind/ocamlfind.1.9.5/files/0001-Fix-bug-when-installing-with-a-system-compiler.patch
3 | diff --git a/findlib.conf.in b/findlib.conf.in
4 | index 261d2c8..461bafc 100644
5 | --- a/findlib.conf.in
6 | +++ b/findlib.conf.in
7 | @@ -1,2 +1,3 @@
8 | destdir="@SITELIB@"
9 | path="@FINDLIB_PATH@"
10 | +ldconf="ignore"
11 | \ No newline at end of file
12 | diff --git a/src/findlib/Makefile b/src/findlib/Makefile
13 | index 84514b6..12e4ef6 100644
14 | --- a/src/findlib/Makefile
15 | +++ b/src/findlib/Makefile
16 | @@ -134,8 +134,7 @@ clean:
17 | install: all
18 | $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAML_SITELIB)/$(NAME)"
19 | $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAMLFIND_BIN)"
20 | - test $(INSTALL_TOPFIND) -eq 0 || $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAML_CORE_STDLIB)"
21 | - test $(INSTALL_TOPFIND) -eq 0 || $(CP) topfind "$(DESTDIR)$(prefix)$(OCAML_CORE_STDLIB)/"
22 | + test $(INSTALL_TOPFIND) -eq 0 || $(CP) topfind "$(DESTDIR)$(prefix)$(OCAML_SITELIB)/"
23 | files=`$(SH) $(TOP)/tools/collect_files $(TOP)/Makefile.config \
24 | findlib.cmi findlib.mli findlib.cma findlib.cmxa findlib$(LIB_SUFFIX) findlib.cmxs \
25 | findlib_config.cmi findlib_config.ml topfind.cmi topfind.mli \
26 |
--------------------------------------------------------------------------------
/nix/overlay/ocamlfind/onix_install_topfind_195.patch:
--------------------------------------------------------------------------------
1 | Install topfind into OCAML_SITELIB instead of OCAML_CORE_STDLIB.
2 | See also: https://github.com/ocaml/opam-repository/blob/master/packages/ocamlfind/ocamlfind.1.9.5/files/0001-Fix-bug-when-installing-with-a-system-compiler.patch
3 | diff --git a/findlib.conf.in b/findlib.conf.in
4 | index 261d2c8..461bafc 100644
5 | --- a/findlib.conf.in
6 | +++ b/findlib.conf.in
7 | @@ -1,2 +1,3 @@
8 | destdir="@SITELIB@"
9 | path="@SITELIB@"
10 | +ldconf="ignore"
11 | \ No newline at end of file
12 | diff --git a/src/findlib/Makefile b/src/findlib/Makefile
13 | index 84514b6..12e4ef6 100644
14 | --- a/src/findlib/Makefile
15 | +++ b/src/findlib/Makefile
16 | @@ -123,8 +123,7 @@ clean:
17 | install: all
18 | $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAML_SITELIB)/$(NAME)"
19 | $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAMLFIND_BIN)"
20 | - test $(INSTALL_TOPFIND) -eq 0 || $(INSTALLDIR) "$(DESTDIR)$(prefix)$(OCAML_CORE_STDLIB)"
21 | - test $(INSTALL_TOPFIND) -eq 0 || $(INSTALLFILE) topfind "$(DESTDIR)$(prefix)$(OCAML_CORE_STDLIB)/"
22 | + test $(INSTALL_TOPFIND) -eq 0 || $(INSTALLFILE) topfind "$(DESTDIR)$(prefix)$(OCAML_SITELIB)/"
23 | files=`$(SH) $(TOP)/tools/collect_files $(TOP)/Makefile.config \
24 | findlib.cmi findlib.mli findlib.cma findlib.cmxa findlib$(LIB_SUFFIX) findlib.cmxs \
25 | findlib_config.cmi findlib_config.ml topfind.cmi topfind.mli \
26 |
--------------------------------------------------------------------------------
/src/onix_core/Overrides.ml:
--------------------------------------------------------------------------------
1 | let depexts_for_opam_name pkg_name =
2 | match OpamPackage.Name.to_string pkg_name with
3 | | "conf-bap-llvm" -> Some ["pkgsStatic.llvm.dev"]
4 | | "bap-llvm" -> Some ["libxml2"; "ncurses"]
5 | | "conf-binutils" -> Some ["binutils"]
6 | | "bap-std" -> Some ["binutils"; "zlib"]
7 | | "bitvec-sexp" -> Some ["which"]
8 | | "conf-gmp" -> Some ["gmp.dev"]
9 | | _ -> None
10 |
11 | (* Always include these packages in buildDepends in the lock-file, if they
12 | are present in depends. *)
13 | let build_depends_names =
14 | Opam_utils.
15 | [
16 | ocaml_base_compiler_name;
17 | ocaml_system_name;
18 | ocaml_variants_name;
19 | ocamlfind_name;
20 | dune_name;
21 | dune_configurator_name;
22 | ocamlbuild_name;
23 | topkg_name;
24 | cppo_name;
25 | menhir_name;
26 | ]
27 |
28 | let ocamlfind_setup_hook =
29 | {|
30 | [[ -z ''${strictDeps-} ]] || (( "$hostOffset" < 0 )) || return 0
31 |
32 | addTargetOCamlPath () {
33 | local libdir="$1/lib/ocaml/${ocaml.version}/site-lib"
34 |
35 | if [[ ! -d "$libdir" ]]; then
36 | return 0
37 | fi
38 |
39 | echo "+ onix-ocamlfind-setup-hook.sh/addTargetOCamlPath: $*"
40 |
41 | addToSearchPath "OCAMLPATH" "$libdir"
42 | addToSearchPath "CAML_LD_LIBRARY_PATH" "$libdir/stublibs"
43 | }
44 |
45 | addEnvHooks "$targetOffset" addTargetOCamlPath
46 |
47 | export OCAMLTOP_INCLUDE_PATH="$1/lib/ocaml/${ocaml.version}/site-lib/toplevel"
48 | |}
49 |
--------------------------------------------------------------------------------
/tests/transitive-deps/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import { }, onix ? import ../../. { inherit pkgs; } }:
2 |
3 | let
4 |
5 | inherit (pkgs.lib.lists) optional optionals;
6 | inherit (builtins)
7 | filter trace hasAttr getAttr setAttr attrNames attrValues mapAttrs concatMap
8 | pathExists foldl';
9 |
10 | evalDepFlag = version: depFlag:
11 | let isRoot = version == "root";
12 | in if depFlag == true then
13 | isRoot
14 | else if depFlag == "deps" then
15 | !isRoot
16 | else if depFlag == "all" then
17 | true
18 | else if depFlag == false then
19 | false
20 | else
21 | throw "invalid dependency flag value: ${depFlag}";
22 |
23 | processDeps = { withTest, withDoc, withDevSetup }@depFlags:
24 | builtins.foldl' (acc: dep:
25 | if acc ? dep.name then
26 | acc
27 | else
28 | let
29 | depends = dep.depends or [ ];
30 | buildDepends = dep.buildDepends or [ ];
31 | testDepends = optionals (evalDepFlag dep.version withTest)
32 | (dep.testDepends or [ ]);
33 | docDepends =
34 | optionals (evalDepFlag dep.version withDoc) (dep.docDepends or [ ]);
35 | devSetupDepends = optionals (evalDepFlag dep.version withDevSetup)
36 | (dep.devSetupDepends or [ ]);
37 | depexts = filter (x: !isNull x) (dep.depexts or [ ]);
38 | transitive = processDeps depFlags { } (depends ++ buildDepends);
39 | in acc // transitive // {
40 | ${dep.name} = {
41 | depends = map (x: x.name) depends;
42 | buildDepends = map (x: x.name) buildDepends;
43 | depexts = map (x: x.name) depexts;
44 | transitiveDepends = attrValues transitive;
45 | };
46 | });
47 |
48 | depFlags = {
49 | withTest = false;
50 | withDoc = false;
51 | withDevSetup = false;
52 | };
53 | onixLock = import ../../onix-lock.nix { inherit pkgs; };
54 | deps = processDeps depFlags { } (builtins.attrValues onixLock);
55 | in pkgs.writeText "transitive-deps.json" (builtins.toJSON deps)
56 |
--------------------------------------------------------------------------------
/src/onix_lock_graphviz/Onix_lock_graphviz.ml:
--------------------------------------------------------------------------------
1 | module String_set = Onix_core.Utils.String_set
2 | module Name_map = Onix_core.Utils.Name_map
3 | module Name_set = Onix_core.Utils.Name_set
4 | open Onix_core
5 |
6 | let get_lock_pkg_name (lock_pkg : Lock_pkg.t) =
7 | lock_pkg.opam_details.package.name
8 |
9 | let to_map lock_pkgs =
10 | List.fold_left
11 | (fun acc (lock_pkg : Lock_pkg.t) ->
12 | let name = get_lock_pkg_name lock_pkg in
13 | Name_map.add name lock_pkg acc)
14 | Name_map.empty lock_pkgs
15 |
16 | let pp ppf (pkgs : Lock_pkg.t Name_map.t) =
17 | Fmt.pf ppf "digraph deps {@.";
18 | Name_map.iter
19 | (fun name (lock_pkg : Lock_pkg.t) ->
20 | let str = OpamPackage.Name.to_string in
21 | let name = OpamPackage.Name.to_string name in
22 | (* depends *)
23 | Name_set.iter
24 | (fun dep -> Fmt.pf ppf "%S -> %S;@." name (str dep))
25 | lock_pkg.depends;
26 | (* depends_build *)
27 | Name_set.iter
28 | (fun dep -> Fmt.pf ppf "%S -> %S [color=\"red\"];@." name (str dep))
29 | lock_pkg.depends_build;
30 | (* depends_test *)
31 | Name_set.iter
32 | (fun dep -> Fmt.pf ppf "%S -> %S [color=\"blue\"];@." name (str dep))
33 | lock_pkg.depends_test;
34 | (* depends_doc *)
35 | Name_set.iter
36 | (fun dep -> Fmt.pf ppf "%S -> %S [color=\"green\"];@." name (str dep))
37 | lock_pkg.depends_doc;
38 | (* depends_dev_setup *)
39 | Name_set.iter
40 | (fun dep -> Fmt.pf ppf "%S -> %S [color=\"yellow\"];@." name (str dep))
41 | lock_pkg.depends_dev_setup;
42 | (* depexts_nix *)
43 | String_set.iter
44 | (fun dep -> Fmt.pf ppf "%S -> %S [color=\"grey\"];@." name dep)
45 | lock_pkg.depexts_nix;
46 | (* depeexts_unknown *)
47 | String_set.iter
48 | (fun dep -> Fmt.pf ppf "%S -> %S [color=\"grey\"];@." name dep)
49 | lock_pkg.depexts_unknown)
50 | pkgs;
51 | Fmt.pf ppf "}@."
52 |
53 | let gen ~graphviz_file_path (lock_file : Lock_file.t) =
54 | let pkgs = to_map lock_file.packages in
55 | Onix_core.Utils.Out_channel.with_open_text graphviz_file_path (fun chan ->
56 | let out = Format.formatter_of_out_channel chan in
57 | Fmt.pf out "%a" pp pkgs);
58 | Logs.info (fun log ->
59 | log "Created an graphviz file at %S." graphviz_file_path)
60 |
--------------------------------------------------------------------------------
/src/onix_lock_nix/Onix_lock_nix.ml:
--------------------------------------------------------------------------------
1 | open Prelude
2 |
3 | let gen_pkg ~lock_dir ~ocaml_version ~gitignore ~with_test ~with_doc
4 | ~with_dev_setup (lock_pkg : Lock_pkg.t) =
5 | let pkg_name = OpamPackage.name_to_string lock_pkg.opam_details.package in
6 | let pkg_lock_dir = lock_dir > "packages" > pkg_name in
7 | let pkg_default_nix =
8 | OpamFilename.mkdir pkg_lock_dir;
9 | OpamFilename.to_string (pkg_lock_dir /> "default.nix")
10 | in
11 | Out_channel.with_open_text pkg_default_nix @@ fun chan ->
12 | let nix_pkg =
13 | Nix_pkg.of_lock_pkg ~ocaml_version ~with_test ~with_doc ~with_dev_setup
14 | lock_pkg
15 | in
16 | Nix_pkg.copy_extra_files ~pkg_lock_dir nix_pkg.extra_files;
17 | (* let nix_pkg = Nix_pkg.resolve_files ~lock_dir nix_pkg in *)
18 | let out = Format.formatter_of_out_channel chan in
19 | Fmt.pf out "%a" (Pp.pp_pkg ~gitignore) nix_pkg
20 |
21 | let gen_overlay ~lock_dir =
22 | let overlay_dir = lock_dir > "overlay" in
23 | List.iter
24 | (fun relpath ->
25 | let file = OpamFilename.(create overlay_dir (Base.of_string relpath)) in
26 | let dir = OpamFilename.dirname file in
27 | OpamFilename.mkdir dir;
28 | match Overlay_files.read relpath with
29 | | None ->
30 | Fmt.failwith "internal error: could not read embedded overlay file: %a"
31 | Opam_utils.pp_filename file
32 | | Some file_content -> OpamFilename.write file file_content)
33 | Overlay_files.file_list
34 |
35 | let gen_index ~lock_dir lock_pkgs =
36 | let index_file =
37 | OpamFilename.Op.(lock_dir // "default.nix") |> OpamFilename.to_string
38 | in
39 | Out_channel.with_open_text index_file @@ fun chan ->
40 | let f = Format.formatter_of_out_channel chan in
41 | Pp.pp_index f lock_pkgs
42 |
43 | let gen ~gitignore ~lock_dir ~with_test ~with_doc ~with_dev_setup
44 | (lock_file : Lock_file.t) =
45 | let ocaml_version = OpamPackage.version lock_file.compiler in
46 | let lock_dir = OpamFilename.Dir.of_string lock_dir in
47 | OpamFilename.rmdir lock_dir;
48 | OpamFilename.mkdir lock_dir;
49 | gen_overlay ~lock_dir;
50 | gen_index ~lock_dir lock_file.packages;
51 | List.iter
52 | (gen_pkg ~lock_dir ~ocaml_version ~gitignore ~with_test ~with_doc
53 | ~with_dev_setup)
54 | lock_file.packages
55 |
56 | module Nix_pkg = Nix_pkg
57 | module Nix_filter = Nix_filter
58 | module Pp = Pp
59 |
--------------------------------------------------------------------------------
/src/onix_core/Scope.mli:
--------------------------------------------------------------------------------
1 | type pkg = {
2 | name : OpamTypes.name;
3 | version : OpamTypes.version;
4 | opamfile : string;
5 | opam : OpamFile.OPAM.t Lazy.t;
6 | prefix : string;
7 | }
8 |
9 | type t = {
10 | self : pkg;
11 | ocaml_version : OpamTypes.version;
12 | pkgs : pkg OpamTypes.name_map;
13 | vars : OpamVariable.variable_contents OpamVariable.Full.Map.t;
14 | }
15 |
16 | val make_pkg :
17 | name:OpamTypes.name ->
18 | version:OpamTypes.version ->
19 | opamfile:string ->
20 | prefix:string ->
21 | pkg
22 |
23 | val make :
24 | deps:pkg OpamPackage.Name.Map.t ->
25 | ?vars:OpamVariable.variable_contents OpamVariable.Full.Map.t ->
26 | ocaml_version:OpamPackage.Version.t ->
27 | pkg ->
28 | t
29 |
30 | val with_onix_path :
31 | onix_path:string ->
32 | ?vars:OpamVariable.variable_contents OpamVariable.Full.Map.t ->
33 | ocaml_version:OpamPackage.Version.t ->
34 | pkg ->
35 | t
36 |
37 | (* val get_opam : OpamPackage.Name.t -> t -> OpamFile.OPAM.t option *)
38 |
39 | (** {2 Variable resolvers} *)
40 |
41 | val resolve_global :
42 | ?system:System.t ->
43 | ?jobs:string ->
44 | ?user:string ->
45 | ?group:string ->
46 | OpamFilter.env
47 | (** Resolve global dynamic variables. *)
48 |
49 | val resolve_global_host : OpamFilter.env
50 | (** Resolve global host system variables. *)
51 |
52 | val resolve_system : ?arch:string -> ?os:string -> OpamFilter.env
53 | (** Resolve global host system variables. *)
54 |
55 | val resolve_pkg : build_dir:string -> t -> OpamFilter.env
56 | (** Resolves [name], [version] and [dev] vars. *)
57 |
58 | val resolve_opam_pkg : OpamPackage.t -> OpamFilter.env
59 | (** Resolves [name], [version] and [dev] vars. *)
60 |
61 | val resolve_stdenv_host : OpamFilter.env
62 | (** Resolves opam variables from host's enviroonment variables. *)
63 |
64 | val resolve_config : t -> OpamFilter.env
65 | (** Resolves the variables from config file. *)
66 |
67 | val resolve_local :
68 | OpamVariable.variable_contents option OpamVariable.Map.t -> OpamFilter.env
69 | (** Resolves from a local variable map.. *)
70 |
71 | val resolve_dep :
72 | ?build:bool ->
73 | ?post:bool ->
74 | ?test:bool ->
75 | ?doc:bool ->
76 | ?dev_setup:bool ->
77 | OpamFilter.env
78 | (** Resolve dependency variables. *)
79 |
80 | val resolve_many : OpamFilter.env list -> OpamFilter.env
81 | (* Resolves using a provided list of resolvers. *)
82 |
--------------------------------------------------------------------------------
/src/onix_core/Resolutions.ml:
--------------------------------------------------------------------------------
1 | type t = {
2 | with_constraint : OpamFormula.version_constraint OpamPackage.Name.Map.t;
3 | without_constraint : OpamPackage.Name.Set.t;
4 | }
5 |
6 | let default =
7 | {
8 | with_constraint = OpamPackage.Name.Map.empty;
9 | without_constraint = OpamPackage.Name.Set.empty;
10 | }
11 |
12 | let constraints t = t.with_constraint
13 |
14 | let make t =
15 | List.fold_left
16 | (fun acc (name, constraint_opt) ->
17 | match constraint_opt with
18 | | Some constr ->
19 | {
20 | acc with
21 | with_constraint =
22 | OpamPackage.Name.Map.add name constr acc.with_constraint;
23 | }
24 | | None ->
25 | {
26 | acc with
27 | without_constraint =
28 | OpamPackage.Name.Set.add name acc.without_constraint;
29 | })
30 | default t
31 |
32 | let all t =
33 | OpamPackage.Name.Map.fold
34 | (fun name _ acc -> OpamPackage.Name.Set.add name acc)
35 | t.with_constraint t.without_constraint
36 | |> OpamPackage.Name.Set.to_seq
37 | |> List.of_seq
38 |
39 | let debug t =
40 | if OpamPackage.Name.Map.cardinal t.with_constraint > 0 then
41 | Fmt.epr "Resolutions:@.";
42 | OpamPackage.Name.Map.iter
43 | (fun n vc ->
44 | Fmt.epr "- %s@." (OpamFormula.short_string_of_atom (n, Some vc)))
45 | t.with_constraint;
46 | OpamPackage.Name.Set.iter
47 | (fun n -> Fmt.epr "- %a@." Opam_utils.pp_package_name n)
48 | t.without_constraint
49 |
50 | let resolution_re =
51 | Re.
52 | [
53 | bos;
54 | group (rep1 (diff any (set ">=<.!")));
55 | group (alt [seq [set "<>"; opt (char '=')]; set "=."; str "!="]);
56 | group (rep1 any);
57 | eos;
58 | ]
59 | |> Re.seq
60 | |> Re.compile
61 |
62 | let parse_resolution str =
63 | try
64 | let sub = Re.exec resolution_re str in
65 | let name = OpamPackage.Name.of_string (Re.Group.get sub 1) in
66 | let op = Re.Group.get sub 2 in
67 | let op = if op = "." then "=" else op in
68 | let op = OpamLexer.FullPos.relop op in
69 | let version = Re.Group.get sub 3 in
70 | let version = OpamPackage.Version.of_string version in
71 | `Ok (name, Some (op, version))
72 | with Not_found | Failure _ | OpamLexer.Error _ -> (
73 | try `Ok (OpamPackage.Name.of_string str, None)
74 | with Failure msg -> `Error msg)
75 |
76 | let pp_resolution ppf x = Fmt.string ppf (OpamFormula.short_string_of_atom x)
77 |
--------------------------------------------------------------------------------
/tests/zarith.nix:
--------------------------------------------------------------------------------
1 | { nixpkgs, onixpkgs, onix, self }:
2 |
3 | onix.package {
4 | name = "zarith";
5 | version = "1.12";
6 | src = {
7 | url = "https://github.com/ocaml/Zarith/archive/release-1.12.tar.gz";
8 | sha512 =
9 | "8075573ae65579a2606b37dd1b213032a07d220d28c733f9288ae80d36f8a2cc4d91632806df2503c130ea9658dc207ee3a64347c21aa53969050a208f5b2bb4";
10 | };
11 |
12 | build = [
13 | [ [ "cmd3" ] { system = "aarch64_darwin"; } ]
14 |
15 | (on (os != "openbsd" && os != "freebsd" && os != "macos") [ "./configure" ])
16 | (on (os == "openbsd" || os == "freebsd") [
17 | "sh"
18 | "-exc"
19 | ''
20 | LDFLAGS="$LDFLAGS -L/usr/local/lib" CFLAGS="$CFLAGS -I/usr/local/include" ./configure''
21 | ])
22 | (on (os == "macos" && arch == "x86_64") [
23 | "sh"
24 | "-exc"
25 | ''
26 | LDFLAGS="$LDFLAGS -L/opt/local/lib -L/usr/local/lib" CFLAGS="$CFLAGS -I/opt/local/include -I/usr/local/include" ./configure''
27 | ])
28 | (on (os == "macos" && arch == "arm64") [
29 | "sh"
30 | "-exc"
31 | ''
32 | LDFLAGS="$LDFLAGS -L/opt/homebrew/lib" CFLAGS="$CFLAGS -I/opt/homebrew/include" ./configure''
33 | ])
34 | [ "make" ]
35 |
36 | [
37 | "ocaml"
38 | "pkg/build.ml"
39 | "native=${var ocaml "native"}"
40 | "native-dynlink=${ocaml "native-dynlink"}"
41 | ]
42 | ];
43 |
44 | install = with onix.vars; [
45 | [ make "install" ]
46 | (on (ocaml.var "preinstalled") [
47 | "install"
48 | "-m"
49 | "0755"
50 | "ocaml-stub"
51 | "${self "bin"}/ocaml"
52 | ])
53 | ];
54 |
55 | install' = with onix.vars; [
56 | [ vars.make "install" ]
57 | (on (var "ocaml" "preinstalled") [
58 | "install"
59 | "-m"
60 | "0755"
61 | "ocaml-stub"
62 | "${self "bin"}/ocaml"
63 | ])
64 | ];
65 |
66 | installPhase = ''
67 | "mkdir" "-p" "$out/lib/ocaml/4.14.0/site-lib"
68 | "make" "install"
69 |
70 | ${nixpkgs.opaline}/bin/opaline \
71 | -prefix="$out" \
72 | -libdir="$out/lib/ocaml/4.14.0/site-lib"
73 |
74 | if [[ -e "./zarith.config" ]]; then
75 | mkdir -p "$out/etc"
76 | cp "./zarith.config" "$out/etc/zarith.config"
77 | fi
78 | '';
79 |
80 | depends = [
81 | "ocaml"
82 | "ocamlfind"
83 | "conf-gmp"
84 | (on with-dev-setup "ocaml-lsp-server")
85 | (on (with-dev-setup || with-test) "utop")
86 | (on with-test "foo")
87 | ];
88 | }
89 |
--------------------------------------------------------------------------------
/src/onix_lock_opam/Pp.ml:
--------------------------------------------------------------------------------
1 | module Lock_file = Onix_core.Lock_file
2 | module Lock_pkg = Onix_core.Lock_pkg
3 | module Opam_utils = Onix_core.Opam_utils
4 |
5 | let ( or ) = Onix_core.Utils.( or )
6 |
7 | let metadata =
8 | [
9 | ("opam-version", "2.0");
10 | ("synopsis", "This lock file was generated by onix " ^ Onix_core.Lib.version);
11 | ("maintainer", "The onix programmers");
12 | ("author", "The onix programmers");
13 | ("homepage", "https://github.com/odis-labs/onix");
14 | ("bug-reports", "https://github.com/odis-labs/onix/issues");
15 | ]
16 |
17 | let pp_metadata f fields =
18 | Fmt.pf f "%a" (Fmt.list (fun f (k, v) -> Fmt.pf f "%s: %S" k v)) fields
19 |
20 | let pp_synopsis f synopsis = Fmt.pf f "synopsis: %S" synopsis
21 |
22 | let pp_depend f (pkg : Lock_pkg.t) =
23 | Fmt.pf f "%S {= %S}"
24 | (OpamPackage.name_to_string pkg.opam_details.package)
25 | (OpamPackage.version_to_string pkg.opam_details.package)
26 |
27 | let pp_depends f depends =
28 | Fmt.pf f "@[depends: [@,%a@]@,]" (Fmt.list pp_depend) depends
29 |
30 | let pp_pin_depend_src f (pkg : Lock_pkg.t) =
31 | match pkg.src with
32 | | Some (Git { url; rev }) -> Fmt.pf f "\"git+%s#%s\"" url rev
33 | | Some _ ->
34 | Fmt.invalid_arg
35 | "Lock dependency error for pinned package %a: url must be a Git URL"
36 | Opam_utils.pp_package pkg.opam_details.package
37 | | None ->
38 | Fmt.invalid_arg
39 | "Lock dependency error for pinned package %a: missing Git URL"
40 | Opam_utils.pp_package pkg.opam_details.package
41 |
42 | let pp_pin_depend f (pkg : Lock_pkg.t) =
43 | Fmt.pf f "@[[@,%S@,%a@]@,]"
44 | (OpamPackage.to_string pkg.opam_details.package)
45 | pp_pin_depend_src pkg
46 |
47 | let pp_pin_depends f pin_depends =
48 | Fmt.pf f "@[pin-depends: [@,%a@]@,]" (Fmt.list pp_pin_depend) pin_depends
49 |
50 | let pp fmt (lock : Lock_file.t) =
51 | let pin_depends, depends =
52 | List.fold_left
53 | (fun (pins, deps) (pkg : Lock_pkg.t) ->
54 | match
55 | ( Opam_utils.is_pinned pkg.opam_details.package,
56 | Opam_utils.Opam_details.check_has_absolute_path pkg.opam_details )
57 | with
58 | | true, true -> (pkg :: pins, pkg :: deps)
59 | | false, true -> (pins, pkg :: deps)
60 | | _ -> (pins, deps))
61 | ([], []) lock.packages
62 | in
63 | let pin_depends, depends = (List.rev pin_depends, List.rev depends) in
64 | Fmt.pf fmt
65 | "@[# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT DIRECTLY.@,\
66 | %a@,\
67 | %a@,\
68 | %a@]@."
69 | pp_metadata metadata pp_depends depends pp_pin_depends pin_depends
70 |
--------------------------------------------------------------------------------
/tests/Opam_to_nix.ml:
--------------------------------------------------------------------------------
1 | (*
2 |
3 | Allowed vars:
4 | - os = linux | macos | win32 | cygwin | freebsd | openbsd | netbsd | dragonfly (uname -s)
5 | - possible = linux | macos
6 | - os-distribution = homebrew | macports | android | distro-name | $os
7 | - os-family = debian | bsd | windows
8 | - possible = nixos
9 | - arch
10 | - os-version = release id
11 | - jobs
12 | - make
13 | - with-test
14 | - with-doc
15 | - with-dev-setup
16 | - post?
17 |
18 |
19 | *)
20 |
21 | let opamfile = "../../../tests/zarith.opam"
22 | let opam = In_channel.with_open_text opamfile OpamFile.OPAM.read_from_channel
23 | let build_commands = OpamFile.OPAM.build opam
24 |
25 | (* let install_commands = OpamFile.OPAM.install opam *)
26 | (* let depends = OpamFile.OPAM.depends opam *)
27 |
28 | let dep_names =
29 | ["ocaml"; "ocamlfind"; "conf-gmp"; "ocaml-lsp-server"; "utop"; "foo"]
30 |
31 | let ocaml_version = OpamPackage.Version.of_string "4.14.0"
32 |
33 | let pkg_scope =
34 | let name = OpamPackage.Name.of_string "zarith" in
35 | let version = OpamPackage.Version.of_string "dev" in
36 | let dep_names =
37 | OpamPackage.Name.Set.of_list (List.map OpamPackage.Name.of_string dep_names)
38 | in
39 | let deps =
40 | OpamPackage.Name.Set.fold
41 | (fun name acc ->
42 | let prefix =
43 | String.concat "" ["${"; OpamPackage.Name.to_string name; "}"]
44 | in
45 | let pkg =
46 | Onix_core.Scope.make_pkg ~name
47 | ~version:(OpamPackage.Version.of_string "version_todo")
48 | ~opamfile:
49 | (Onix_core.Paths.lib ~pkg_name:name ~ocaml_version prefix
50 | ^ "/opam")
51 | ~prefix
52 | in
53 | OpamPackage.Name.Map.add name pkg acc)
54 | dep_names OpamPackage.Name.Map.empty
55 | in
56 | let self =
57 | Onix_core.Scope.make_pkg ~name ~version ~opamfile:"./zarith.opam"
58 | ~prefix:"$out"
59 | in
60 | Onix_core.Scope.make ~deps ~ocaml_version self
61 |
62 | (* let env = Onix_lock_nix.Nix_pkg.resolve_commands pkg_scope *)
63 |
64 | let process_commands commands =
65 | let commands' =
66 | Onix_core.Filter.process_commands ~with_test:true ~with_doc:true
67 | ~with_dev_setup:false pkg_scope commands
68 | in
69 | Fmt.pr "%a" Onix_core.Filter.pp_commands commands'
70 |
71 | (* let process_depends depends =
72 | (* let depends_filtered_formula =
73 | OpamFilter.string_of_filtered_formula depends
74 | in *)
75 | let depends_ands : OpamTypes.filtered_formula list =
76 | OpamFormula.ands_to_list depends
77 | in
78 | let depends_ands_srs_list =
79 | List.map OpamFilter.string_of_filtered_formula depends_ands
80 | in
81 |
82 | Fmt.pr "%s" (String.concat ";; " depends_ands_srs_list) *)
83 |
84 | let () = process_commands build_commands
85 |
--------------------------------------------------------------------------------
/nix/experiments/subst.nix:
--------------------------------------------------------------------------------
1 | let
2 | inherit (builtins) length toJSON elemAt isNull isList;
3 | trace = x: builtins.trace "onix: [TRACE] ${builtins.toJSON x}" x;
4 | debug = data: x: builtins.trace "onix: [DEBUG] ${builtins.toJSON data}" x;
5 | in rec {
6 | # %{...}%
7 | split-file-content = content:
8 | builtins.split "%\\{([a-z0-9?:_-]+)\\}%" content;
9 |
10 | # scope:var?string-if-true:string-if-false-or-undefined
11 | split-full-var-cond = ful-var-cond:
12 | builtins.split "^([^:]+):?([^?]*)\\??([^:]*):?(.*)" ful-var-cond;
13 |
14 | # builtins.split ''^([^:]+):?([^?]*)\??([^:]*):?(.*)'' "ocaml-system:installed?yes:no"
15 | # => ["",["ocaml-system","installed","yes","no"],""]
16 | # => { scope = "package"; pkg-name = "ocaml-system"; var = "installed"; val-if-true = "yes"; val-if-false = "no"; }
17 | process-var = parts:
18 | if length parts != 3 then
19 | throw "Could not process subst variable: ${toJSON parts}"
20 | else
21 | let var-parts = elemAt parts 1;
22 | in if length var-parts != 4 then
23 | throw "Could not process subst variable: ${toJSON var-parts}"
24 | else
25 | let
26 | scope-or-var-name-part = elemAt var-parts 0;
27 | var-name-opt-part = elemAt var-parts 1;
28 | val-if-true-part = elemAt var-parts 2;
29 | val-if-false-part = elemAt var-parts 3;
30 | in rec {
31 | pkg-name =
32 | if var-name-opt-part != "" then scope-or-var-name-part else null;
33 | scope = if isNull pkg-name then
34 | "global"
35 | else if pkg-name == "_" then
36 | "self"
37 | else
38 | "package";
39 | var = if var-name-opt-part != "" then
40 | var-name-opt-part
41 | else
42 | scope-or-var-name-part;
43 | val-if-true =
44 | if val-if-true-part != "" then val-if-true-part else null;
45 | val-if-false =
46 | if val-if-false-part != "" then val-if-false-part else null;
47 | };
48 |
49 | subst-file-content = resolve: content:
50 | let
51 | is-var-part = builtins.isList;
52 | content-parts = split-file-content content;
53 | contents-resolved-parts = builtins.concatMap (part:
54 | # invalid var
55 | if isList part && length part != 1 then
56 | throw "could not process file subst var: ${toJSON part}"
57 | else
58 | # valid var
59 | if isList part then
60 | let
61 | full-var-cond-str = elemAt part 0;
62 | full-var-cond = split-full-var-cond full-var-cond-str;
63 | full-var = process-var full-var-cond;
64 | resolved = resolve full-var;
65 | in [ (if isNull resolved then "" else resolved) ]
66 | else
67 | # text
68 | [ part ]) content-parts;
69 | in builtins.concatStringsSep "" contents-resolved-parts;
70 | }
71 |
--------------------------------------------------------------------------------
/tests/zarith.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 | maintainer: "Xavier Leroy "
3 | authors: [
4 | "Antoine Miné"
5 | "Xavier Leroy"
6 | "Pascal Cuoq"
7 | ]
8 | homepage: "https://github.com/ocaml/Zarith"
9 | bug-reports: "https://github.com/ocaml/Zarith/issues"
10 | dev-repo: "git+https://github.com/ocaml/Zarith.git"
11 | build: [
12 | #["cmd1"] {pkg1+pkg2+pkg3:installed}
13 | #["cmd2"] {!(?foo & foo != bar)}
14 | #["cmd2"] {var1}
15 | #["cmd2"] {var2 = 3 | !(pkg1:var2)}
16 |
17 | ["cmd" "macos & arm64"] {os = "macos" & arch = "arm64"}
18 |
19 | ["dune" "subst"] {pinned}
20 |
21 | [
22 | "dune"
23 | "build"
24 | "-p"
25 | name
26 | "-j"
27 | jobs
28 | "@install"
29 | "@runtest" {with-test}
30 | "@doc" {with-doc}
31 | ]
32 |
33 | ["./configure" "!openbs & !freebsd & !macos"] {os != "openbsd" & os != "freebsd" & os != "macos"}
34 |
35 | [
36 | "sh"
37 | "-exc"
38 | "LDFLAGS=\"$LDFLAGS -L/usr/local/lib\" CFLAGS=\"$CFLAGS -I/usr/local/include\" ./configure"
39 | ] {os = "openbsd" | os = "freebsd"}
40 |
41 | [
42 | "sh"
43 | "-exc"
44 | "LDFLAGS=\"$LDFLAGS -L/opt/local/lib -L/usr/local/lib\" CFLAGS=\"$CFLAGS -I/opt/local/include -I/usr/local/include\" ./configure"
45 | ] {os = "macos" & os-distribution != "homebrew"}
46 |
47 | [
48 | "sh"
49 | "-exc"
50 | "LDFLAGS=\"$LDFLAGS -L/opt/local/lib -L/usr/local/lib\" CFLAGS=\"$CFLAGS -I/opt/local/include -I/usr/local/include\" ./configure"
51 | ] {os = "macos" & os-distribution = "homebrew" & arch = "x86_64" }
52 |
53 | [
54 | "sh"
55 | "-exc"
56 | "LDFLAGS=\"$LDFLAGS -L/opt/homebrew/lib\" CFLAGS=\"$CFLAGS -I/opt/homebrew/include\" ./configure"
57 | ] {os = "macos" & os-distribution = "homebrew" & arch = "arm64" }
58 |
59 | [make]
60 | ]
61 |
62 | install: [
63 | ["dune" "build" "@runtest"] {with-test}
64 | ["dune" "build" "@arm64"] {arch = "arm64"}
65 | ["dune" "build" "@macos"] {os = "macos"}
66 | ["dune" "build" "@arm64-macos"] {arch = "arm64" & os = "macos"}
67 | ["dune" "build" "@arm64-macos"] {arch = "arm64" | os = "macos"}
68 | [make "install"]
69 | ]
70 | #depends: ("pkg-1" | "pkg-2")
71 | depends: [
72 | "pkg-plain"
73 | "pkg-ver" {>= "4.04.0"}
74 | "pkg-flag" {with-dev-setup}
75 | "pkg-ver-or-flag" {= "4.0" & with-test}
76 | "pkg-ver-and-flag" {= "4.0" & with-test}
77 | "pkg-flag-or" {with-dev-setup | with-test}
78 | "pkg-flag-and" {with-dev-setup & with-test}
79 | ]
80 | synopsis:
81 | "Implements arithmetic and logical operations over arbitrary-precision integers"
82 | description: """
83 | The Zarith library implements arithmetic and logical operations over
84 | arbitrary-precision integers. It uses GMP to efficiently implement
85 | arithmetic over big integers. Small integers are represented as Caml
86 | unboxed integers, for speed and space economy."""
87 | url {
88 | src: "https://github.com/ocaml/Zarith/archive/release-1.12.tar.gz"
89 | checksum: [
90 | "md5=bf368f3d9e20b6b446d54681afc05a04"
91 | "sha512=8075573ae65579a2606b37dd1b213032a07d220d28c733f9288ae80d36f8a2cc4d91632806df2503c130ea9658dc207ee3a64347c21aa53969050a208f5b2bb4"
92 | ]
93 | }
94 |
--------------------------------------------------------------------------------
/nix/experiments/test-vars.nix:
--------------------------------------------------------------------------------
1 | { nixpkgs ? import { } }:
2 |
3 | let
4 | ocaml-version = "4.12.0";
5 |
6 | vars = import ./vars.nix {
7 | platform = nixpkgs.platform;
8 | inherit ocaml-version;
9 | };
10 |
11 | pkgs = {
12 | "ocaml-config-2" = {
13 | name = "ocaml-config";
14 | version = "2";
15 | opamfile = "/packages/ocaml-config/opam";
16 | prefix = "/packages/ocaml-config";
17 | };
18 | "ocaml-base-compiler" = {
19 | name = "ocaml-base-compiler";
20 | version = "4.14.0";
21 | opamfile = "/packages/ocaml-base-compiler/opam";
22 | prefix = "/packages/ocaml-base-compiler";
23 | };
24 | "bos" = {
25 | name = "bos";
26 | version = "0.2.1";
27 | opamfile = "/packages/bos/opam";
28 | prefix = "/packages/bos";
29 | };
30 | "cmdliner" = {
31 | name = "cmdliner";
32 | version = "1.1.1";
33 | opamfile = "/packages/cmdliner/opam";
34 | prefix = "/packages/cmdliner";
35 | };
36 | "dune" = {
37 | name = "dune";
38 | version = "3.1.1";
39 | opamfile = "/packages/dune/opam";
40 | prefix = "/packages/dune";
41 | };
42 | "easy-format" = {
43 | name = "easy-format-1";
44 | version = "1.3.2";
45 | opamfile = "/packages/easy-format-1/opam";
46 | prefix = "/packages/easy-format-1";
47 | };
48 | "fpath" = {
49 | name = "fpath";
50 | version = "0.7.3";
51 | opamfile = "/packages/fpath/opam";
52 | prefix = "/packages/fpath";
53 | };
54 | "ocaml" = {
55 | name = "ocaml";
56 | version = "4.14.0";
57 | opamfile = "/packages/ocaml/opam";
58 | prefix = "/packages/ocaml";
59 | };
60 | "opam-0install" = {
61 | name = "opam-0install";
62 | version = "0.4.3";
63 | opamfile = "/packages/opam-0install/opam";
64 | prefix = "/packages/opam-0install";
65 | };
66 | "options" = {
67 | name = "options";
68 | version = "dev";
69 | opamfile = "/packages/options/opam";
70 | prefix = "/packages/options";
71 | };
72 | "uri" = {
73 | name = "uri";
74 | version = "4.2.0";
75 | opamfile = "/packages/uri/opam";
76 | prefix = "/packages/uri";
77 | };
78 | "yojson" = {
79 | name = "yojson";
80 | version = "1.7.0";
81 | opamfile = "/packages/yojson/opam";
82 | prefix = "/packages/yojson";
83 | };
84 | };
85 |
86 | self = {
87 | name = "onix-example";
88 | version = "dev";
89 | opamfile = "/packages/onix-exmple/opam";
90 | prefix = "/packages/onix-example";
91 | };
92 |
93 | build-dir = "/build";
94 |
95 | resolve = vars.resolve-pkg { inherit build-dir self pkgs ocaml-version; };
96 | in builtins.toJSON {
97 | "name" = resolve {
98 | scope = "global";
99 | var = "name";
100 | };
101 | "version" = resolve {
102 | scope = "global";
103 | var = "version";
104 | };
105 | "prefix" = resolve {
106 | scope = "global";
107 | var = "prefix";
108 | };
109 | "switch" = resolve {
110 | scope = "global";
111 | var = "switch";
112 | };
113 | "installed" = resolve {
114 | scope = "global";
115 | var = "installed";
116 | };
117 | "lib" = resolve {
118 | scope = "global";
119 | var = "lib";
120 | };
121 | "yojson:name" = resolve {
122 | scope = "package";
123 | pkg-name = "yojson";
124 | var = "name";
125 | };
126 | "yojson:lib" = resolve {
127 | scope = "package";
128 | pkg-name = "yojson";
129 | var = "lib";
130 | };
131 | }
132 |
--------------------------------------------------------------------------------
/src/onix_core/Pin_depends.ml:
--------------------------------------------------------------------------------
1 | open Utils
2 |
3 | let equal (pkg, url) (pkg', url') = OpamPackage.equal pkg pkg' && url = url'
4 |
5 | let _pp fmt (pkg, url) =
6 | Format.fprintf fmt "(%a, %a)" Opam_utils.pp_package pkg Opam_utils.pp_url url
7 |
8 | let sort_uniq pin_depends =
9 | let add acc ((pkg, url) as t) =
10 | let name = OpamPackage.name pkg in
11 | match OpamPackage.Name.Map.find_opt name acc with
12 | | None -> OpamPackage.Name.Map.add name (pkg, url) acc
13 | | Some t' when equal t t' -> acc
14 | | Some (pkg', url') ->
15 | Fmt.failwith
16 | "Package %a is pinned to different versions/url:\n\
17 | \ - %a: %a\n\
18 | \ - %a: %a" Opam_utils.pp_package_name name Opam_utils.pp_package pkg
19 | Opam_utils.pp_url url Opam_utils.pp_package pkg' Opam_utils.pp_url url'
20 | in
21 | List.fold_left add OpamPackage.Name.Map.empty pin_depends
22 |
23 | (* FIXME: validate pin-depends urls/format *)
24 | let collect_urls_from_opam_files project_opam_files =
25 | OpamPackage.Name.Map.fold
26 | (fun _pkg { Opam_utils.opam; _ } acc ->
27 | OpamFile.OPAM.pin_depends opam @ acc)
28 | project_opam_files []
29 | |> sort_uniq
30 |
31 | (* Returns the opam path and opam representation. *)
32 | let load_opam package src =
33 | let name = OpamPackage.name_to_string package in
34 | let opam_path =
35 | let without_pkg_name = OpamFilename.Op.(src // "opam") in
36 | if OpamFilename.exists without_pkg_name then without_pkg_name
37 | else
38 | let with_pkg_name =
39 | OpamFilename.add_extension OpamFilename.Op.(src // name) "opam"
40 | in
41 | if OpamFilename.exists with_pkg_name then with_pkg_name
42 | else
43 | Fmt.invalid_arg "Could not find opam file for package %s in %s" name
44 | (OpamFilename.Dir.to_string src)
45 | in
46 | (opam_path, Opam_utils.read_opam opam_path)
47 |
48 | (* Better error for missing files/paths. *)
49 | let collect_from_opam_files project_opam_files =
50 | let pin_urls = collect_urls_from_opam_files project_opam_files in
51 | OpamPackage.Name.Map.map
52 | (fun (pkg, url) ->
53 | let name_str = OpamPackage.name_to_string pkg in
54 | (* If the pinned url uses the file transport, we considered it a local "root". *)
55 | if String.equal url.OpamUrl.transport "file" then (
56 | (* FIXME: Dir.of_string resolves to absolute path. *)
57 | let src = OpamFilename.Dir.of_string url.OpamUrl.path in
58 | Logs.debug (fun log ->
59 | log "Reading opam file for vendored pin: name=%S url=%a src=%a"
60 | name_str Opam_utils.pp_url url Opam_utils.pp_filename_dir src);
61 | let path, opam = load_opam pkg src in
62 | (* Ensure the opam file does not have a remote url field. *)
63 | let opam = OpamFile.OPAM.with_url_opt None opam in
64 | let package =
65 | OpamPackage.create (OpamPackage.name pkg) Opam_utils.dev_version
66 | in
67 | { Opam_utils.package; opam; path })
68 | else
69 | (* Read original opam file for pin and use a fixed [url]. *)
70 | let src = Nix_utils.fetch_git url in
71 | Logs.debug (fun log ->
72 | log "Reading opam file for remote pin: name=%S url=%a src=%a"
73 | name_str Opam_utils.pp_url url Opam_utils.pp_filename_dir src);
74 | let path, opam = load_opam pkg src in
75 | let file_url = OpamFile.URL.create url in
76 | let opam = OpamFile.OPAM.with_url file_url opam in
77 | let package =
78 | OpamPackage.create (OpamPackage.name pkg) Opam_utils.dev_version
79 | in
80 | { package; opam; path })
81 | pin_urls
82 |
--------------------------------------------------------------------------------
/src/onix_lock_nix/Nix_filter.ml:
--------------------------------------------------------------------------------
1 | let relop_kind_to_nix_string (relop_kind : OpamParserTypes.relop) =
2 | match relop_kind with
3 | | `Eq -> "=="
4 | | `Neq -> "!="
5 | | `Geq -> ">="
6 | | `Gt -> ">"
7 | | `Leq -> "<="
8 | | `Lt -> "<"
9 |
10 | let opam_filter_to_nix_string ?custom (t : OpamTypes.filter) =
11 | let custom ~context ~paren t =
12 | match custom with
13 | | None -> None
14 | | Some f -> f ~context ~paren t
15 | in
16 | let rec aux ?(context = `Or) (t : OpamTypes.filter) =
17 | let paren ?(cond = false) f =
18 | if cond || OpamFormatConfig.(!r.all_parens) then Printf.sprintf "(%s)" f
19 | else f
20 | in
21 | match custom ~context ~paren t with
22 | | Some str -> str
23 | | None -> (
24 | match t with
25 | | FBool b -> string_of_bool b
26 | | FString s -> Printf.sprintf "%S" s
27 | | FIdent (pkgs, var, converter) -> (
28 | OpamStd.List.concat_map "+"
29 | (function
30 | | None -> "_"
31 | | Some p -> OpamPackage.Name.to_string p)
32 | pkgs
33 | ^ (if pkgs <> [] then ":" else "")
34 | ^ OpamVariable.to_string var
35 | ^
36 | match converter with
37 | | Some (it, ifu) -> "?" ^ it ^ ":" ^ ifu
38 | | None -> "")
39 | | FOp (e, s, f) ->
40 | paren
41 | ~cond:(context <> `Or && context <> `And)
42 | (Printf.sprintf "%s %s %s" (aux ~context:`Relop e)
43 | (relop_kind_to_nix_string s)
44 | (aux ~context:`Relop f))
45 | | FAnd (e, f) ->
46 | paren
47 | ~cond:(context <> `Or && context <> `And)
48 | (Printf.sprintf "%s && %s" (aux ~context:`And e) (aux ~context:`And f))
49 | | FOr (e, f) ->
50 | paren ~cond:(context <> `Or) (Printf.sprintf "%s || %s" (aux e) (aux f))
51 | | FNot e ->
52 | paren ~cond:(context = `Relop)
53 | (Printf.sprintf "!%s" (aux ~context:`Not e))
54 | | FDefined e ->
55 | paren ~cond:(context = `Relop)
56 | (Printf.sprintf "?%s" (aux ~context:`Defined e))
57 | | FUndef f -> Printf.sprintf "#undefined(%s)" (aux f))
58 | in
59 | aux t
60 |
61 | let rec simplify_conjunction (filter : OpamTypes.filter) : OpamTypes.filter =
62 | match filter with
63 | | FOp (f1, relop, f2) ->
64 | FOp (simplify_conjunction f1, relop, simplify_conjunction f2)
65 | | FOr (f1, f2) -> FOr (simplify_conjunction f1, simplify_conjunction f2)
66 | | FNot f1 -> simplify_conjunction f1
67 | | FAnd (FBool true, f) | FAnd (f, FBool true) -> simplify_conjunction f
68 | | FAnd (f1, f2) -> FAnd (simplify_conjunction f1, simplify_conjunction f2)
69 | | _ -> filter
70 |
71 | let partial_eval_filter ~env filter =
72 | let filter' = OpamFilter.partial_eval env filter in
73 | simplify_conjunction filter'
74 |
75 | let process_command ~env (cmd : OpamTypes.command) =
76 | match cmd with
77 | | args, Some filter -> (
78 | let filter' = partial_eval_filter ~env filter in
79 | (* let args' = OpamFilter.single_command dummy_env args in *)
80 | (* Fmt.pr "%a: %S --> %S |- %b@."
81 | Fmt.Dump.(list string)
82 | args'
83 | (OpamFilter.to_string filter)
84 | (OpamFilter.to_string filter')
85 | (opam_filter_is_false filter'); *)
86 | let args' = OpamFilter.single_command env args in
87 | match filter' with
88 | | OpamTypes.FBool true -> Some (args', None)
89 | | OpamTypes.FBool false -> None
90 | | _ -> Some (args', Some filter'))
91 | | args, None ->
92 | let args' = OpamFilter.single_command env args in
93 | Some (args', None)
94 |
95 | let process_commands ~env commands =
96 | List.filter_map (process_command ~env) commands
97 |
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_file/onix-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.5",
3 | "repositories": [
4 | {
5 | "url": "https://github.com/ocaml/opam-repository.git",
6 | "rev": "9e6ae0a9398cf087ec2b3fbcd62cb6072ccf95ce"
7 | }
8 | ],
9 | "packages" : {
10 | "base-threads": {
11 | "version": "base"
12 | },
13 | "base-unix": {
14 | "version": "base"
15 | },
16 | "dune": {
17 | "version": "3.10.0",
18 | "src": {
19 | "url": "https://github.com/ocaml/dune/releases/download/3.10.0/dune-3.10.0.tbz",
20 | "sha256": "9ff03384a98a8df79852cc674f0b4738ba8aec17029b6e2eeb514f895e710355"
21 | },
22 | "depends": [
23 | "base-threads",
24 | "base-unix",
25 | "ocaml"
26 | ]
27 | },
28 | "fmt": {
29 | "version": "0.9.0",
30 | "src": {
31 | "url": "https://erratique.ch/software/fmt/releases/fmt-0.9.0.tbz",
32 | "sha512": "66cf4b8bb92232a091dfda5e94d1c178486a358cdc34b1eec516d48ea5acb6209c0dfcb416f0c516c50ddbddb3c94549a45e4a6d5c5fd1c81d3374dec823a83b"
33 | },
34 | "depends": [
35 | "base-unix",
36 | "ocaml"
37 | ],
38 | "build-depends": [
39 | "ocamlbuild",
40 | "ocamlfind",
41 | "topkg"
42 | ]
43 | },
44 | "ocaml": {
45 | "version": "4.14.2",
46 | "depends": [
47 | "ocaml-config",
48 | "ocaml-variants"
49 | ],
50 | "build-depends": [
51 | "ocaml-variants"
52 | ]
53 | },
54 | "ocaml-config": {
55 | "version": "2",
56 | "depends": [
57 | "ocaml-variants"
58 | ],
59 | "build-depends": [
60 | "ocaml-variants"
61 | ]
62 | },
63 | "ocaml-variants": {
64 | "version": "4.14.2+trunk",
65 | "src": {
66 | "url": "https://github.com/ocaml/ocaml/archive/4.14.tar.gz",
67 | "sha256": "0qpibbjizcfp4hk3c95zdkpgfvjxffxasp6z850ia9sw4prxdmfd"
68 | }
69 | },
70 | "ocamlbuild": {
71 | "version": "0.14.2",
72 | "src": {
73 | "url": "https://github.com/ocaml/ocamlbuild/archive/refs/tags/0.14.2.tar.gz",
74 | "sha512": "f568bf10431a1f701e8bd7554dc662400a0d978411038bbad93d44dceab02874490a8a5886a9b44e017347e7949997f13f5c3752f74e1eb5e273d2beb19a75fd"
75 | },
76 | "depends": [
77 | "ocaml"
78 | ]
79 | },
80 | "ocamlfind": {
81 | "version": "1.9.6",
82 | "src": {
83 | "url": "http://download.camlcity.org/download/findlib-1.9.6.tar.gz",
84 | "sha512": "cfaf1872d6ccda548f07d32cc6b90c3aafe136d2aa6539e03143702171ee0199add55269bba894c77115535dc46a5835901a5d7c75768999e72db503bfd83027"
85 | },
86 | "depends": [
87 | "ocaml"
88 | ]
89 | },
90 | "pkg_opam_file": {
91 | "version": "dev",
92 | "src": { "url": "file://." },
93 | "depends": [
94 | "fmt",
95 | "ocaml"
96 | ],
97 | "build-depends": [
98 | "dune"
99 | ]
100 | },
101 | "topkg": {
102 | "version": "1.0.7",
103 | "src": {
104 | "url": "https://erratique.ch/software/topkg/releases/topkg-1.0.7.tbz",
105 | "sha512": "09e59f1759bf4db8471f02d0aefd8db602b44932a291c05c312b1423796e7a15d1598d3c62a0cec7f083eff8e410fac09363533dc4bd2120914bb9664efea535"
106 | },
107 | "depends": [
108 | "ocaml",
109 | "ocamlbuild"
110 | ],
111 | "build-depends": [
112 | "ocamlbuild",
113 | "ocamlfind"
114 | ]
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/onix_core/Utils.ml:
--------------------------------------------------------------------------------
1 | module Option = struct
2 | include Option
3 |
4 | let map_default default f = function
5 | | None -> default
6 | | Some x -> f x
7 |
8 | let or_fail msg t =
9 | match t with
10 | | Some x -> x
11 | | None -> failwith msg
12 |
13 | let if_some f t =
14 | match t with
15 | | Some x -> f x
16 | | None -> ()
17 |
18 | let ( or ) t default =
19 | match t with
20 | | Some x -> x
21 | | None -> default
22 |
23 | let or_else default t =
24 | match t with
25 | | Some x -> x
26 | | None -> default ()
27 | end
28 |
29 | let ( or ) = Option.( or )
30 |
31 | module List = struct
32 | include List
33 |
34 | let is_empty = function
35 | | [] -> true
36 | | _ -> false
37 |
38 | let is_not_empty = function
39 | | [] -> false
40 | | _ -> true
41 |
42 | let is_singleton t =
43 | match t with
44 | | [_] -> true
45 | | _ -> false
46 | end
47 |
48 | module String = struct
49 | include String
50 |
51 | let starts_with_number t =
52 | match String.get t 0 with
53 | | '0' .. '9' -> true
54 | | (exception Invalid_argument _) | _ -> false
55 | end
56 |
57 | module String_set = Set.Make (String)
58 | module String_map = Map.Make (String)
59 |
60 | (* Since 4.14.0 *)
61 | module Out_channel = struct
62 | let with_open openfun s f =
63 | let oc = openfun s in
64 | Fun.protect ~finally:(fun () -> Stdlib.close_out_noerr oc) (fun () -> f oc)
65 |
66 | let with_open_text s f = with_open Stdlib.open_out s f
67 | end
68 |
69 | module In_channel = struct
70 | let with_open openfun s f =
71 | let ic = openfun s in
72 | Fun.protect ~finally:(fun () -> Stdlib.close_in_noerr ic) (fun () -> f ic)
73 |
74 | let with_open_bin s f = with_open Stdlib.open_in_bin s f
75 | let with_open_text s f = with_open Stdlib.open_in s f
76 | end
77 |
78 | module Filesystem = struct
79 | let with_dir path fn =
80 | let ch = Unix.opendir path in
81 | Fun.protect ~finally:(fun () -> Unix.closedir ch) (fun () -> fn ch)
82 |
83 | let list_dir path =
84 | let rec aux acc ch =
85 | match Unix.readdir ch with
86 | | name -> aux (name :: acc) ch
87 | | exception End_of_file -> acc
88 | in
89 | with_dir path (aux [])
90 |
91 | let fold_dir ?(include_hidden = false) f init path =
92 | let rec loop acc ch =
93 | match Unix.readdir ch with
94 | | name when (not include_hidden) && String.starts_with ~prefix:"." name ->
95 | loop acc ch
96 | | name -> loop (f acc name) ch
97 | | exception End_of_file -> Some acc
98 | in
99 | try with_dir path (loop init)
100 | with Unix.Unix_error (Unix.ENOENT, _, _) -> None
101 | end
102 |
103 | module Result = struct
104 | include Result
105 |
106 | let force_with_msg t =
107 | match t with
108 | | Ok x -> x
109 | | Error (`Msg err) -> failwith err
110 | end
111 |
112 | let command_to_string cmd =
113 | let cmd =
114 | List.map
115 | (fun word -> String.concat "" ["\""; String.escaped word; "\""])
116 | cmd
117 | in
118 | String.concat " " cmd
119 |
120 | let print_command cmd = print_endline (command_to_string cmd)
121 |
122 | module Os = struct
123 | let run_command cmd =
124 | let open Bos in
125 | let cmd = Cmd.of_list cmd in
126 | match OS.Cmd.run_status cmd with
127 | | Ok (`Exited 0) -> ()
128 | | Ok (`Exited n) ->
129 | Fmt.failwith "Command terminated with a non-zero code: %d@." n
130 | | Ok (`Signaled n) -> Fmt.failwith "Command terminated by signal: %d@." n
131 | | Error (`Msg err) -> Fmt.failwith "Could not run command: %s" err
132 | end
133 |
134 | module Name_set = OpamPackage.Name.Set
135 | module Name_map = OpamPackage.Name.Map
136 |
--------------------------------------------------------------------------------
/src/onix_lock_nix/Subst_and_patch.ml:
--------------------------------------------------------------------------------
1 | open Prelude
2 |
3 | let print_subst ~nv basename =
4 | let file = OpamFilename.Base.to_string basename in
5 | let file_in = file ^ ".in" in
6 | Logs.debug (fun log ->
7 | log "%s: expanding opam variables in %s, generating %s..."
8 | (OpamPackage.name_to_string nv)
9 | file_in file)
10 |
11 | let run_substs ~pkg_lock_dir ~env ~nv substs =
12 | OpamFilename.in_dir pkg_lock_dir @@ fun () ->
13 | List.fold_left
14 | (fun (errs, oks) f ->
15 | try
16 | print_subst ~nv f;
17 | OpamFilter.expand_interpolations_in_file env f;
18 | (errs, f :: oks)
19 | with e -> ((f, e) :: errs, oks))
20 | ([], []) substs
21 |
22 | (* https://opam.ocaml.org/doc/Manual.html#opamfield-patches *)
23 | (* TODO: check ~env *)
24 | let get_patches ~env:_ opam =
25 | let filtered_patches = OpamFile.OPAM.patches opam in
26 | (* FIXME: Resolve patches formula! *)
27 | List.map fst filtered_patches
28 |
29 | let get_extra_files (opam_details : Opam_utils.opam_details) =
30 | (* FIXME: Check for undeclared in ./files! *)
31 | match OpamFile.OPAM.extra_files opam_details.opam with
32 | | None -> []
33 | | Some extra_files ->
34 | let bad_files, good_files =
35 | Opam_utils.check_extra_files_hashes ~opamfile:opam_details.path
36 | extra_files
37 | in
38 | if List.is_not_empty bad_files then
39 | Logs.warn (fun log ->
40 | log "@[%a: bad hash for extra files:@,%a@]" Opam_utils.pp_package
41 | opam_details.package
42 | (Fmt.list Opam_utils.pp_filename)
43 | bad_files);
44 | let all = List.append bad_files good_files in
45 | if List.is_not_empty all then
46 | Logs.debug (fun log ->
47 | log "@[%a: found extra files:@,%a@]" Opam_utils.pp_package
48 | opam_details.package
49 | (Fmt.list Opam_utils.pp_filename)
50 | all);
51 | all
52 |
53 | let get_subst_and_patches ~env ~pkg_lock_dir
54 | (opam_details : Opam_utils.opam_details) =
55 | let nv = opam_details.package in
56 |
57 | let patches = get_patches ~env opam_details.opam in
58 | let substs = OpamFile.OPAM.substs opam_details.opam in
59 | let extra_files = get_extra_files opam_details in
60 |
61 | let subst_patches, subst_other =
62 | List.partition (fun subst_file -> List.mem subst_file patches) substs
63 | in
64 |
65 | List.iter
66 | (fun base -> Fmt.epr "*** extra_files: %s@." (OpamFilename.to_string base))
67 | extra_files;
68 |
69 | List.iter
70 | (fun base -> Fmt.epr "*** patch: %s@." (OpamFilename.Base.to_string base))
71 | patches;
72 |
73 | List.iter
74 | (fun base -> Fmt.epr "*** subst: %s@." (OpamFilename.Base.to_string base))
75 | substs;
76 |
77 | List.iter
78 | (fun base ->
79 | Fmt.epr "*** subst_patch: %s@." (OpamFilename.Base.to_string base))
80 | subst_patches;
81 |
82 | List.iter
83 | (fun base ->
84 | Fmt.epr "*** subst_other: %s@." (OpamFilename.Base.to_string base))
85 | subst_other;
86 |
87 | (* Expand opam variables in subst_patches. *)
88 | let subst_errs, _subst_oks = run_substs ~pkg_lock_dir ~env ~nv substs in
89 |
90 | (* Report subst errors. *)
91 | if subst_errs <> [] then begin
92 | Logs.err (fun log ->
93 | log "%s: variable expansion failed for files:@.%s"
94 | (OpamPackage.to_string opam_details.package)
95 | (OpamStd.Format.itemize
96 | (fun (b, err) ->
97 | Printf.sprintf "%s.in: %s"
98 | (OpamFilename.Base.to_string b)
99 | (Printexc.to_string err))
100 | subst_errs));
101 | exit 1
102 | end;
103 |
104 | (subst_other, patches)
105 |
--------------------------------------------------------------------------------
/nix/experiments/test-subst.nix:
--------------------------------------------------------------------------------
1 | let
2 | nixpkgs = import { };
3 | ocaml-version = "4.14.0";
4 | vars = import ./vars.nix {
5 | platform = nixpkgs.hostPlatform;
6 | inherit ocaml-version;
7 | };
8 |
9 | subst = import ./subst.nix;
10 |
11 | pkgs = {
12 | "ocaml-config-2" = {
13 | name = "ocaml-config";
14 | version = "2";
15 | opamfile = "/packages/ocaml-config/opam";
16 | prefix = "/packages/ocaml-config";
17 | };
18 | "ocaml-base-compiler" = {
19 | name = "ocaml-base-compiler";
20 | version = "4.14.0";
21 | opamfile = "/packages/ocaml-base-compiler/opam";
22 | prefix = "/packages/ocaml-base-compiler";
23 | };
24 | "bos" = {
25 | name = "bos";
26 | version = "0.2.1";
27 | opamfile = "/packages/bos/opam";
28 | prefix = "/packages/bos";
29 | };
30 | "cmdliner" = {
31 | name = "cmdliner";
32 | version = "1.1.1";
33 | opamfile = "/packages/cmdliner/opam";
34 | prefix = "/packages/cmdliner";
35 | };
36 | "dune" = {
37 | name = "dune";
38 | version = "3.1.1";
39 | opamfile = "/packages/dune/opam";
40 | prefix = "/packages/dune";
41 | };
42 | "easy-format" = {
43 | name = "easy-format-1";
44 | version = "1.3.2";
45 | opamfile = "/packages/easy-format-1/opam";
46 | prefix = "/packages/easy-format-1";
47 | };
48 | "fpath" = {
49 | name = "fpath";
50 | version = "0.7.3";
51 | opamfile = "/packages/fpath/opam";
52 | prefix = "/packages/fpath";
53 | };
54 | "ocaml" = {
55 | name = "ocaml";
56 | version = "4.14.0";
57 | opamfile = "/packages/ocaml/opam";
58 | prefix = "/packages/ocaml";
59 | };
60 | "opam-0install" = {
61 | name = "opam-0install";
62 | version = "0.4.3";
63 | opamfile = "/packages/opam-0install/opam";
64 | prefix = "/packages/opam-0install";
65 | };
66 | "options" = {
67 | name = "options";
68 | version = "dev";
69 | opamfile = "/packages/options/opam";
70 | prefix = "/packages/options";
71 | };
72 | "uri" = {
73 | name = "uri";
74 | version = "4.2.0";
75 | opamfile = "/packages/uri/opam";
76 | prefix = "/packages/uri";
77 | };
78 | "yojson" = {
79 | name = "yojson";
80 | version = "1.7.0";
81 | opamfile = "/packages/yojson/opam";
82 | prefix = "/packages/yojson";
83 | };
84 | };
85 |
86 | self = {
87 | name = "onix-example";
88 | version = "dev";
89 | opamfile = "/packages/onix-exmple/opam";
90 | prefix = "/packages/onix-example";
91 | };
92 |
93 | build-dir = "/build";
94 |
95 | resolveStr = var-str:
96 | vars.resolve { inherit build-dir self pkgs ocaml-version; }
97 | (subst.process-var (subst.split-full-var-cond var-str));
98 |
99 | resolve = full-var:
100 | vars.resolve { inherit build-dir self pkgs ocaml-version; } full-var;
101 |
102 | test-vars = {
103 | # global
104 | "name" = resolve "name";
105 | "os" = resolve "os";
106 | "make" = resolve "make";
107 |
108 | # not-installed
109 | "foo:name" = resolve "foo:name";
110 | "foo:installed" = resolve "foo:installed";
111 | "foo:enable" = resolve "foo:enable";
112 |
113 | # self
114 | "_:opamfile" = resolve "_:opamfile";
115 | "opamfile" = resolve "opamfile";
116 | "lib" = resolve "lib";
117 |
118 | # pkg scope
119 | "uri:enable" = resolve "uri:enable";
120 | "yojson:installed" = resolve "yojson:installed";
121 | "yojson:lib" = resolve "yojson:lib";
122 | };
123 |
124 | test-content = ''
125 | description = "OCaml Secondary Compiler"
126 | version = "%{ocaml:version}%"
127 | yojson-bin = "%{yojson:prefix}%/bin"
128 | os = "%{os}%"
129 | lib = "%{lib}%"
130 | self-opamfile = "%{_:opamfile}%"
131 | cond-with-true = "%{yojson:installed?with-json}%"
132 | cond-with-false = "%{foo:installed?with-foo:without-foo}%"
133 | '';
134 | in {
135 | inherit test-content resolve;
136 | inherit (subst) subst-file-content;
137 | }
138 |
--------------------------------------------------------------------------------
/src/onix_core/Solver_context.ml:
--------------------------------------------------------------------------------
1 | (* Based on https://github.com/ocaml-opam/opam-0install-solver/blob/master/lib/dir_context.ml *)
2 |
3 | module Resolvers = struct
4 | let resolve_available_current_system = Scope.resolve_global_host
5 |
6 | let resolve_filter_deps_current_system ~test ~doc ~dev_setup opam_pkg =
7 | Scope.resolve_many
8 | [
9 | Scope.resolve_opam_pkg opam_pkg;
10 | Scope.resolve_global_host;
11 | Scope.resolve_dep ~post:true ~test ~doc ~dev_setup;
12 | ]
13 | end
14 |
15 | type t = {
16 | repo : Repo.t;
17 | package_dep_vars : Opam_utils.package_dep_vars;
18 | fixed_opam_details : Opam_utils.opam_details OpamPackage.Name.Map.t;
19 | constraints : OpamFormula.version_constraint OpamTypes.name_map;
20 | prefer_oldest : bool;
21 | }
22 |
23 | type rejection =
24 | | UserConstraint of OpamFormula.atom
25 | | Unavailable
26 |
27 | let pp_rejection f = function
28 | | UserConstraint x ->
29 | Fmt.pf f "Rejected by user-specified constraint %s"
30 | (OpamFormula.string_of_atom x)
31 | | Unavailable -> Fmt.string f "Availability condition not satisfied"
32 |
33 | open struct
34 | (* Availability only seems to require os, ocaml-version, opam-version. *)
35 | let check_available ~pkg available =
36 | let n = OpamPackage.name pkg in
37 | let v = OpamPackage.version pkg in
38 | if OpamPackage.Name.equal Opam_utils.ocaml_system_name n then
39 | Nix_utils.check_ocaml_packages_version v
40 | else
41 | let env = Resolvers.resolve_available_current_system in
42 | OpamFilter.eval_to_bool ~default:false env available
43 |
44 | let check_user_restrictions ~version name constraints =
45 | match OpamPackage.Name.Map.find_opt name constraints with
46 | | Some test
47 | when not
48 | (OpamFormula.check_version_formula (OpamFormula.Atom test) version)
49 | -> Some (UserConstraint (name, Some test))
50 | | _ -> None
51 |
52 | let select_versions { repo; constraints; _ } name v =
53 | match check_user_restrictions ~version:v name constraints with
54 | | Some rejection -> (v, Error rejection)
55 | | None ->
56 | let pkg = OpamPackage.create name v in
57 | let opam = Repo.read_opam repo pkg in
58 | let available = OpamFile.OPAM.available opam in
59 | if check_available ~pkg available then (v, Ok opam)
60 | else (v, Error Unavailable)
61 | end
62 |
63 | let make ?(prefer_oldest = false)
64 | ?(fixed_opam_details = OpamPackage.Name.Map.empty) ~constraints
65 | ~package_dep_vars ~repo () =
66 | { repo; package_dep_vars; fixed_opam_details; constraints; prefer_oldest }
67 |
68 | let version_compare t v1 v2 =
69 | if t.prefer_oldest then OpamPackage.Version.compare v1 v2
70 | else OpamPackage.Version.compare v2 v1
71 |
72 | let user_restrictions t name = OpamPackage.Name.Map.find_opt name t.constraints
73 |
74 | let candidates t name =
75 | match OpamPackage.Name.Map.find_opt name t.fixed_opam_details with
76 | (* It's a fixed user-provided package. *)
77 | | Some { Opam_utils.package; opam; _ } ->
78 | [(OpamPackage.version package, Ok opam)]
79 | (* Lookup in the repository. *)
80 | | None -> (
81 | match Repo.read_package_versions t.repo name with
82 | | None ->
83 | Logs.warn (fun log ->
84 | log "Could not find package %a in repository"
85 | Opam_utils.pp_package_name name);
86 | []
87 | | Some versions ->
88 | versions
89 | |> List.sort (version_compare t)
90 | |> List.map (select_versions t name))
91 |
92 | (*
93 | Variable lookup frequency for a small sample project:
94 |
95 | 1 arch
96 | 4139 build
97 | 137 dev
98 | 80 opam-version
99 | 41 os
100 | 1 os-distribution
101 | 2572 post
102 | 672 version
103 | 224 with-doc
104 | 2339 with-test
105 | 12 with-dev-setup *)
106 | let filter_deps t pkg depends_formula =
107 | let name = OpamPackage.name pkg in
108 | let { Opam_utils.test; doc; dev_setup } =
109 | Opam_utils.eval_package_dep_vars name t.package_dep_vars
110 | in
111 | let env var =
112 | let contents =
113 | Resolvers.resolve_filter_deps_current_system pkg ~test ~doc ~dev_setup var
114 | in
115 | (* let nv = OpamPackage.to_string pkg in
116 | Opam_utils.debug_var ~scope:("filter_deps/" ^ nv) var contents; *)
117 | contents
118 | in
119 | OpamFilter.filter_formula ~default:false env depends_formula
120 |
--------------------------------------------------------------------------------
/src/onix_core/Solver.ml:
--------------------------------------------------------------------------------
1 | module Opam_0install_solver = Opam_0install.Solver.Make (Solver_context)
2 |
3 | let solve ?(resolutions = []) ~repository_urls ~with_test ~with_doc
4 | ~with_dev_setup opam_file_paths =
5 | let resolutions = Resolutions.make resolutions in
6 | Resolutions.debug resolutions;
7 |
8 | (* Packages with .opam files at the root of the project. *)
9 | let root_opam_details = Opam_utils.find_root_packages opam_file_paths in
10 |
11 | (* Apply provided dep vars to the root packages. *)
12 | let package_dep_vars =
13 | OpamPackage.Name.Map.map
14 | (fun _ ->
15 | {
16 | Opam_utils.test = with_test;
17 | doc = with_doc;
18 | dev_setup = with_dev_setup;
19 | })
20 | root_opam_details
21 | in
22 |
23 | (* Pin-depends packages found in root_packages. *)
24 | let pin_opam_details =
25 | Pin_depends.collect_from_opam_files root_opam_details
26 | in
27 |
28 | (* Packages provided by the project (roots + pins). *)
29 | let fixed_opam_details =
30 | OpamPackage.Name.Map.union
31 | (fun _local _pin ->
32 | failwith "Locally defined packages are not allowed in pin-depends")
33 | root_opam_details pin_opam_details
34 | in
35 |
36 | (* Packages to start solve with (roots + user-provided resolutions). *)
37 | let target_package_names =
38 | List.append
39 | (Resolutions.all resolutions)
40 | (OpamPackage.Name.Map.keys root_opam_details)
41 | in
42 |
43 | let repository_dir, resolved_repository_urls =
44 | Logs.info (fun log -> log "Fetching opam repositories...");
45 | Nix_utils.resolve_repos repository_urls
46 | in
47 | let repo = Repo.make ~root:repository_dir ~urls:resolved_repository_urls in
48 | let constraints = Resolutions.constraints resolutions in
49 |
50 | let context =
51 | Solver_context.make ~repo ~fixed_opam_details ~constraints ~package_dep_vars
52 | ()
53 | in
54 |
55 | let get_opam_details package =
56 | let name = OpamPackage.name package in
57 | try OpamPackage.Name.Map.find name fixed_opam_details
58 | with Not_found ->
59 | let opam = Repo.read_opam repo package in
60 | let path = Repo.get_opam_filename repo package in
61 | { package; path; opam }
62 | in
63 |
64 | Logs.info (fun log -> log "Solving dependencies...");
65 | Logs.info (fun log ->
66 | log "Root packages: %a"
67 | Fmt.(seq ~sep:Fmt.sp Opam_utils.pp_package_name)
68 | (root_opam_details |> OpamPackage.Name.Map.to_seq |> Seq.map fst));
69 | Logs.debug (fun log ->
70 | log "Fixed packages: %a"
71 | Fmt.(seq ~sep:Fmt.sp Opam_utils.pp_package_name)
72 | (fixed_opam_details |> OpamPackage.Name.Map.to_seq |> Seq.map fst));
73 | Logs.debug (fun log ->
74 | log "Target packages: %a"
75 | Fmt.(list ~sep:Fmt.sp Opam_utils.pp_package_name)
76 | target_package_names);
77 | match Opam_0install_solver.solve context target_package_names with
78 | | Ok selections ->
79 | let packages =
80 | selections
81 | |> Opam_0install_solver.packages_of_result
82 | |> List.fold_left
83 | (fun acc pkg ->
84 | OpamPackage.Name.Map.add (OpamPackage.name pkg) pkg acc)
85 | OpamPackage.Name.Map.empty
86 | in
87 | Fmt.pr "Resolved %d packages:@." (OpamPackage.Name.Map.cardinal packages);
88 | OpamPackage.Name.Map.iter
89 | (fun _ -> Fmt.pr "- %a@." Opam_utils.pp_package)
90 | packages;
91 | let installed name = OpamPackage.Name.Map.mem name packages in
92 | packages
93 | |> OpamPackage.Name.Map.filter_map (fun pkg_name pkg ->
94 | match
95 | let opam_details = get_opam_details pkg in
96 | let {
97 | Opam_utils.test = with_test;
98 | doc = with_doc;
99 | dev_setup = with_dev_setup;
100 | } =
101 | Opam_utils.eval_package_dep_vars pkg_name package_dep_vars
102 | in
103 | Lock_pkg.of_opam ~installed ~with_test ~with_doc ~with_dev_setup
104 | opam_details
105 | with
106 | | None ->
107 | Logs.warn (fun log ->
108 | log "Missing url for %a, ignoring..." Opam_utils.pp_package pkg);
109 | None
110 | | some -> some)
111 | |> OpamPackage.Name.Map.values
112 | |> Lock_file.make ~repository_urls:resolved_repository_urls
113 | | Error err ->
114 | prerr_endline (Opam_0install_solver.diagnostics ~verbose:false err);
115 | exit 2
116 |
--------------------------------------------------------------------------------
/src/onix_core/Opam_actions.ml:
--------------------------------------------------------------------------------
1 | open Utils
2 |
3 | let mk_dep_vars ~with_test ~with_doc ~with_dev_setup =
4 | let open OpamVariable in
5 | Map.of_list
6 | [
7 | (of_string "with-test", Some (B with_test));
8 | (of_string "with-doc", Some (B with_doc));
9 | (of_string "with-dev-setup", Some (B with_dev_setup));
10 | ]
11 |
12 | let resolve_actions =
13 | let build_dir = Sys.getcwd () in
14 | fun ?(local = OpamVariable.Map.empty) pkg_scope ->
15 | Scope.resolve_many
16 | [
17 | Scope.resolve_stdenv_host;
18 | Scope.resolve_local local;
19 | Scope.resolve_config pkg_scope;
20 | Scope.resolve_global_host;
21 | Scope.resolve_pkg ~build_dir pkg_scope;
22 | ]
23 |
24 | module Patch = struct
25 | let copy_extra_files ~opamfile ~build_dir extra_files =
26 | let bad_hash =
27 | Stdlib.List.filter_map
28 | (fun (basename, hash) ->
29 | let src = Opam_utils.make_opam_files_path ~opamfile basename in
30 | if OpamHash.check_file (OpamFilename.to_string src) hash then (
31 | let dst = OpamFilename.create build_dir basename in
32 | Logs.debug (fun log ->
33 | log "Opam_actions.copy_extra_files: %a -> %a"
34 | Opam_utils.pp_filename src Opam_utils.pp_filename dst);
35 | OpamFilename.copy ~src ~dst;
36 | None)
37 | else Some src)
38 | extra_files
39 | in
40 | if List.is_not_empty bad_hash then
41 | Fmt.failwith "Bad hash for %s"
42 | (OpamStd.Format.itemize OpamFilename.to_string bad_hash)
43 |
44 | let copy_undeclared_files ~opamfile ~build_dir () =
45 | let ( > ) = OpamFilename.Op.( / ) in
46 | let files_dir = OpamFilename.dirname opamfile > "files" in
47 | List.iter
48 | (fun src ->
49 | let base = OpamFilename.basename src in
50 | let dst = OpamFilename.create build_dir base in
51 | Logs.debug (fun log ->
52 | log "Opam_actions.copy_undeclared_files: %a -> %a"
53 | Opam_utils.pp_filename src Opam_utils.pp_filename dst);
54 | OpamFilename.copy ~src ~dst)
55 | (OpamFilename.files files_dir)
56 |
57 | (* TODO: implement extra file fetching via lock-file?:
58 | - https://github.com/ocaml/opam/blob/e36650b3007e013cfb5b6bb7ed769a349af3ee97/src/client/opamAction.ml#L455 *)
59 | let run (pkg_scope : Scope.t) =
60 | let opamfile = OpamFilename.of_string pkg_scope.self.opamfile in
61 | let opam = Opam_utils.read_opam opamfile in
62 | let () =
63 | let build_dir = OpamFilename.Dir.of_string (Sys.getcwd ()) in
64 | match OpamFile.OPAM.extra_files opam with
65 | | None ->
66 | Logs.debug (fun log ->
67 | log "No extra files in opam file, checking for undeclared files...");
68 | copy_undeclared_files ~opamfile ~build_dir ()
69 | | Some extra_files -> copy_extra_files ~opamfile ~build_dir extra_files
70 | in
71 | let resolve = resolve_actions pkg_scope in
72 | let cwd = OpamFilename.Dir.of_string (Sys.getcwd ()) in
73 | let pkg = OpamPackage.create pkg_scope.self.name pkg_scope.self.version in
74 | OpamAction.prepare_package_build resolve opam pkg cwd
75 | |> Option.if_some raise
76 | end
77 |
78 | let patch = Patch.run
79 |
80 | let build ~with_test ~with_doc ~with_dev_setup (pkg_scope : Scope.t) =
81 | let opam =
82 | Opam_utils.read_opam (OpamFilename.of_string pkg_scope.self.opamfile)
83 | in
84 | let resolve_with_dep_vars =
85 | resolve_actions
86 | ~local:(mk_dep_vars ~with_test ~with_doc ~with_dev_setup)
87 | pkg_scope
88 | in
89 | let resolve =
90 | resolve_actions
91 | ~local:(mk_dep_vars ~with_test ~with_doc ~with_dev_setup)
92 | pkg_scope
93 | in
94 | let commands =
95 | (OpamFilter.commands resolve_with_dep_vars (OpamFile.OPAM.build opam)
96 | @ (if with_test then
97 | OpamFilter.commands resolve (OpamFile.OPAM.run_test opam)
98 | else [])
99 | @
100 | if with_doc then
101 | OpamFilter.commands resolve (OpamFile.OPAM.deprecated_build_doc opam)
102 | else [])
103 | |> List.filter List.is_not_empty
104 | in
105 | commands
106 |
107 | module Install = struct
108 | let run ~with_test ~with_doc ~with_dev_setup (pkg_scope : Scope.t) =
109 | let opam =
110 | Opam_utils.read_opam (OpamFilename.of_string pkg_scope.self.opamfile)
111 | in
112 | let resolve_with_dep_vars =
113 | resolve_actions
114 | ~local:(mk_dep_vars ~with_test ~with_doc ~with_dev_setup)
115 | pkg_scope
116 | in
117 | OpamFilter.commands resolve_with_dep_vars (OpamFile.OPAM.install opam)
118 | |> List.filter List.is_not_empty
119 | end
120 |
121 | let install = Install.run
122 | let all ~with_test ~with_doc ~with_dev_setup (pkg_scope : Scope.t) = ()
123 |
--------------------------------------------------------------------------------
/src/onix_lock_json/Pp.ml:
--------------------------------------------------------------------------------
1 | open Onix_core
2 | open Onix_core.Utils
3 |
4 | (* Lock pkg printers *)
5 |
6 | let pp_name_quoted formatter name =
7 | let name = OpamPackage.Name.to_string name in
8 | Fmt.Dump.string formatter name
9 |
10 | let pp_hash f (kind, hash) =
11 | match kind with
12 | | `SHA256 -> Fmt.pf f "\"sha256\": %S" hash
13 | | `SHA512 -> Fmt.pf f "\"sha512\": %S" hash
14 | | `MD5 -> Fmt.pf f "\"md5\": %S" hash
15 |
16 | let pp_http_src f ({ url; hash } : Lock_pkg.http_src) =
17 | Fmt.pf f "\"url\": %a,@,%a" (Fmt.quote Opam_utils.pp_url) url pp_hash hash
18 |
19 | let pp_src f (t : Lock_pkg.t) =
20 | if Opam_utils.Opam_details.check_has_absolute_path t.opam_details then
21 | (* Absolute path: use src. *)
22 | match t.src with
23 | | None -> ()
24 | | Some (Git { url; rev }) ->
25 | Fmt.pf f ",@,@[\"src\": {@,\"url\": \"git+%s\",@,\"rev\": %S@]@,}" url
26 | rev
27 | (* MD5 hashes are not supported by Nix fetchers. Fetch without hash.
28 | This normally would not happen as we try to prefetch_src_if_md5. *)
29 | | Some (Http { url; hash = `MD5, _ }) ->
30 | Fmt.invalid_arg "Unexpected md5 hash: package=%a url=%a"
31 | Opam_utils.pp_package t.opam_details.package Opam_utils.pp_url url
32 | | Some (Http hsrc) ->
33 | Fmt.pf f ",@,@[\"src\": {@,%a@]@,}" pp_http_src hsrc
34 | else
35 | (* Relative path: use file scheme. *)
36 | let path =
37 | let opam_path = t.opam_details.Opam_utils.path in
38 | let path = OpamFilename.(Dir.to_string (dirname opam_path)) in
39 | match path with
40 | | "./." | "./" -> "."
41 | (* Use "." for ./opam/$pkg.opam. *)
42 | | "opam" -> "."
43 | | _ -> path
44 | in
45 | Fmt.pf f ",@,\"src\": { \"url\": \"file://%s\" }" path
46 |
47 | let pp_extra_src =
48 | let pp_item f (name, hsrc) =
49 | Fmt.pf f "@[%S: {@,%a@]@,}" name pp_http_src hsrc
50 | in
51 | fun f (srcs : (string * Lock_pkg.http_src) list) ->
52 | if List.is_empty srcs then ()
53 | else begin
54 | Fmt.pf f ",@,@[\"src-extra\": {@ %a@]@ }"
55 | (Fmt.iter ~sep:Fmt.comma List.iter pp_item)
56 | srcs
57 | end
58 |
59 | let pp_depends =
60 | let pp_deps = Fmt.iter ~sep:Fmt.comma Name_set.iter pp_name_quoted in
61 | fun key f deps ->
62 | if Name_set.is_empty deps then ()
63 | else Fmt.pf f ",@,@[%S: [@ %a@]@ ]" key pp_deps deps
64 |
65 | let pp_depexts =
66 | let pp_deps = Fmt.iter ~sep:Fmt.comma String_set.iter Fmt.Dump.string in
67 | fun f deps ->
68 | if String_set.is_empty deps then ()
69 | else Fmt.pf f ",@,@[\"depexts\": [@ %a@]@ ]" pp_deps deps
70 |
71 | let pp_pkg_vars f vars =
72 | match vars with
73 | | { Opam_utils.test = false; doc = false; dev_setup = false } -> ()
74 | | { Opam_utils.test; doc; dev_setup } ->
75 | let vars_str =
76 | String.concat ", "
77 | (List.filter
78 | (fun str -> String.length str > 0)
79 | [
80 | (if test then "\"with-test\": true" else "");
81 | (if doc then "\"with-doc\": true" else "");
82 | (if dev_setup then "\"with-dev-setup\": true" else "");
83 | ])
84 | in
85 | Fmt.pf f ",@,@[\"vars\": { %s }@]" vars_str
86 |
87 | let pp_pkg ppf (t : Lock_pkg.t) =
88 | Fmt.pf ppf "\"version\": %S%a%a%a%a%a%a%a%a%a"
89 | (OpamPackage.version_to_string t.opam_details.package)
90 | pp_src t pp_extra_src t.extra_src (pp_depends "depends") t.depends
91 | (pp_depends "build-depends")
92 | t.depends_build
93 | (pp_depends "test-depends")
94 | t.depends_test (pp_depends "doc-depends") t.depends_doc
95 | (pp_depends "dev-setup-depends")
96 | t.depends_dev_setup pp_depexts
97 | (String_set.union t.depexts_unknown t.depexts_nix)
98 | pp_pkg_vars t.vars
99 |
100 | (* Lock file printers *)
101 |
102 | let pp_version f version = Fmt.pf f "\"version\": %S" version
103 |
104 | let pp_repos =
105 | let pp_repo_url ppf repo_url =
106 | match repo_url.OpamUrl.hash with
107 | | None ->
108 | Fmt.invalid_arg "Repo URI without fragment: %a" Opam_utils.pp_url repo_url
109 | | Some rev ->
110 | Fmt.pf ppf "@[{@ \"url\": %a,@ \"rev\": %S@]@,}"
111 | (Fmt.quote Opam_utils.pp_url)
112 | { repo_url with OpamUrl.hash = None }
113 | rev
114 | in
115 | fun f repos ->
116 | Fmt.pf f "@[\"repositories\": [@,%a@]@,]"
117 | (Fmt.list ~sep:Fmt.comma pp_repo_url)
118 | repos
119 |
120 | let pp_packages f deps =
121 | let pp_pkg fmt pkg =
122 | Fmt.pf fmt "@[%a: {@ %a@]@,}" pp_name_quoted (Lock_pkg.name pkg) pp_pkg
123 | pkg
124 | in
125 | let pp_list = Fmt.iter ~sep:Fmt.comma List.iter pp_pkg in
126 | Fmt.pf f "@[\"packages\" : {@,%a@]@,}" (Fmt.hvbox pp_list) deps
127 |
128 | let pp fmt (t : Lock_file.t) =
129 | Fmt.pf fmt "{@[@,%a,@,%a,@,%a@]@,}@." pp_version Lib.version pp_repos
130 | t.repository_urls pp_packages t.packages
131 |
--------------------------------------------------------------------------------
/tests/opam-roots/pkg_opam_dir/onix-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.5",
3 | "repositories": [
4 | {
5 | "url": "https://github.com/ocaml/opam-repository.git",
6 | "rev": "9e6ae0a9398cf087ec2b3fbcd62cb6072ccf95ce"
7 | }
8 | ],
9 | "packages" : {
10 | "aaa": {
11 | "version": "dev",
12 | "src": { "url": "file://." },
13 | "depends": [
14 | "fmt",
15 | "ocaml"
16 | ],
17 | "build-depends": [
18 | "dune"
19 | ]
20 | },
21 | "base-threads": {
22 | "version": "base"
23 | },
24 | "base-unix": {
25 | "version": "base"
26 | },
27 | "bbb": {
28 | "version": "dev",
29 | "src": { "url": "file://." },
30 | "depends": [
31 | "ocaml",
32 | "yojson"
33 | ],
34 | "build-depends": [
35 | "dune"
36 | ]
37 | },
38 | "cppo": {
39 | "version": "1.6.9",
40 | "src": {
41 | "url": "https://github.com/ocaml-community/cppo/archive/v1.6.9.tar.gz",
42 | "sha512": "26ff5a7b7f38c460661974b23ca190f0feae3a99f1974e0fd12ccf08745bd7d91b7bc168c70a5385b837bfff9530e0e4e41cf269f23dd8cf16ca658008244b44"
43 | },
44 | "depends": [
45 | "base-unix",
46 | "dune",
47 | "ocaml"
48 | ],
49 | "build-depends": [
50 | "dune"
51 | ]
52 | },
53 | "dune": {
54 | "version": "3.10.0",
55 | "src": {
56 | "url": "https://github.com/ocaml/dune/releases/download/3.10.0/dune-3.10.0.tbz",
57 | "sha256": "9ff03384a98a8df79852cc674f0b4738ba8aec17029b6e2eeb514f895e710355"
58 | },
59 | "depends": [
60 | "base-threads",
61 | "base-unix",
62 | "ocaml"
63 | ]
64 | },
65 | "fmt": {
66 | "version": "0.9.0",
67 | "src": {
68 | "url": "https://erratique.ch/software/fmt/releases/fmt-0.9.0.tbz",
69 | "sha512": "66cf4b8bb92232a091dfda5e94d1c178486a358cdc34b1eec516d48ea5acb6209c0dfcb416f0c516c50ddbddb3c94549a45e4a6d5c5fd1c81d3374dec823a83b"
70 | },
71 | "depends": [
72 | "base-unix",
73 | "ocaml"
74 | ],
75 | "build-depends": [
76 | "ocamlbuild",
77 | "ocamlfind",
78 | "topkg"
79 | ]
80 | },
81 | "ocaml": {
82 | "version": "4.14.2",
83 | "depends": [
84 | "ocaml-config",
85 | "ocaml-variants"
86 | ],
87 | "build-depends": [
88 | "ocaml-variants"
89 | ]
90 | },
91 | "ocaml-config": {
92 | "version": "2",
93 | "depends": [
94 | "ocaml-variants"
95 | ],
96 | "build-depends": [
97 | "ocaml-variants"
98 | ]
99 | },
100 | "ocaml-variants": {
101 | "version": "4.14.2+trunk",
102 | "src": {
103 | "url": "https://github.com/ocaml/ocaml/archive/4.14.tar.gz",
104 | "sha256": "0qpibbjizcfp4hk3c95zdkpgfvjxffxasp6z850ia9sw4prxdmfd"
105 | }
106 | },
107 | "ocamlbuild": {
108 | "version": "0.14.2",
109 | "src": {
110 | "url": "https://github.com/ocaml/ocamlbuild/archive/refs/tags/0.14.2.tar.gz",
111 | "sha512": "f568bf10431a1f701e8bd7554dc662400a0d978411038bbad93d44dceab02874490a8a5886a9b44e017347e7949997f13f5c3752f74e1eb5e273d2beb19a75fd"
112 | },
113 | "depends": [
114 | "ocaml"
115 | ]
116 | },
117 | "ocamlfind": {
118 | "version": "1.9.6",
119 | "src": {
120 | "url": "http://download.camlcity.org/download/findlib-1.9.6.tar.gz",
121 | "sha512": "cfaf1872d6ccda548f07d32cc6b90c3aafe136d2aa6539e03143702171ee0199add55269bba894c77115535dc46a5835901a5d7c75768999e72db503bfd83027"
122 | },
123 | "depends": [
124 | "ocaml"
125 | ]
126 | },
127 | "seq": {
128 | "version": "base",
129 | "depends": [
130 | "ocaml"
131 | ]
132 | },
133 | "topkg": {
134 | "version": "1.0.7",
135 | "src": {
136 | "url": "https://erratique.ch/software/topkg/releases/topkg-1.0.7.tbz",
137 | "sha512": "09e59f1759bf4db8471f02d0aefd8db602b44932a291c05c312b1423796e7a15d1598d3c62a0cec7f083eff8e410fac09363533dc4bd2120914bb9664efea535"
138 | },
139 | "depends": [
140 | "ocaml",
141 | "ocamlbuild"
142 | ],
143 | "build-depends": [
144 | "ocamlbuild",
145 | "ocamlfind"
146 | ]
147 | },
148 | "yojson": {
149 | "version": "2.1.0",
150 | "src": {
151 | "url": "https://github.com/ocaml-community/yojson/releases/download/2.1.0/yojson-2.1.0.tbz",
152 | "sha256": "9fcb1ff2db58ab259f9228796b0ada4794eae97177b1833371380c4e4f90b15d"
153 | },
154 | "depends": [
155 | "dune",
156 | "ocaml",
157 | "seq"
158 | ],
159 | "build-depends": [
160 | "cppo",
161 | "dune"
162 | ]
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/onix_lock_nix/Nix_pkg.ml:
--------------------------------------------------------------------------------
1 | open Prelude
2 |
3 | let resolve_commands =
4 | let jobs = "%{jobs}%" in
5 | let user = "%{user}%" in
6 | let group = "%{group}%" in
7 | let build_dir = "." in
8 | fun scope ->
9 | Scope.resolve_many
10 | [
11 | Scope.resolve_config scope;
12 | Scope.resolve_global ~jobs ~user ~group;
13 | Scope.resolve_pkg ~build_dir scope;
14 | ]
15 |
16 | let resolve_depends ?(build = false) ?(test = false) ?(doc = false)
17 | ?(dev_setup = false) pkg =
18 | Scope.resolve_many
19 | [
20 | Scope.resolve_stdenv_host;
21 | Scope.resolve_opam_pkg pkg;
22 | Scope.resolve_global_host;
23 | Scope.resolve_dep ~build ~test ~doc ~dev_setup;
24 | ]
25 |
26 | (* FIXME this shouldn't use hosts' vars! *)
27 | let resolve_subst_and_patch =
28 | let build_dir = Sys.getcwd () in
29 | fun ?(local = OpamVariable.Map.empty) scope ->
30 | Scope.resolve_many
31 | [
32 | Scope.resolve_stdenv_host;
33 | Scope.resolve_local local;
34 | Scope.resolve_config scope;
35 | Scope.resolve_global_host;
36 | Scope.resolve_pkg ~build_dir scope;
37 | ]
38 |
39 | let scope_for_lock_pkg ~ocaml_version (lock_pkg : Lock_pkg.t) =
40 | let name = OpamPackage.name lock_pkg.opam_details.package in
41 | let version = OpamPackage.version lock_pkg.opam_details.package in
42 | let dep_names = Name_set.union lock_pkg.depends lock_pkg.depends_build in
43 | let deps =
44 | Name_set.fold
45 | (fun name acc ->
46 | let prefix =
47 | String.concat "" ["%{"; OpamPackage.Name.to_string name; "}%"]
48 | in
49 | let pkg =
50 | Scope.make_pkg ~name
51 | ~version:(OpamPackage.Version.of_string "version_todo")
52 | ~opamfile:
53 | (Onix_core.Paths.lib ~pkg_name:name ~ocaml_version prefix
54 | ^ "/opam")
55 | ~prefix
56 | in
57 | Name_map.add name pkg acc)
58 | dep_names Name_map.empty
59 | in
60 | let self =
61 | Scope.make_pkg ~name ~version
62 | ~opamfile:(OpamFilename.to_string lock_pkg.opam_details.path)
63 | ~prefix:"%{prefix}%"
64 | in
65 |
66 | Scope.make ~deps ~ocaml_version self
67 |
68 | type t = {
69 | lock_pkg : Lock_pkg.t;
70 | scope : Scope.t;
71 | opam_details : Opam_utils.Opam_details.t;
72 | inputs : String_set.t;
73 | build : (string list * OpamTypes.filter option) list;
74 | install : (string list * OpamTypes.filter option) list;
75 | extra_files : OpamFilename.t list;
76 | patches : OpamFilename.Base.t list;
77 | substs : OpamFilename.Base.t list;
78 | }
79 |
80 | let add_nixpkgs_prefix_to_depexts (lock_pkg : Lock_pkg.t) =
81 | let depexts_nix =
82 | String_set.map (fun name -> "nixpkgs." ^ name) lock_pkg.depexts_nix
83 | in
84 | { lock_pkg with depexts_nix }
85 |
86 | let get_propagated_build_inputs (lock_pkg : Lock_pkg.t) =
87 | List.fold_left
88 | (fun acc names ->
89 | Name_set.fold
90 | (fun name acc -> String_set.add (OpamPackage.Name.to_string name) acc)
91 | names acc)
92 | lock_pkg.depexts_nix
93 | [lock_pkg.depends; lock_pkg.depends_build]
94 |
95 | let get_propagated_native_build_inputs (lock_pkg : Lock_pkg.t) =
96 | List.fold_left
97 | (fun acc names ->
98 | Name_set.fold
99 | (fun name acc -> String_set.add (OpamPackage.Name.to_string name) acc)
100 | names acc)
101 | lock_pkg.depexts_nix
102 | [
103 | lock_pkg.depends;
104 | lock_pkg.depends_build;
105 | lock_pkg.depends_test;
106 | lock_pkg.depends_doc;
107 | lock_pkg.depends_dev_setup;
108 | ]
109 |
110 | let default_install_commands =
111 | [["mkdir"; "-p"; "$out/lib/ocaml/4.14.0/site-lib"]]
112 |
113 | let default_configure_commands =
114 | [["export"; (* FIXME *) "OCAMLFIND_DESTDIR=$out/lib/ocaml/4.14.0/site-lib"]]
115 |
116 | let default_inputs = String_set.of_list ["nixpkgs"; "onixpkgs"; "onix"]
117 |
118 | (* Ex: "cp" "${./ocaml-config.install}" ./ocaml-config.install *)
119 | let mk_copy_files_commands filenames =
120 | List.map
121 | (fun filename ->
122 | let basename = OpamFilename.(Base.to_string (basename filename)) in
123 | ["cp"; String.concat "" ["${./"; basename; "}"]; basename])
124 | filenames
125 |
126 | (* TODO: check ~with_x args *)
127 | let of_lock_pkg ~ocaml_version ~with_test:_ ~with_doc:_ ~with_dev_setup:_
128 | (lock_pkg : Lock_pkg.t) =
129 | let opam = lock_pkg.opam_details.opam in
130 | let lock_pkg = add_nixpkgs_prefix_to_depexts lock_pkg in
131 |
132 | let scope = scope_for_lock_pkg ~ocaml_version lock_pkg in
133 | let env = resolve_subst_and_patch scope in
134 |
135 | let extra_files = Subst_and_patch.get_extra_files lock_pkg.opam_details in
136 | let patches = Subst_and_patch.get_patches ~env lock_pkg.opam_details.opam in
137 | let substs = OpamFile.OPAM.substs lock_pkg.opam_details.opam in
138 |
139 | let env = resolve_commands scope in
140 | let build = Nix_filter.process_commands ~env (OpamFile.OPAM.build opam) in
141 |
142 | let install = Nix_filter.process_commands ~env (OpamFile.OPAM.install opam) in
143 |
144 | {
145 | lock_pkg;
146 | scope;
147 | opam_details = lock_pkg.opam_details;
148 | inputs = default_inputs;
149 | build;
150 | install;
151 | extra_files;
152 | patches;
153 | substs;
154 | }
155 |
156 | let copy_extra_files ~pkg_lock_dir extra_files =
157 | (* pkg_lock_dir is assumed to exist. *)
158 | List.iter
159 | (fun src ->
160 | let base = OpamFilename.basename src in
161 | let dst = OpamFilename.create pkg_lock_dir base in
162 | OpamFilename.copy ~src ~dst)
163 | extra_files
164 |
--------------------------------------------------------------------------------
/nix/overlay/default.nix:
--------------------------------------------------------------------------------
1 | nixpkgs: self: super:
2 |
3 | let
4 | inherit (nixpkgs) lib;
5 |
6 | common = {
7 | ocaml-version = super.ocaml-version.overrideAttrs (oldAttrs:
8 | if (oldAttrs.version == "3.7.1") then {
9 | unpackCmd = ''
10 | tar xf "$curSrc"
11 | '';
12 | } else {}
13 | );
14 |
15 | ocamlfind = super.ocamlfind.overrideAttrs (oldAttrs: {
16 | patches = oldAttrs.patches or [ ]
17 | ++ lib.optional (oldAttrs.version == "1.9.2") ./ocamlfind/onix_install_topfind_192.patch
18 | ++ lib.optional (oldAttrs.version == "1.9.3") ./ocamlfind/onix_install_topfind_193.patch
19 | ++ lib.optional (oldAttrs.version == "1.9.4") ./ocamlfind/onix_install_topfind_194.patch
20 | ++ lib.optional (oldAttrs.version == "1.9.5") ./ocamlfind/onix_install_topfind_195.patch
21 | ++ lib.optional (oldAttrs.version == "1.9.8") ./ocamlfind/onix_install_topfind_198.patch;
22 | setupHook = nixpkgs.writeText "ocamlfind-setup-hook.sh" ''
23 | [[ -z ''${strictDeps-} ]] || (( "$hostOffset" < 0 )) || return 0
24 | export OCAMLTOP_INCLUDE_PATH="$1/lib/ocaml/${super.ocaml.version}/site-lib/toplevel"
25 | '';
26 | # setupHook = nixpkgs.writeText "ocamlfind-setup-hook.sh" ''
27 | # [[ -z ''${strictDeps-} ]] || (( "$hostOffset" < 0 )) || return 0
28 |
29 | # addTargetOCamlPath () {
30 | # local libdir="$1/lib/ocaml/${super.ocaml.version}/site-lib"
31 |
32 | # if [[ ! -d "$libdir" ]]; then
33 | # return 0
34 | # fi
35 |
36 | # echo "+ onix-ocamlfind-setup-hook.sh/addTargetOCamlPath: $*"
37 |
38 | # addToSearchPath "OCAMLPATH" "$libdir"
39 | # addToSearchPath "CAML_LD_LIBRARY_PATH" "$libdir/stublibs"
40 | # }
41 |
42 | # addEnvHooks "$targetOffset" addTargetOCamlPath
43 |
44 | # export OCAMLTOP_INCLUDE_PATH="$1/lib/ocaml/${super.ocaml.version}/site-lib/toplevel"
45 | # '';
46 | });
47 |
48 | # topkg = super.topkg.overrideAttrs (oldAttrs: {
49 | # setupHook = nixpkgs.writeText "topkg-setup-hook.sh" ''
50 | # echo ">>> topkg-setup-hook: $1"
51 | # addToSearchPath "OCAMLPATH" "$1/lib/ocaml/${self.ocaml.version}/site-lib"
52 | # '';
53 | # });
54 |
55 | ocb-stubblr = super.ocb-stubblr.overrideAttrs (oldAttrs: {
56 | patches = oldAttrs.patches or [ ]
57 | ++ [ ./ocb-stubblr/onix_disable_opam.patch ];
58 | });
59 |
60 | # https://github.com/ocsigen/lwt/pull/946
61 | lwt_react = super.lwt_react.overrideAttrs (oldAttrs: {
62 | nativeBuildInputs = oldAttrs.nativeBuildInputs or [ ]
63 | ++ [ self.cppo or null ];
64 | });
65 |
66 | # https://github.com/pqwy/ocb-stubblr/blob/34dcbede6b51327172a0a3d83ebba02843aca249/src/ocb_stubblr.ml#L42
67 | core_unix = super.core_unix.overrideAttrs (oldAttrs: {
68 | prePatch = (oldAttrs.prePatch or "") + ''
69 | patchShebangs unix_pseudo_terminal/src/discover.sh
70 | '';
71 | });
72 |
73 | # For versions < 1.12
74 | zarith = super.zarith.overrideAttrs (oldAttrs: {
75 | prePatch = (oldAttrs.prePatch or "") + ''
76 | if test -e ./z_pp.pl; then
77 | patchShebangs ./z_pp.pl
78 | fi
79 | '';
80 | });
81 |
82 | # https://nixos.org/manual/nixpkgs/stable/#var-stdenv-sourceRoot
83 | timedesc-tzdb =
84 | super.timedesc-tzdb.overrideAttrs (attrs: { sourceRoot = "."; });
85 |
86 | timedesc-tzlocal =
87 | super.timedesc-tzlocal.overrideAttrs (attrs: { sourceRoot = "."; });
88 |
89 | timedesc =
90 | super.timedesc.overrideAttrs (attrs: { sourceRoot = "."; });
91 |
92 | # With propagated inputs this is not necessary.
93 | # https://github.com/ocaml/opam-repository/blob/e470f5f4ad3083618a4e144668faaa81b726b912/packages/either/either.1.0.0/opam#L14
94 | # either = super.either.overrideAttrs
95 | # (oldAttrs: { buildInputs = oldAttrs.buildInputs ++ [ self.ocaml ]; });
96 | #
97 | # ctypes = super.ctypes.overrideAttrs (selfAttrs: superAttrs: {
98 | # postInstall = ''
99 | # mkdir -p "$out/lib/ocaml/4.14.0/site-lib/stublibs"
100 | # mv $out/lib/ocaml/4.14.0/site-lib/ctypes/*.so "$out/lib/ocaml/4.14.0/site-lib/stublibs"
101 | # '';
102 | # });
103 |
104 | num = super.num.overrideAttrs (selfAttrs: superAttrs: {
105 | installPhase = ''
106 | # opaline does not support lib_root
107 | substituteInPlace num.install --replace lib_root lib
108 | ${nixpkgs.opaline}/bin/opaline -prefix $out -libdir $OCAMLFIND_DESTDIR num.install
109 | '';
110 | });
111 |
112 | odoc = super.odoc.overrideAttrs (oldAttrs: {
113 | nativeBuildInputs = (oldAttrs.nativeBuildInputs or [ ]) ++ [ self.crunch ];
114 | });
115 |
116 | # nix 23.11 renamed `pkgconfig` to `pkg-config`
117 | conf-pkg-config = super.conf-pkg-config.overrideAttrs (oldAttrs: {
118 | propagatedBuildInputs = [ nixpkgs.pkg-config ];
119 | propagatedNativeBuildInputs = [ nixpkgs.pkg-config ];
120 | });
121 | };
122 |
123 | darwin = {
124 | dune = super.dune.overrideAttrs (oldAttrs: {
125 | buildInputs = oldAttrs.buildInputs or [ ] ++ [
126 | nixpkgs.darwin.apple_sdk.frameworks.Foundation
127 | nixpkgs.darwin.apple_sdk.frameworks.CoreServices
128 | ];
129 |
130 | # See https://github.com/ocaml/dune/pull/6260
131 | nativeBuildInputs = oldAttrs.nativeBuildInputs ++ [ nixpkgs.makeWrapper ];
132 | postFixup =
133 | if nixpkgs.stdenv.isDarwin then ''
134 | wrapProgram $out/bin/dune \
135 | --suffix PATH : "${nixpkgs.darwin.sigtool}/bin"
136 | ''
137 | else "";
138 | });
139 | };
140 |
141 | all = common
142 | // nixpkgs.lib.optionalAttrs nixpkgs.stdenv.hostPlatform.isDarwin darwin;
143 |
144 | # Remove overrides for packages not present in scope.
145 | in lib.attrsets.filterAttrs (name: _: builtins.hasAttr name super) all
146 |
--------------------------------------------------------------------------------
/src/onix_core/Filter.ml:
--------------------------------------------------------------------------------
1 | open Utils
2 |
3 | let relop_kind_to_nix_string (relop_kind : OpamParserTypes.relop) =
4 | match relop_kind with
5 | | `Eq -> "=="
6 | | `Neq -> "!="
7 | | `Geq -> ">="
8 | | `Gt -> ">"
9 | | `Leq -> "<="
10 | | `Lt -> "<"
11 |
12 | let opam_filter_to_nix_string ?custom (t : OpamTypes.filter) =
13 | let custom ~context ~paren t =
14 | match custom with
15 | | None -> None
16 | | Some f -> f ~context ~paren t
17 | in
18 | let rec aux ?(context = `Or) (t : OpamTypes.filter) =
19 | let paren ?(cond = false) f =
20 | if cond || OpamFormatConfig.(!r.all_parens) then Printf.sprintf "(%s)" f
21 | else f
22 | in
23 | match custom ~context ~paren t with
24 | | Some str -> str
25 | | None -> (
26 | match t with
27 | | FBool b -> string_of_bool b
28 | | FString s -> Printf.sprintf "%S" s
29 | | FIdent (pkgs, var, converter) -> (
30 | OpamStd.List.concat_map "+"
31 | (function
32 | | None -> "_"
33 | | Some p -> OpamPackage.Name.to_string p)
34 | pkgs
35 | ^ (if pkgs <> [] then ":" else "")
36 | ^ OpamVariable.to_string var
37 | ^
38 | match converter with
39 | | Some (it, ifu) -> "?" ^ it ^ ":" ^ ifu
40 | | None -> "")
41 | | FOp (e, s, f) ->
42 | paren
43 | ~cond:(context <> `Or && context <> `And)
44 | (Printf.sprintf "%s %s %s" (aux ~context:`Relop e)
45 | (relop_kind_to_nix_string s)
46 | (aux ~context:`Relop f))
47 | | FAnd (e, f) ->
48 | paren
49 | ~cond:(context <> `Or && context <> `And)
50 | (Printf.sprintf "%s && %s" (aux ~context:`And e) (aux ~context:`And f))
51 | | FOr (e, f) ->
52 | paren ~cond:(context <> `Or) (Printf.sprintf "%s || %s" (aux e) (aux f))
53 | | FNot e ->
54 | paren ~cond:(context = `Relop)
55 | (Printf.sprintf "!%s" (aux ~context:`Not e))
56 | | FDefined e ->
57 | paren ~cond:(context = `Relop)
58 | (Printf.sprintf "?%s" (aux ~context:`Defined e))
59 | | FUndef f -> Printf.sprintf "#undefined(%s)" (aux f))
60 | in
61 | aux t
62 |
63 | let resolve_build ?system ?(with_test = false) ?(with_doc = false)
64 | ?(with_dev_setup = false) pkg_scope =
65 | Scope.resolve_many
66 | [
67 | Scope.resolve_stdenv_host;
68 | Scope.resolve_dep ~test:with_test ~doc:with_doc ~dev_setup:with_dev_setup;
69 | Scope.resolve_config pkg_scope;
70 | Scope.resolve_global ?system ~jobs:Nix_utils.nix_build_jobs_var;
71 | Scope.resolve_pkg ~build_dir:"." pkg_scope;
72 | ]
73 |
74 | let pp_command f (args_str, system_filter) =
75 | match system_filter with
76 | | None -> Fmt.pf f "%S" (String.concat " " args_str)
77 | | Some (`arch arch) ->
78 | Fmt.pf f {|@[[%S, {"arch": %S}]@]|} (String.concat " " args_str) arch
79 | | Some (`os os) ->
80 | Fmt.pf f {|@[[%S, {"os": "%s"}]@]|} (String.concat " " args_str) os
81 | | Some (`system (system : System.t)) ->
82 | Fmt.pf f {|@[[%S, {"arch": %S, "os": %S}]@]|}
83 | (String.concat " " args_str)
84 | system.arch system.os
85 |
86 | let pp_commands f
87 | (commands :
88 | (string list
89 | * [`system of System.t | `arch of string | `os of string] option)
90 | list) =
91 | if List.is_empty commands then ()
92 | else
93 | Fmt.pf f {|@["build": [@,%a@]@,]|}
94 | (Fmt.list ~sep:Fmt.comma pp_command)
95 | commands
96 |
97 | let rec simplify_conjunction (filter : OpamTypes.filter) : OpamTypes.filter =
98 | match filter with
99 | | FOp (f1, relop, f2) ->
100 | FOp (simplify_conjunction f1, relop, simplify_conjunction f2)
101 | | FOr (f1, f2) -> FOr (simplify_conjunction f1, simplify_conjunction f2)
102 | | FNot f1 -> simplify_conjunction f1
103 | | FAnd (FBool true, f) | FAnd (f, FBool true) -> simplify_conjunction f
104 | | FAnd (f1, f2) -> FAnd (simplify_conjunction f1, simplify_conjunction f2)
105 | | _ -> filter
106 |
107 | let partial_eval ~env filter =
108 | let filter' = OpamFilter.partial_eval env filter in
109 | simplify_conjunction filter'
110 |
111 | let system_resolver_for_vars full_vars =
112 | let has_arch, has_os =
113 | List.fold_left
114 | (fun (has_arch, has_os) fv ->
115 | let var = OpamVariable.(to_string (Full.variable fv)) in
116 | match var with
117 | | "arch" -> (true, has_os)
118 | | "os" -> (has_arch, true)
119 | | _ -> (has_arch, has_os))
120 | (false, false) full_vars
121 | in
122 | match (has_arch, has_os) with
123 | | true, true ->
124 | List.map
125 | (fun (system : System.t) ->
126 | (`system system, Scope.resolve_system ~os:system.os ~arch:system.arch))
127 | System.all
128 | | true, false ->
129 | List.map
130 | (fun arch -> (`arch arch, Scope.resolve_system ~arch ?os:None))
131 | System.arch_list
132 | | false, true ->
133 | List.map
134 | (fun os -> (`os os, Scope.resolve_system ?arch:None ~os))
135 | System.os_list
136 | | false, false -> []
137 |
138 | let eval_filter_for_systems ~static_env filter =
139 | (* Partially eval the cmd args filter with the static env. *)
140 | let filter_static = partial_eval ~env:static_env filter in
141 | match filter_static with
142 | | FBool false -> []
143 | | FBool true ->
144 | (* This is a non-system specific filter. *)
145 | [None]
146 | | _ ->
147 | let remaining_vars = OpamFilter.variables filter_static in
148 | let resolvers_by_system = system_resolver_for_vars remaining_vars in
149 | List.fold_left
150 | (fun acc (kind, env) ->
151 | (* Attempt to eval for each system env. *)
152 | let bool = OpamFilter.eval_to_bool ~default:false env filter_static in
153 | if bool then Some kind :: acc else acc)
154 | [] resolvers_by_system
155 |
156 | let process_command ~with_test ~with_doc ~with_dev_setup scope
157 | ((args, filter_opt) : OpamTypes.command) =
158 | let static_env = resolve_build ~with_test ~with_doc ~with_dev_setup scope in
159 | let args' = OpamFilter.single_command static_env args in
160 | match filter_opt with
161 | | Some filter ->
162 | let target_systems = eval_filter_for_systems ~static_env filter in
163 | List.map (fun sys -> (args', sys)) target_systems
164 | | None -> [(args', None)]
165 |
166 | let process_commands ~with_test ~with_doc ~with_dev_setup scope commands =
167 | List.concat_map
168 | (process_command ~with_test ~with_doc ~with_dev_setup scope)
169 | commands
170 |
--------------------------------------------------------------------------------
/src/onix_core/Opam_utils.ml:
--------------------------------------------------------------------------------
1 | open Utils
2 |
3 | type opam_file_type =
4 | [ `opam
5 | | `pkg_opam ]
6 |
7 | (* path:
8 | - opam
9 | - opam/$pkg.opam
10 | - $pkg.opam
11 | - vendor/$pkg/$pkg.opam
12 | - vendor/$pkg/opam
13 | - $repo/packages/$pkg/$pkg.version/opam ($repo is absolute) *)
14 | type opam_details = {
15 | package : OpamTypes.package;
16 | path : OpamFilename.t;
17 | opam : OpamFile.OPAM.t;
18 | }
19 |
20 | let root_dir = OpamFilename.Dir.of_string "/"
21 |
22 | module Opam_details = struct
23 | type t = opam_details
24 |
25 | let check_has_absolute_path t = OpamFilename.starts_with root_dir t.path
26 | end
27 |
28 | let opam_name = OpamFile.OPAM.name
29 | let pp_package = Fmt.using OpamPackage.to_string Fmt.string
30 | let pp_package_version = Fmt.using OpamPackage.Version.to_string Fmt.string
31 | let pp_package_name = Fmt.using OpamPackage.Name.to_string Fmt.string
32 | let pp_url = Fmt.using OpamUrl.to_string Fmt.string
33 | let pp_filename = Fmt.using OpamFilename.to_string Fmt.string
34 | let pp_filename_dir = Fmt.using OpamFilename.Dir.to_string Fmt.string
35 | let pp_filename_base = Fmt.using OpamFilename.Base.to_string Fmt.string
36 | let pp_hash = Fmt.using OpamHash.to_string Fmt.string
37 |
38 | let read_opam path =
39 | let filename = OpamFile.make path in
40 | Utils.In_channel.with_open_text (OpamFilename.to_string path) (fun ic ->
41 | OpamFile.OPAM.read_from_channel ~filename ic)
42 |
43 | let ocaml_name = OpamPackage.Name.of_string "ocaml"
44 | let ocaml_config_name = OpamPackage.Name.of_string "ocaml-config"
45 | let ocamlfind_name = OpamPackage.Name.of_string "ocamlfind"
46 | let dune_name = OpamPackage.Name.of_string "dune"
47 | let ocamlbuild_name = OpamPackage.Name.of_string "ocamlbuild"
48 | let topkg_name = OpamPackage.Name.of_string "ocamlfind"
49 | let cppo_name = OpamPackage.Name.of_string "cppo"
50 | let ocaml_base_compiler_name = OpamPackage.Name.of_string "ocaml-base-compiler"
51 | let ocaml_system_name = OpamPackage.Name.of_string "ocaml-system"
52 | let ocaml_variants_name = OpamPackage.Name.of_string "ocaml-variants"
53 | let dune_configurator_name = OpamPackage.Name.of_string "dune-configurator"
54 | let menhir_name = OpamPackage.Name.of_string "menhir"
55 |
56 | let is_ocaml_compiler_name name =
57 | OpamPackage.Name.equal name ocaml_base_compiler_name
58 | || OpamPackage.Name.equal name ocaml_system_name
59 | || OpamPackage.Name.equal name ocaml_variants_name
60 |
61 | let is_ocaml_name name = OpamPackage.Name.equal name ocaml_name
62 | let dev_version = OpamPackage.Version.of_string "dev"
63 | let is_pinned_version version = OpamPackage.Version.equal version dev_version
64 | let is_pinned package = is_pinned_version (OpamPackage.version package)
65 |
66 | let opam_package_of_filename filename =
67 | let filename_str = OpamFilename.to_string filename in
68 | if String.equal filename_str "./opam" then
69 | (* ./opam - read as $pkg/opam where $pkg is CWD.
70 | If ./opam is a dir, `filename` would have been `./opam/$pkg.opam`. *)
71 | let dirname =
72 | OpamFilename.Base.to_string
73 | (OpamFilename.basename_dir (OpamFilename.cwd ()))
74 | in
75 | OpamPackage.create (OpamPackage.Name.of_string dirname) dev_version
76 | else
77 | let base_str =
78 | OpamFilename.Base.to_string (OpamFilename.basename filename)
79 | in
80 | if String.equal base_str "opam" then
81 | (* $pkg/opam *)
82 | let dir_str =
83 | OpamFilename.Dir.to_string (OpamFilename.dirname filename)
84 | in
85 | match List.rev (String.split_on_char '/' dir_str) with
86 | | pkg_dir :: _ ->
87 | OpamPackage.create (OpamPackage.Name.of_string pkg_dir) dev_version
88 | | _ ->
89 | invalid_arg
90 | ("Could not extract package name from path (must be pkg/opam): "
91 | ^ filename_str)
92 | else
93 | (* $pkg.opam *)
94 | let opamname = Filename.remove_extension base_str in
95 | OpamPackage.create (OpamPackage.Name.of_string opamname) dev_version
96 |
97 | type dep_vars = {
98 | test : bool;
99 | doc : bool;
100 | dev_setup : bool;
101 | }
102 |
103 | type package_dep_vars = dep_vars OpamPackage.Name.Map.t
104 |
105 | let eval_package_dep_vars name package_dep_vars =
106 | try OpamPackage.Name.Map.find name package_dep_vars
107 | with Not_found -> { test = false; doc = false; dev_setup = false }
108 |
109 | let debug_var ?(scope = "unknown") var contents =
110 | Logs.debug (fun log ->
111 | log "Variable lookup: %s=%a scope=%s"
112 | (OpamVariable.Full.to_string var)
113 | (Fmt.Dump.option
114 | (Fmt.using OpamVariable.string_of_variable_contents Fmt.Dump.string))
115 | contents scope)
116 |
117 | let find_root_packages opam_file_paths =
118 | opam_file_paths
119 | |> List.to_seq
120 | |> Seq.map (fun path ->
121 | let package = opam_package_of_filename path in
122 | Logs.info (fun log ->
123 | log "Reading packages from %a..." pp_filename path);
124 | let opam = read_opam path in
125 | let details = { opam; package; path } in
126 | (OpamPackage.name package, details))
127 | |> OpamPackage.Name.Map.of_seq
128 |
129 | let mk_repo_opamfile ~(repository_dir : OpamFilename.Dir.t) opam_package =
130 | let name = OpamPackage.name_to_string opam_package in
131 | let name_with_version = OpamPackage.to_string opam_package in
132 | OpamFilename.Op.(
133 | repository_dir / "packages" / name / name_with_version // "opam")
134 |
135 | let make_opam_files_path ~opamfile file =
136 | let opam_dir = OpamFilename.dirname opamfile in
137 | let file = OpamFilename.Base.to_string file in
138 | let base = OpamFilename.Base.of_string ("files/" ^ file) in
139 | OpamFilename.create opam_dir base
140 |
141 | type extra_file_status =
142 | | Undeclared
143 | | Ok_hash
144 | | Bad_hash
145 |
146 | (* Undeclared extra-files are looked up in ./files in the opam file directory. *)
147 | let lookup_undeclared_opam_extra_files ~opamfile =
148 | let ( > ) = OpamFilename.Op.( / ) in
149 | let files_dir = OpamFilename.(dirname opamfile > "files") in
150 | OpamFilename.files files_dir
151 |
152 | (* Check hashes for opam's extra files.
153 | Returns a tuple (files_with_bad_hashes, files_with_good_hashes). *)
154 | let check_extra_files_hashes ~opamfile extra_files =
155 | List.partition_map
156 | (fun (basename, hash) ->
157 | let file = make_opam_files_path ~opamfile basename in
158 | if OpamHash.check_file (OpamFilename.to_string file) hash then Right file
159 | else Left file)
160 | extra_files
161 |
162 | let name_set_to_string_set name_set =
163 | Name_set.fold
164 | (fun name acc -> String_set.add (OpamPackage.Name.to_string name) acc)
165 | name_set String_set.empty
166 |
--------------------------------------------------------------------------------
/tests/Test_lock.ml:
--------------------------------------------------------------------------------
1 | open Onix_core
2 |
3 | let complex_opam =
4 | {|
5 | opam-version: "2.0"
6 | depends: [
7 | "ocaml" {>= "4.08" & < "5.0.0"}
8 | "dune" {>= "2.0"}
9 | "odoc" {with-doc}
10 | "bos"
11 | "cmdliner"
12 | "logs"
13 | "fmt"
14 | "fpath"
15 | "opam-0install"
16 | "yojson"
17 | "easy-format" {="1.3.2"}
18 | ]
19 | depexts: [
20 | ["libogg-dev"] {os-distribution = "alpine"}
21 | ["libogg"] {os-distribution = "arch"}
22 | ["libogg-dev"] {os-family = "debian"}
23 | ["libogg-devel"] {os-distribution = "centos"}
24 | ["libogg-devel"] {os-distribution = "fedora"}
25 | ["libogg-devel"] {os-family = "suse"}
26 | ["libogg"] {os-distribution = "nixos"}
27 | ["libogg"] {os = "macos" & os-distribution = "homebrew"}
28 | ]
29 | url {
30 | src: "https://github.com/xavierleroy/camlzip/archive/rel110.zip"
31 | checksum: "sha256=a5541cbc38c14467a8abcbdcb54c1d2ed12515c1c4c6da0eb3bdafb44aff7996"
32 | }
33 | extra-source "gui_gtk_dir.patch" {
34 | src:
35 | "https://raw.githubusercontent.com/ocaml/opam-source-archives/main/patches/0install/gui_gtk_dir.patch"
36 | checksum: [
37 | "sha256=ef4c291794ed4ca7f024c671f48a8aaa2dcd9d12c1ab73829373a7d904e537e1"
38 | "md5=0a14e57ca2b2a914a5433b3a2ca2abb1"
39 | ]
40 | }
41 | extra-source "0install.install" {
42 | src:
43 | "https://raw.githubusercontent.com/ocaml/opam-source-archives/main/patches/0install/0install.install"
44 | checksum: [
45 | "sha256=db9ef395b376617d963fd4c097ebdfe005978f9a3282810f858f89207fa85ab2"
46 | "md5=db6ee7a35da5d98136e5a56bad08496e"
47 | ]
48 | }
49 | |}
50 |
51 | let dev_opam =
52 | {|
53 | opam-version: "2.0"
54 | url {
55 | src: "git+https://github.com/odis-labs/options.git#5b1165d99aba112d550ddc3133a8eb1d174441ec"
56 | }
57 | |}
58 |
59 | let zip_src_opam =
60 | {|
61 | opam-version: "2.0"
62 | url {
63 | src: "https://github.com/xavierleroy/camlzip/archive/rel110.zip"
64 | checksum: "sha256=a5541cbc38c14467a8abcbdcb54c1d2ed12515c1c4c6da0eb3bdafb44aff7996"
65 | }
66 | |}
67 |
68 | let other_deps_opam =
69 | {|
70 | opam-version: "2.0"
71 | depends: [
72 | "dep1"
73 | "dep2"
74 | "dep-build-1" {build}
75 | "dep-build-2" {build}
76 | "dep-test-1" {with-test}
77 | "dep-test-2" {with-test}
78 | "dep-doc-1" {with-doc}
79 | "dep-doc-2" {with-doc}
80 | "dep-tool-1" {with-dev-setup}
81 | "dep-tool-2" {with-dev-setup}
82 | "dep-test-o-doc-1" {with-test | with-doc}
83 | "dep-test-n-doc-1" {with-test & with-doc}
84 | ]
85 | depopts: [
86 | "opt1"
87 | "opt2"
88 | "opt-build-1" {build}
89 | "opt-build-2" {build}
90 | "opt-test-1" {with-test}
91 | "opt-test-2" {with-test}
92 | "opt-doc-1" {with-doc}
93 | "opt-doc-2" {with-doc}
94 | "opt-tool-1" {with-dev-setup}
95 | "opt-tool-2" {with-dev-setup}
96 | "opt-test-o-doc-1" {with-test | with-doc}
97 | "opt-test-n-doc-1" {with-test & with-doc}
98 | ]
99 | depexts: [
100 | ["opt-ext-1" "opt-ext-2" "opt-ext-3"] {os-distribution = "alpine"}
101 | ]
102 | |}
103 |
104 | let eq ~actual ~expected =
105 | if not (String.equal actual expected) then (
106 | Fmt.pr "--- EXPECTED ---\n%s\n\n--- ACTUAL ---\n%s@." expected actual;
107 | raise Exit)
108 |
109 | let installed pkg_name =
110 | match OpamPackage.Name.to_string pkg_name with
111 | | "opt1"
112 | | "opt2"
113 | | "opt-build-1"
114 | | "opt-build-2"
115 | | "opt-test-1"
116 | | "opt-test-2"
117 | | "opt-doc-1"
118 | | "opt-doc-2"
119 | | "opt-tool-1"
120 | | "opt-tool-2"
121 | | "opt-test-o-doc-1"
122 | | "opt-test-n-doc-1" -> false
123 | | _ -> true
124 |
125 | let mk_lock ~name str =
126 | let package = OpamPackage.of_string name in
127 | let opam = OpamFile.OPAM.read_from_string str in
128 | let path = OpamFilename.of_string (name ^ ".opam") in
129 | let opam_details = { Opam_utils.package; opam; path } in
130 | Lock_pkg.of_opam ~installed ~with_dev_setup:true ~with_test:true
131 | ~with_doc:true opam_details
132 | |> Option.get
133 |
134 | let test_complex_opam () =
135 | let lock_pkg = mk_lock ~name:"complex.root" complex_opam in
136 | let actual = Fmt.str "@[%a@]@." Onix_lock_json.Pp.pp_pkg lock_pkg in
137 | let expected =
138 | {|"version": "root",
139 | "src": {
140 | "url": "https://github.com/xavierleroy/camlzip/archive/rel110.zip",
141 | "sha256": "a5541cbc38c14467a8abcbdcb54c1d2ed12515c1c4c6da0eb3bdafb44aff7996"
142 | },
143 | "src-extra": {
144 | "0install.install": {
145 | "url": "https://raw.githubusercontent.com/ocaml/opam-source-archives/main/patches/0install/0install.install",
146 | "sha256": "db9ef395b376617d963fd4c097ebdfe005978f9a3282810f858f89207fa85ab2"
147 | },
148 | "gui_gtk_dir.patch": {
149 | "url": "https://raw.githubusercontent.com/ocaml/opam-source-archives/main/patches/0install/gui_gtk_dir.patch",
150 | "sha256": "ef4c291794ed4ca7f024c671f48a8aaa2dcd9d12c1ab73829373a7d904e537e1"
151 | }
152 | },
153 | "depends": [
154 | "bos",
155 | "cmdliner",
156 | "dune",
157 | "easy-format",
158 | "fmt",
159 | "fpath",
160 | "logs",
161 | "ocaml",
162 | "opam-0install",
163 | "yojson"
164 | ],
165 | "build-depends": [
166 | "dune"
167 | ],
168 | "doc-depends": [
169 | "odoc"
170 | ],
171 | "depexts": [
172 | "libogg",
173 | "unzip"
174 | ],
175 | "vars": { "with-test": true, "with-doc": true, "with-dev-setup": true }
176 | |}
177 | in
178 | eq ~actual ~expected
179 |
180 | let test_dev_opam () =
181 | let lock_pkg = mk_lock ~name:"dev.dev" dev_opam in
182 | let actual = Fmt.str "@[%a@]@." Onix_lock_json.Pp.pp_pkg lock_pkg in
183 | let expected =
184 | {|"version": "dev",
185 | "src": {
186 | "url": "git+https://github.com/odis-labs/options.git",
187 | "rev": "5b1165d99aba112d550ddc3133a8eb1d174441ec"
188 | },
189 | "vars": { "with-test": true, "with-doc": true, "with-dev-setup": true }
190 | |}
191 | in
192 | eq ~actual ~expected
193 |
194 | let test_zip_src_opam () =
195 | let lock_pkg = mk_lock ~name:"zip.1.0.2" zip_src_opam in
196 | let actual = Fmt.str "@[%a@]@." Onix_lock_json.Pp.pp_pkg lock_pkg in
197 | let expected =
198 | {|"version": "1.0.2",
199 | "src": {
200 | "url": "https://github.com/xavierleroy/camlzip/archive/rel110.zip",
201 | "sha256": "a5541cbc38c14467a8abcbdcb54c1d2ed12515c1c4c6da0eb3bdafb44aff7996"
202 | },
203 | "depexts": [
204 | "unzip"
205 | ],
206 | "vars": { "with-test": true, "with-doc": true, "with-dev-setup": true }
207 | |}
208 | in
209 | eq ~actual ~expected
210 |
211 | let test_other_deps_opam () =
212 | let lock_pkg = mk_lock ~name:"other-deps.1.0.1" other_deps_opam in
213 | let actual = Fmt.str "@[%a@]@." Onix_lock_json.Pp.pp_pkg lock_pkg in
214 | let expected =
215 | {|"version": "1.0.1",
216 | "depends": [
217 | "dep1",
218 | "dep2"
219 | ],
220 | "build-depends": [
221 | "dep-build-1",
222 | "dep-build-2"
223 | ],
224 | "test-depends": [
225 | "dep-test-1",
226 | "dep-test-2",
227 | "dep-test-o-doc-1"
228 | ],
229 | "doc-depends": [
230 | "dep-doc-1",
231 | "dep-doc-2",
232 | "dep-test-o-doc-1"
233 | ],
234 | "dev-setup-depends": [
235 | "dep-tool-1",
236 | "dep-tool-2"
237 | ],
238 | "depexts": [
239 | "opt-ext-1",
240 | "opt-ext-2",
241 | "opt-ext-3"
242 | ],
243 | "vars": { "with-test": true, "with-doc": true, "with-dev-setup": true }
244 | |}
245 | in
246 | eq ~actual ~expected
247 |
248 | let () =
249 | test_complex_opam ();
250 | test_dev_opam ();
251 | test_zip_src_opam ();
252 | test_other_deps_opam ()
253 |
--------------------------------------------------------------------------------
/nix/experiments/vars.nix:
--------------------------------------------------------------------------------
1 | { platform, ocaml-version }:
2 |
3 | let
4 | # pkg_unscoped = {
5 | # "lib" = paths.lib { prefix = "$out"; };
6 | # "stublibs" = paths.stublibs { prefix = "$out"; };
7 | # "toplevel" = paths.toplevel { prefix = "$out"; };
8 | # "bin" = paths.bin { prefix = "$out"; };
9 | # "sbin" = paths.sbin { prefix = "$out"; };
10 | # "share" = paths.share { prefix = "$out"; };
11 | # "etc" = paths.etc { prefix = "$out"; };
12 | # "doc" = paths.doc { prefix = "$out"; };
13 | # "man" = paths.man { prefix = "$out"; };
14 |
15 | # "build-id" = "$out";
16 | # "build" = ".";
17 | # "installed" = false;
18 | # "name" = self.pname;
19 |
20 | # "opamfile" = (paths.lib {
21 | # prefix = "$out";
22 | # pkg-name = self.pname;
23 | # }) + "/opam";
24 |
25 | # "switch" = "$out";
26 | # "prefix" = "$out";
27 | # "version" = self.version;
28 | # "pinned" = self.version == "dev";
29 | # "dev" = self.version == "dev";
30 | # };
31 |
32 | # pkg-vars = pkg: {
33 | # "lib" = paths.lib { prefix = pkg; };
34 | # "stublibs" = paths.stublibs { prefix = pkg; };
35 | # "toplevel" = paths.toplevel { prefix = pkg; };
36 | # "bin" = paths.bin { prefix = pkg; };
37 | # "sbin" = paths.sbin { prefix = pkg; };
38 | # "share" = paths.share { prefix = pkg; };
39 | # "etc" = paths.etc { prefix = pkg; };
40 | # "doc" = paths.doc { prefix = pkg; };
41 | # "man" = paths.man { prefix = pkg; };
42 |
43 | # "build-id" = builtins.toString pkg;
44 | # "installed" = false;
45 | # "name" = pkg.pname;
46 |
47 | # "opamfile" = (paths.lib {
48 | # prefix = pkg;
49 | # pkg-name = pkg.pname;
50 | # }) + "/opam";
51 |
52 | # "version" = pkg.version;
53 | # "pinned" = pkg.version == "dev";
54 | # "dev" = pkg.version == "dev";
55 | # };
56 |
57 | paths = import ./paths.nix { inherit ocaml-version; };
58 |
59 | global = {
60 | opam-version = "2.0";
61 | root = "/tmp/onix-opam-root";
62 | jobs = "$NIX_BUILD_CORES";
63 | make = "make";
64 |
65 | os-distribution = "nixos";
66 | os-family = "nixos";
67 | os-version = "unknown";
68 |
69 | arch = platform.uname.processor;
70 |
71 | os = if platform.isDarwin then
72 | "macos"
73 | else if platform.isLinux then
74 | "linux"
75 | else
76 | throw "${platform.uname.system} not supported";
77 | };
78 |
79 | resolve-global = full-var:
80 | let
81 | v = (builtins.trace "resolving global: ${builtins.toJSON full-var}"
82 | full-var).var;
83 | in if builtins.hasAttr v global then global.${v} else null;
84 |
85 | # full-var = { var : string; scope : "global" | "self" | "package"; pkg-name : string; }
86 | resolve-pkg = { build-dir, self, pkgs, ocaml-version }:
87 | full-var:
88 | let
89 | v = full-var.var;
90 | # g=global, i=installed, m=missing
91 | scope = if full-var.scope == "global" then {
92 | tag = "g";
93 | } else if full-var.scope == "self" then {
94 | tag = "i";
95 | pkg = self;
96 | } else if full-var.scope == "package" then
97 | if builtins.hasAttr full-var.pkg-name pkgs then {
98 | tag = "i";
99 | pkg = pkgs.${full-var.pkg-name};
100 | } else {
101 | tag = "m";
102 | pkg.name = full-var.pkg-name;
103 | }
104 | else
105 | throw "invalid variable scope in ${builtins.toJSON full-var}";
106 |
107 | # name
108 | in if scope.tag == "g" && v == "name" then
109 | self.name
110 | else if scope.tag == "i" && v == "name" then
111 | scope.pkg.name
112 | else if scope.tag == "m" && v == "name" then
113 | scope.pkg.name
114 | # version
115 | else if scope.tag == "g" && v == "version" then
116 | self.version
117 | else if scope.tag == "i" && v == "version" then
118 | scope.pkg.version
119 | # pinned, dev
120 | else if scope.tag == "g" && (v == "pinned" || v == "dev") then
121 | self.version == "dev"
122 | else if scope.tag == "i" && (v == "pinned" || v == "dev") then
123 | scope.pkg.version == "dev"
124 | # opamfile
125 | else if scope.tag == "g" && v == "opamfile" then
126 | self.opamfile
127 | else if scope.tag == "i" && v == "opamfile" then
128 | scope.pkg.opamfile
129 | # installed/enable
130 | else if scope.tag == "g" && v == "installed" then
131 | false # not yet
132 | else if scope.tag == "i" && v == "installed" then
133 | true
134 | else if scope.tag == "m" && v == "installed" then
135 | false
136 | else if scope.tag == "i" && v == "enable" then
137 | "enable"
138 | else if scope.tag == "m" && v == "enable" then
139 | "disable"
140 | # build info
141 | else if scope.tag == "g" && v == "build" then
142 | build-dir
143 | else if scope.tag == "g" && v == "build-id" then
144 | self.prefix
145 | else if scope.tag == "g" && v == "depends" then
146 | null # TODO
147 |
148 | # paths
149 | else if scope.tag == "g" && (v == "switch" || v == "prefix") then
150 | self.prefix
151 | else if scope.tag == "i" && (v == "switch" || v == "prefix") then
152 | scope.pkg.prefix
153 |
154 | else if scope.tag == "g" && v == "lib" then
155 | paths.lib { prefix = self.prefix; }
156 | else if scope.tag == "i" && v == "lib" then
157 | paths.lib {
158 | prefix = scope.pkg.prefix;
159 | pkg-name = scope.pkg.name;
160 | }
161 | else if scope.tag == "g" && v == "toplevel" then
162 | paths.toplevel { prefix = self.prefix; }
163 | else if scope.tag == "i" && v == "toplevel" then
164 | paths.toplevel {
165 | prefix = scope.pkg.prefix;
166 | pkg-name = scope.pkg.name;
167 | }
168 | else if scope.tag == "g" && v == "stublibs" then
169 | paths.stublibs { prefix = self.prefix; }
170 | else if scope.tag == "i" && v == "stublibs" then
171 | paths.stublibs {
172 | prefix = scope.pkg.prefix;
173 | pkg-name = scope.pkg.name;
174 | }
175 |
176 | else if scope.tag == "g" && v == "bin" then
177 | paths.bin { prefix = self.prefix; }
178 | else if scope.tag == "i" && v == "bin" then
179 | paths.bin {
180 | prefix = scope.pkg.prefix;
181 | pkg-name = scope.pkg.name;
182 | }
183 |
184 | else if scope.tag == "g" && v == "sbin" then
185 | paths.sbin { prefix = self.prefix; }
186 | else if scope.tag == "i" && v == "sbin" then
187 | paths.sbin {
188 | prefix = scope.pkg.prefix;
189 | pkg-name = scope.pkg.name;
190 | }
191 |
192 | else if scope.tag == "g" && v == "share" then
193 | paths.share { prefix = self.prefix; }
194 | else if scope.tag == "i" && v == "share" then
195 | paths.share {
196 | prefix = scope.pkg.prefix;
197 | pkg-name = scope.pkg.name;
198 | }
199 |
200 | else if scope.tag == "g" && v == "doc" then
201 | paths.doc { prefix = self.prefix; }
202 | else if scope.tag == "i" && v == "doc" then
203 | paths.doc {
204 | prefix = scope.pkg.prefix;
205 | pkg-name = scope.pkg.name;
206 | }
207 |
208 | else if scope.tag == "g" && v == "etc" then
209 | paths.etc { prefix = self.prefix; }
210 | else if scope.tag == "i" && v == "etc" then
211 | paths.etc {
212 | prefix = scope.pkg.prefix;
213 | pkg-name = scope.pkg.name;
214 | }
215 |
216 | else if scope.tag == "g" && v == "man" then
217 | paths.man { prefix = self.prefix; }
218 | else if scope.tag == "i" && v == "man" then
219 | paths.man { prefix = scope.pkg.prefix; }
220 |
221 | else if (scope.tag == "i" || scope.tag == "m") && (v == "preinstalled" || v
222 | == "native" || v == "native-tools" || v == "native-dynlink")
223 | && scope.pkg.name == "ocaml" then
224 | true
225 |
226 | else if scope.tag == "g" && v == "sys-ocaml-version" then
227 | ocaml-version
228 | else
229 | null;
230 |
231 | resolve = { build-dir, self, pkgs, ocaml-version }@resolve-pkg-args:
232 | full-var:
233 | let
234 | g = resolve-global
235 | (builtins.trace "resolve: ${builtins.toJSON full-var}" full-var);
236 | in if !(builtins.isNull g) then
237 | g
238 | else
239 | let
240 | resolved = resolve-pkg resolve-pkg-args full-var;
241 | # Attempt to use the fallback values, if any.
242 | in if builtins.isBool resolved then
243 | if resolved && builtins.hasAttr "val-if-true" full-var then
244 | full-var.val-if-true
245 | else if !resolved && builtins.hasAttr "val-if-false" full-var then
246 | full-var.val-if-false
247 | else
248 | resolved
249 | else
250 | resolved;
251 |
252 | in { inherit resolve-global resolve-pkg resolve; }
253 |
--------------------------------------------------------------------------------
/src/onix_core/Nix_utils.ml:
--------------------------------------------------------------------------------
1 | open Utils
2 |
3 | let get_nix_build_jobs () =
4 | try Unix.getenv "NIX_BUILD_CORES" with Not_found -> "1"
5 |
6 | let nix_build_jobs_var = "$NIX_BUILD_CORES"
7 |
8 | let eval expr =
9 | let open Bos in
10 | let output =
11 | Cmd.(v "nix-instantiate" % "--eval" % "--expr" % expr)
12 | |> OS.Cmd.run_out
13 | |> OS.Cmd.to_string
14 | |> Utils.Result.force_with_msg
15 | in
16 | String.sub output 1 (String.length output - 2)
17 |
18 | let _eval ?(raw = true) ?(pure = true) expr =
19 | let open Bos in
20 | Cmd.(
21 | v "nix"
22 | % "eval"
23 | %% Cmd.on raw (v "--raw")
24 | %% Cmd.on (not pure) (v "--impure")
25 | % "--expr"
26 | % expr)
27 | |> OS.Cmd.run_out
28 | |> OS.Cmd.to_string
29 | |> Utils.Result.force_with_msg
30 |
31 | let fetch_git_expr ~rev url =
32 | Fmt.str
33 | {|let result = builtins.fetchGit {
34 | url = %S;
35 | rev = %S;
36 | allRefs = true;
37 | }; in result.outPath|}
38 | url rev
39 |
40 | let fetch_git url =
41 | let rev = url.OpamUrl.hash |> Option.or_fail "Missing rev in opam url" in
42 | let nix_url = OpamUrl.base_url url in
43 | Logs.debug (fun log ->
44 | log "Fetching git repository: url=%S rev=%S" nix_url rev);
45 | nix_url |> fetch_git_expr ~rev |> eval |> OpamFilename.Dir.of_string
46 |
47 | let fetch_git_resolve_expr url =
48 | Fmt.str
49 | {|let result = builtins.fetchGit { url = %S; }; in
50 | "${result.rev},${result.outPath}"|}
51 | url
52 |
53 | let fetch_git_resolve url =
54 | let nix_url = OpamUrl.base_url url in
55 | Logs.debug (fun log -> log "Fetching git repository: url=%S rev=None" nix_url);
56 | let result = nix_url |> fetch_git_resolve_expr |> eval in
57 | match String.split_on_char ',' result with
58 | | [rev; path] -> (rev, OpamFilename.Dir.of_string path)
59 | | _ -> Fmt.failwith "Could not fetch: %S, output=%S" nix_url result
60 |
61 | let maybe opt =
62 | match opt with
63 | | Some x -> Bos.Cmd.v x
64 | | None -> Bos.Cmd.empty
65 |
66 | let prefetch_url_cmd ?(print_path = true) ?(hash_type = `sha256) ?hash url =
67 | let open Bos in
68 | let hash_type =
69 | match hash_type with
70 | | `sha256 -> "sha256"
71 | | `sha512 -> "sha512"
72 | in
73 | Cmd.(
74 | v "nix-prefetch-url"
75 | %% on print_path (v "--print-path")
76 | % "--type"
77 | % hash_type
78 | % url
79 | %% maybe hash)
80 |
81 | let prefetch_url_with_path ?hash_type ?hash url =
82 | let open Bos in
83 | let lines =
84 | prefetch_url_cmd ~print_path:true ?hash_type ?hash url
85 | |> OS.Cmd.run_out
86 | |> OS.Cmd.to_lines
87 | |> Utils.Result.force_with_msg
88 | in
89 | match lines with
90 | | [hash; path] -> (hash, OpamFilename.Dir.of_string path)
91 | | _ ->
92 | Fmt.invalid_arg "Invalid output from nix-prefetch-url: %a"
93 | Fmt.Dump.(list string)
94 | lines
95 |
96 | let prefetch_url ?hash_type ?hash uri =
97 | let open Bos in
98 | prefetch_url_cmd ~print_path:false ?hash_type ?hash uri
99 | |> OS.Cmd.run_out ~err:OS.Cmd.err_null
100 | |> OS.Cmd.to_string
101 | |> Utils.Result.force_with_msg
102 |
103 | let guess_git_rev rev =
104 | match rev with
105 | | Some "master" -> Bos.Cmd.(v "--rev" % "refs/heads/master")
106 | | Some "main" -> Bos.Cmd.(v "--rev" % "refs/heads/master")
107 | | Some tag_or_commit -> Bos.Cmd.(v "--rev" % tag_or_commit)
108 | | None -> Bos.Cmd.empty
109 |
110 | let prefetch_git_cmd ?rev url =
111 | let open Bos in
112 | let rev_opt = guess_git_rev rev in
113 | Cmd.(v "nix-prefetch-git" %% rev_opt % url)
114 |
115 | let prefetch_git_with_path url =
116 | let url, rev =
117 | match url with
118 | | { OpamUrl.backend = `git; hash = rev; _ } -> (OpamUrl.base_url url, rev)
119 | | { OpamUrl.backend = `http; hash = rev; _ } -> (OpamUrl.base_url url, rev)
120 | | { OpamUrl.backend; _ } ->
121 | Fmt.failwith "Unsupported backend in url: %s"
122 | (OpamUrl.string_of_backend backend)
123 | in
124 | let open Bos in
125 | let json =
126 | prefetch_git_cmd ?rev url
127 | |> OS.Cmd.run_out ~err:OS.Cmd.err_null
128 | |> OS.Cmd.to_string
129 | |> Utils.Result.force_with_msg
130 | |> Yojson.Basic.from_string
131 | in
132 | let rev =
133 | Yojson.Basic.Util.member "rev" json |> Yojson.Basic.Util.to_string
134 | in
135 | let path =
136 | Yojson.Basic.Util.member "path" json
137 | |> Yojson.Basic.Util.to_string
138 | |> OpamFilename.Dir.of_string
139 | in
140 | (rev, path)
141 |
142 | let fetch_resolve_many_expr urls =
143 | let url_to_nix (url : OpamUrl.t) =
144 | match url.hash with
145 | | Some hash ->
146 | let url' = { url with OpamUrl.hash = None } in
147 | Fmt.str "{ url = \"%a\"; rev = \"%s\"; }" Opam_utils.pp_url url' hash
148 | | None -> Fmt.str "{ url = \"%a\"; }" Opam_utils.pp_url url
149 | in
150 | let urls = urls |> List.map url_to_nix |> String.concat " " in
151 | Fmt.str
152 | {|
153 | let
154 | urls = [ %s ];
155 | fetched = map (x: (builtins.fetchGit x) // { inherit (x) url; }) urls;
156 | resolved = map (x: "${x.url}#${x.rev},${x.outPath}") fetched;
157 | in
158 | builtins.concatStringsSep ";" resolved
159 | |}
160 | urls
161 |
162 | let fetch_resolve_many urls =
163 | let result = urls |> fetch_resolve_many_expr |> eval in
164 | let lines = String.split_on_char ';' result in
165 | List.map
166 | (fun line ->
167 | match String.split_on_char ',' line with
168 | | [url; path] -> (OpamUrl.of_string url, OpamFilename.Dir.of_string path)
169 | | _ -> Fmt.failwith "Invalid repo format: %s" line)
170 | lines
171 |
172 | let symlink_join_expr ~name paths =
173 | Fmt.str
174 | {|
175 | let pkgs = import {};
176 | in pkgs.symlinkJoin {
177 | name = %S;
178 | paths = [ %a ];
179 | }
180 | |}
181 | name
182 | Fmt.(list ~sep:Fmt.sp Opam_utils.pp_filename_dir)
183 | paths
184 |
185 | let symlink_join ~name paths =
186 | let open Bos in
187 | let expr = symlink_join_expr ~name paths in
188 | let cmd = Cmd.(v "nix-build" % "--no-out-link" % "-E" % expr) in
189 | let result =
190 | cmd
191 | |> OS.Cmd.run_out ~err:OS.Cmd.err_null
192 | |> OS.Cmd.to_string
193 | |> Utils.Result.force_with_msg
194 | in
195 | OpamFilename.Dir.of_string result
196 |
197 | let resolve_repos repos =
198 | let resolved_with_path = fetch_resolve_many repos in
199 | let joint_path =
200 | match resolved_with_path with
201 | | [(_repo_url, path)] -> path
202 | | _ -> symlink_join ~name:"onix-opam-repo" (List.map snd resolved_with_path)
203 | in
204 | let resolved_urls = List.map fst resolved_with_path in
205 | Fmt.epr "@[Repositories:@,%a@,%a@]@."
206 | Fmt.(list ~sep:cut (any "- url: " ++ Opam_utils.pp_url))
207 | resolved_urls
208 | Fmt.(any "- dir: " ++ Opam_utils.pp_filename_dir)
209 | joint_path;
210 | (joint_path, resolved_urls)
211 |
212 | type store_path = {
213 | hash : string;
214 | pkg_name : string;
215 | pkg_version : string;
216 | prefix : string;
217 | suffix : string;
218 | }
219 |
220 | let pp_store_path formatter store_path =
221 | let field = Fmt.Dump.field in
222 | Fmt.pf formatter "%a"
223 | (Fmt.Dump.record
224 | [
225 | field "hash" (fun r -> r.hash) Fmt.Dump.string;
226 | field "pkg_name" (fun r -> r.pkg_name) Fmt.Dump.string;
227 | field "pkg_version" (fun r -> r.pkg_version) Fmt.Dump.string;
228 | field "prefix" (fun r -> r.prefix) Fmt.Dump.string;
229 | field "suffix" (fun r -> r.suffix) Fmt.Dump.string;
230 | ])
231 | store_path
232 |
233 | let parse_store_path path =
234 | match String.split_on_char '/' path with
235 | | "" :: "nix" :: "store" :: hash_name_v :: base_path_parts -> (
236 | let hash_name_v_parts = String.split_on_char '-' hash_name_v in
237 | match (List.hd hash_name_v_parts, List.rev (List.tl hash_name_v_parts)) with
238 | | hash, pkg_version :: name_rev ->
239 | let pkg_name = String.concat "-" (List.rev name_rev) in
240 | let prefix = String.concat "/" [""; "nix"; "store"; hash_name_v] in
241 | let suffix = String.concat "/" base_path_parts in
242 | { hash; pkg_name; pkg_version; prefix; suffix }
243 | | (exception _) | _ ->
244 | Fmt.invalid_arg "Invalid hash and package name in path: %S" path)
245 | | _ -> Fmt.invalid_arg "Invalid nix store path: %S" path
246 |
247 | let make_ocaml_packages_path version =
248 | (* See: pkgs.ocaml-ng.ocamlPackages_X_XX.ocaml.version *)
249 | match OpamPackage.Version.to_string version with
250 | | "4.08.1" -> "ocaml-ng.ocamlPackages_4_08.ocaml"
251 | | "4.09.1" -> "ocaml-ng.ocamlPackages_4_09.ocaml"
252 | | "4.10.2" -> "ocaml-ng.ocamlPackages_4_10.ocaml"
253 | | "4.11.2" -> "ocaml-ng.ocamlPackages_4_11.ocaml"
254 | | "4.12.1" -> "ocaml-ng.ocamlPackages_4_12.ocaml"
255 | | "4.13.1" -> "ocaml-ng.ocamlPackages_4_13.ocaml"
256 | | "4.14.1" -> "ocaml-ng.ocamlPackages_4_14.ocaml"
257 | | "5.0.0" -> "ocaml-ng.ocamlPackages_5_0.ocaml"
258 | | "5.1.1" -> "ocaml-ng.ocamlPackages_5_1.ocaml"
259 | | "5.2.0" -> "ocaml-ng.ocamlPackages_5_2.ocaml"
260 | | unsupported ->
261 | Fmt.failwith "Unsupported nixpkgs ocaml version: %s" unsupported
262 |
263 | let check_ocaml_packages_version version =
264 | try
265 | let _ = make_ocaml_packages_path version in
266 | true
267 | with Failure _ -> false
268 |
--------------------------------------------------------------------------------
/src/onix_core/Scope.ml:
--------------------------------------------------------------------------------
1 | module Name = OpamPackage.Name
2 | module Version = OpamPackage.Version
3 | module Var = OpamVariable
4 |
5 | let is_pinned_version = Opam_utils.is_pinned_version
6 |
7 | open Utils
8 |
9 | type pkg = {
10 | name : Name.t;
11 | version : Version.t;
12 | opamfile : string;
13 | opam : OpamFile.OPAM.t Lazy.t;
14 | prefix : string;
15 | }
16 |
17 | type t = {
18 | self : pkg;
19 | ocaml_version : Version.t;
20 | pkgs : pkg Name_map.t;
21 | vars : OpamTypes.variable_contents Var.Full.Map.t;
22 | }
23 |
24 | let make_pkg ~name ~version ~opamfile ~prefix =
25 | let opam =
26 | lazy OpamFile.(OPAM.(read (make (OpamFilename.of_string opamfile))))
27 | in
28 | { name; version; opamfile; opam; prefix }
29 |
30 | let make ~deps ?(vars = Var.Full.Map.empty) ~ocaml_version self =
31 | let pkgs = Name_map.add self.name self deps in
32 | { self; ocaml_version; pkgs; vars }
33 |
34 | let deps_of_onix_path ~ocaml_version onix_path =
35 | if String.length onix_path = 0 then Name_map.empty
36 | else
37 | let onix_pkg_dirs = String.split_on_char ':' onix_path in
38 | List.fold_left
39 | (fun acc onix_pkg_dir ->
40 | let { Nix_utils.pkg_name; pkg_version; prefix; _ } =
41 | Nix_utils.parse_store_path onix_pkg_dir
42 | in
43 | let name = Name.of_string pkg_name in
44 | let version = Version.of_string pkg_version in
45 | (* This is the installed opam file and not the one from repo. *)
46 | let opamfile =
47 | Paths.lib ~pkg_name:name ~ocaml_version onix_pkg_dir ^ "/opam"
48 | in
49 | let pkg = make_pkg ~name ~version ~opamfile ~prefix in
50 | Name_map.add name pkg acc)
51 | Name_map.empty onix_pkg_dirs
52 |
53 | let with_onix_path ~onix_path ?vars ~ocaml_version self =
54 | let deps = deps_of_onix_path ~ocaml_version onix_path in
55 | make ~deps ?vars ~ocaml_version self
56 |
57 | (* let get_opam name scope =
58 | match Name_map.find_opt name scope.pkgs with
59 | | None -> None
60 | | Some pkg -> Some (Lazy.force pkg.opam) *)
61 |
62 | (* Variable resolvers *)
63 |
64 | open struct
65 | let string = Var.string
66 | let bool = Var.bool
67 | let string' x = Some (Var.string x)
68 | let bool' x = Some (Var.bool x)
69 | end
70 |
71 | let resolve_global ?(system : System.t option) ?jobs ?user ?group full_var =
72 | if Var.Full.(scope full_var <> Global) then None
73 | else
74 | let var = Var.Full.variable full_var in
75 | match Var.to_string var with
76 | (* Static *)
77 | | "opam-version" -> string' OpamVersion.(to_string current)
78 | | "root" -> string' "/tmp/onix-opam-root"
79 | | "make" -> string' "make"
80 | | "os-distribution" -> string' "nixos"
81 | | "os-family" -> string' "nixos"
82 | | "os-version" -> string' "unknown"
83 | (* Dynamic *)
84 | | "jobs" -> Option.map string jobs
85 | | "arch" -> (
86 | match system with
87 | | Some system -> string' system.arch
88 | | None -> None)
89 | | "os" -> (
90 | match system with
91 | | Some system -> string' system.os
92 | | None -> None)
93 | | "user" -> Option.map string user
94 | | "group" -> Option.map string group
95 | | _ -> None
96 |
97 | let resolve_global_host =
98 | let jobs = Nix_utils.get_nix_build_jobs () in
99 | let user = "onix" in
100 | let group = None in
101 | resolve_global ~jobs ~system:System.host ~user ?group
102 |
103 | let resolve_system ?arch ?os full_var =
104 | if Var.Full.(scope full_var <> Global) then None
105 | else
106 | let var = Var.Full.variable full_var in
107 | match Var.to_string var with
108 | | "arch" -> Option.map string arch
109 | | "os" -> Option.map string os
110 | | _ -> None
111 |
112 | let resolve_pkg ~build_dir { self; pkgs; ocaml_version; _ } full_var =
113 | let var = Var.to_string (Var.Full.variable full_var) in
114 | let scope =
115 | (* G=Global, I=Installed, M=Missing*)
116 | match Var.Full.scope full_var with
117 | | Global -> `G
118 | | Self -> `I self
119 | | Package name -> (
120 | match Name_map.find_opt name pkgs with
121 | | Some pkg -> `I pkg
122 | | None -> `M name)
123 | in
124 | let open Paths in
125 | match (scope, var) with
126 | (* Package metadata *)
127 | | `G, "name" -> string' (Name.to_string self.name)
128 | | `I pkg, "name" -> string' (Name.to_string pkg.name)
129 | | `M name, "name" -> string' (Name.to_string name)
130 | | `G, "version" -> string' (Version.to_string self.version)
131 | | `I pkg, "version" -> string' (Version.to_string pkg.version)
132 | | `G, ("pinned" | "dev") -> bool' (is_pinned_version self.version)
133 | | `I pkg, ("pinned" | "dev") -> bool' (is_pinned_version pkg.version)
134 | | `G, "opamfile" -> string' self.opamfile
135 | | `I pkg, "opamfile" -> string' pkg.opamfile
136 | (* Installed/enable *)
137 | | `G, "installed" -> bool' false (* not yet *)
138 | | `I _pkg, "installed" -> bool' true
139 | | `M _name, "installed" -> bool' false
140 | | `I _pkg, "enable" -> string' "enable"
141 | | `M _name, "enable" -> string' "disable"
142 | (* Build info *)
143 | | `G, "build" -> string' build_dir
144 | | `G, "build-id" -> string' self.prefix
145 | | _, "depends" -> None
146 | (* | ":hash" -> None *)
147 | (* Paths *)
148 | | `G, "switch" | `G, "prefix" -> string' self.prefix
149 | | `G, "lib" -> string' (lib ~ocaml_version self.prefix)
150 | | `I pkg, "lib" -> string' (lib ~pkg_name:pkg.name ~ocaml_version pkg.prefix)
151 | | `G, "toplevel" -> string' (toplevel ~ocaml_version self.prefix)
152 | | `I pkg, "toplevel" ->
153 | string' (toplevel ~pkg_name:pkg.name ~ocaml_version pkg.prefix)
154 | | `G, "stublibs" -> string' (stublibs ~ocaml_version self.prefix)
155 | | `I pkg, "stublibs" ->
156 | string' (stublibs ~pkg_name:pkg.name ~ocaml_version pkg.prefix)
157 | | `G, "bin" -> string' (bin self.prefix)
158 | | `I pkg, "bin" -> string' (bin ~pkg_name:pkg.name pkg.prefix)
159 | | `G, "sbin" -> string' (sbin self.prefix)
160 | | `I pkg, "sbin" -> string' (sbin ~pkg_name:pkg.name pkg.prefix)
161 | | `G, "share" -> string' (share self.prefix)
162 | | `I pkg, "share" -> string' (share ~pkg_name:pkg.name pkg.prefix)
163 | | `G, "doc" -> string' (doc self.prefix)
164 | | `I pkg, "doc" -> string' (doc ~pkg_name:pkg.name pkg.prefix)
165 | | `G, "etc" -> string' (etc self.prefix)
166 | | `I pkg, "etc" -> string' (etc ~pkg_name:pkg.name pkg.prefix)
167 | | `G, "man" -> string' (man self.prefix)
168 | | `I pkg, "man" -> string' (man pkg.prefix)
169 | (* OCaml package variables *)
170 | | ( (`I { name; _ } | `M name),
171 | ("preinstalled" | "native" | "native-tools" | "native-dynlink") )
172 | when Opam_utils.is_ocaml_name name -> bool' true
173 | | `G, "sys-ocaml-version" -> string' (Version.to_string ocaml_version)
174 | | _ -> None
175 |
176 | let resolve_opam_pkg pkg full_var =
177 | match Var.Full.to_string full_var with
178 | | "name" -> string' (OpamPackage.name_to_string pkg)
179 | | "version" -> string' (OpamPackage.version_to_string pkg)
180 | | "dev" | "pinned" -> bool' (Opam_utils.is_pinned pkg)
181 | | _ -> None
182 |
183 | (* Use https://docs.ocaml.pro/docs/LIBRARY.opam_format@opam-format.2.0.8/OpamFilter/index.html#val-deps_var_env *)
184 | let resolve_dep ?(build = true) ?(post = false) ?(test = false) ?(doc = false)
185 | ?(dev_setup = false) var =
186 | match Var.Full.to_string var with
187 | | "build" -> bool' build
188 | | "post" -> bool' post
189 | | "with-test" -> bool' test
190 | | "with-doc" -> bool' doc
191 | | "with-dev-setup" -> bool' dev_setup
192 | | _ -> None
193 |
194 | let resolve_stdenv_host = Var.Full.read_from_env
195 |
196 | let resolve_config { self; pkgs; _ } full_var =
197 | let ( > ) = OpamFilename.Op.( / ) in
198 | let resolve_for_package pkg var =
199 | let base =
200 | OpamFilename.Base.of_string (Name.to_string pkg.name ^ ".config")
201 | in
202 | let config_filename =
203 | OpamFilename.create (OpamFilename.Dir.of_string pkg.prefix > "etc") base
204 | in
205 | if OpamFilename.exists config_filename then (
206 | Logs.debug (fun log ->
207 | log "Scope.resolve_config: loading %a..." Opam_utils.pp_filename
208 | config_filename);
209 | let config_file = OpamFile.make config_filename in
210 | let config = OpamFile.Dot_config.read config_file in
211 | OpamFile.Dot_config.variable config var)
212 | else None
213 | in
214 | match Var.Full.(scope full_var, variable full_var) with
215 | | Global, _var -> None
216 | | Self, var -> resolve_for_package self var
217 | | Package pkg_name, var -> (
218 | match Name_map.find_opt pkg_name pkgs with
219 | | Some pkg -> resolve_for_package pkg var
220 | | None -> None)
221 |
222 | let resolve_local local_vars full_var =
223 | match Var.Full.package full_var with
224 | | Some _ -> None
225 | | None -> (
226 | let var = Var.Full.variable full_var in
227 | try
228 | match Var.Map.find var local_vars with
229 | | None -> raise Exit (* Variable explicitly undefined *)
230 | | some -> some
231 | with Not_found -> None)
232 |
233 | let resolve_many resolvers full_var =
234 | let rec loop resolvers =
235 | match resolvers with
236 | | [] -> None
237 | | resolver :: resolvers' ->
238 | let contents = resolver full_var in
239 | if Option.is_some contents then contents else loop resolvers'
240 | in
241 | try loop resolvers with Exit -> None
242 |
--------------------------------------------------------------------------------
/NOTES.md:
--------------------------------------------------------------------------------
1 | # Notes
2 |
3 | ## DUNE_INSTALL_PREFIX
4 |
5 | `export DUNE_INSTALL_PREFIX="$out"` is required for dune to know about the installation directory. Compiling dune with the standard commands from the opam file, but without this results in:
6 |
7 | ```
8 | dune> ./dune.exe install dune
9 | dune> Error: The mandir installation directory is unknown.
10 | dune> Hint: It could be specified with --mandir
11 | dune> make: *** [Makefile:62: install] Error 1
12 | ```
13 |
14 | This no longer seems to be the case? See onix#3a0dd9c221a62a0000c5e83c6cb557de51270780.
15 |
16 |
17 | ## Cache sharing with files
18 |
19 | When copying files use `${./path}` instead of `${builtins.toString ./path}` to avoid having different paths hardcoded in the commands (like a `buildPhase`). This ensures that there are no project-specific inputs for nix.
20 |
21 |
22 | ## OCAMLFIND_DESTDIR
23 |
24 | Packages that use ocamlfind for installation, require that `OCAMLFIND_DESTDIR` is set. For example zarith.
25 |
26 |
27 | ## `sys-ocaml-version`
28 |
29 | Is only used in ocaml-system.3.07 and is not documented.
30 |
31 |
32 | ## opam vars
33 |
34 | Opam variables can occur in multiple places:
35 |
36 | - `build` field
37 | - `depends` field
38 | - `available` field
39 | - other opam filed fields
40 | - `.in` files as defined by the `substs` field in opam file (including patches);
41 |
42 | This means that all of these places need their variables expanded with an appropriate scope. We cannot replace all of the variables during lock context generation because the system generating the lock file might be different from the system building the locked packages.
43 |
44 | It is possible to partially evaluate some fixed variables, that are common between the host and build systems, and delay the evaluation of other variables to the build system. This means that our lock context needs to preserve some variables such as `os`, `arch` and potentially dep vars like `with-test`.
45 |
46 | Performing this for opam file fields is relatively straightforward. See the expansion example below for an example:
47 |
48 | ```example.opam
49 | build: [
50 | ["dune" "subst"] {pinned}
51 |
52 | [
53 | "dune"
54 | "build"
55 | "-p"
56 | name
57 | "-j"
58 | jobs
59 | "@install"
60 | "@runtest" {with-test}
61 | "@doc" {with-doc}
62 | ]
63 |
64 | ["./configure"] {os != "openbsd" & os != "freebsd" & os != "macos"}
65 |
66 | [
67 | "sh"
68 | "-exc"
69 | "LDFLAGS=\"$LDFLAGS -L/usr/local/lib\" CFLAGS=\"$CFLAGS -I/usr/local/include\" ./configure"
70 | ] {os = "openbsd" | os = "freebsd"}
71 |
72 | [
73 | "sh"
74 | "-exc"
75 | "LDFLAGS=\"$LDFLAGS -L/opt/local/lib -L/usr/local/lib\" CFLAGS=\"$CFLAGS -I/opt/local/include -I/usr/local/include\" ./configure"
76 | ] {os = "macos" & os-distribution != "homebrew"}
77 |
78 | [
79 | "sh"
80 | "-exc"
81 | "LDFLAGS=\"$LDFLAGS -L/opt/local/lib -L/usr/local/lib\" CFLAGS=\"$CFLAGS -I/opt/local/include -I/usr/local/include\" ./configure"
82 | ] {os = "macos" & os-distribution = "homebrew" & arch = "x86_64" }
83 |
84 | [
85 | "sh"
86 | "-exc"
87 | "LDFLAGS=\"$LDFLAGS -L/opt/homebrew/lib\" CFLAGS=\"$CFLAGS -I/opt/homebrew/include\" ./configure"
88 | ] {os = "macos" & os-distribution = "homebrew" & arch = "arm64" }
89 |
90 | [make]
91 | ]
92 | ```
93 |
94 | This would translate to:
95 |
96 | ```example.nix
97 | build = with onix.vars; [
98 | (on (os != "openbsd" && os != "freebsd" && os != "macos")
99 | [ "./configure" ])
100 | (on (os == "openbsd" || os == "freebsd") [
101 | "sh"
102 | "-exc"
103 | ''
104 | LDFLAGS="$LDFLAGS -L/usr/local/lib" CFLAGS="$CFLAGS -I/usr/local/include" ./configure''
105 | ])
106 | (on (os == "macos" && arch == "x86_64") [
107 | "sh"
108 | "-exc"
109 | ''
110 | LDFLAGS="$LDFLAGS -L/opt/local/lib -L/usr/local/lib" CFLAGS="$CFLAGS -I/opt/local/include -I/usr/local/include" ./configure''
111 | ])
112 | (on (os == "macos" && arch == "arm64") [
113 | "sh"
114 | "-exc"
115 | ''
116 | LDFLAGS="$LDFLAGS -L/opt/homebrew/lib" CFLAGS="$CFLAGS -I/opt/homebrew/include" ./configure''
117 | ])
118 | [ "make" ]
119 | ];
120 | ```
121 |
122 | We delay the evaluation of `os` and `arch` variables.
123 |
124 | A bigger challenge is (partially?) evaluating variables in `.in` files. These files aren't always included in the opam repository, for example the `gmp` package applies substs to a file from the source archive.
125 |
126 | Fetching all source archives just for variable substitution during lock context genreation is not acceptable.
127 |
128 | Even if we could fetch all sources, some variables would still need to be evaluated during build time.
129 |
130 | The only viable solution is to completely delay variable evaluation to build time for most fields/files. This can be done by introducing a builder powered by opam libraries, like the `onix opam-build` command or by implementing a small build-time Nix module.
131 |
132 |
133 | # Var resolution for package installs
134 |
135 | Why does opam allow looking custom local stateful switch vars when installing packages?
136 |
137 | - https://github.com/ocaml/opam/blob/601e244409c93c1f4b1cc509a82221484f77537d/src/state/opamPackageVar.ml#L217
138 | - https://github.com/ocaml/opam/blob/601e244409c93c1f4b1cc509a82221484f77537d/src/client/opamAction.ml#L519
139 |
140 | This even applies to .in files.
141 |
142 |
143 | # Solver errors and `post` var
144 |
145 | If post is set to `true` in filter_deps env, opam-0install does not show the full error when an unknown package is detected.
146 |
147 | With post=true:
148 |
149 | ```
150 | Main.exe: [DEBUG] Target packages: ocaml-system example
151 | Can't find all required versions.
152 | Selected: base-bigarray.base base-threads.base base-unix.base ocaml.5.1.0
153 | ocaml-config.3 ocaml-system&example ocaml-base-compiler
154 | ocaml-base-compiler
155 | - example -> (problem)
156 | Rejected candidates:
157 | example.dev: Requires ocaml >= 4.08 & < 5.0
158 | - ocaml-base-compiler -> (problem)
159 | Rejected candidates:
160 | ocaml-base-compiler.5.0.0~alpha1: In same conflict class (ocaml-core-compiler) as ocaml-system
161 | ocaml-base-compiler.5.0.0~alpha0: In same conflict class (ocaml-core-compiler) as ocaml-system
162 | ocaml-base-compiler.4.14.0: In same conflict class (ocaml-core-compiler) as ocaml-system
163 | ocaml-base-compiler.4.14.0~rc2: In same conflict class (ocaml-core-compiler) as ocaml-system
164 | ocaml-base-compiler.4.14.0~rc1: In same conflict class (ocaml-core-compiler) as ocaml-system
165 | ...
166 | - ocaml-system -> ocaml-system.4.14.0
167 | User requested = 4.14.0
168 | ```
169 |
170 | With post=false:
171 |
172 | ```
173 | Main.exe: [DEBUG] Target packages: ocaml-system example
174 | Can't find all required versions.
175 | Selected: example.dev ocaml-config.3 ocaml-system&example ocaml-base-compiler
176 | ocaml-base-compiler
177 | - ocaml -> ocaml.4.14.1
178 | example dev requires >= 4.08 & < 5.0
179 | - ocaml-base-compiler -> (problem)
180 | Rejected candidates:
181 | ocaml-base-compiler.5.0.0~alpha1: In same conflict class (ocaml-core-compiler) as ocaml-system
182 | ocaml-base-compiler.5.0.0~alpha0: In same conflict class (ocaml-core-compiler) as ocaml-system
183 | ocaml-base-compiler.4.14.0: In same conflict class (ocaml-core-compiler) as ocaml-system
184 | ocaml-base-compiler.4.14.0~rc2: In same conflict class (ocaml-core-compiler) as ocaml-system
185 | ocaml-base-compiler.4.14.0~rc1: In same conflict class (ocaml-core-compiler) as ocaml-system
186 | ...
187 | - ocaml-system -> ocaml-system.4.14.0
188 | User requested = 4.14.0
189 | - xxx -> (problem)
190 | No known implementations at all
191 | ```
192 |
193 | Why is ocaml-base-compiler added in the first place?
194 |
195 |
196 | # IFD
197 |
198 | With the onix driven build process, the package derivation could be fully generated by onix.
199 |
200 | ```
201 | onix gen-nix-drv
202 | stdenv.mkDerivation {
203 | ...
204 | }
205 | ```
206 |
207 | - This can be now used to import and evaluate the final package build.
208 | - This reduces the multiple calls to onix opam actions (patch, build, install).
209 |
210 | ## onix-less build is not an option...
211 |
212 | Ok, we can:
213 |
214 | - generate pure nix representation of opam files;
215 | - include platform-specific conditions for opam fields by using partial evaluation of formulas;
216 | - recreate the varible resolution scope in nix;
217 | - apply substs to files using pure nix by matching vars in files with regex;
218 |
219 |
220 | But ultimately we still need to parse opam files to complete the variable subst from .config files.
221 | See: https://opam.ocaml.org/doc/Manual.html#lt-pkgname-gt-config
222 |
223 | This seems too much just to avoid having onix available as a build runtime during build time...
224 |
225 | On the other hand, we could read .config files from the opam repo and include them in the lock context,
226 | but I think they can be part of the source code and thus would require fetching ALL sources to generate
227 | the lock to lookup the .config files. Even that would not be enough because they could be generated by
228 | the build action.
229 |
230 | Conclusion: it is impossible to avoid parsing opam format during build time.
231 |
232 | Is it worth implementing a basic opam parser in nix or a small language like awk? Bringing in
233 | something like opam2json (in OCaml) defeats the purpose of not requiring heavy tooling during build.
234 |
235 | We could implement an opam2json in awk, but... no.
236 |
--------------------------------------------------------------------------------
/tests/Test_vars.ml:
--------------------------------------------------------------------------------
1 | open Onix_core
2 |
3 | let onix_path =
4 | String.concat ":"
5 | [
6 | "/nix/store/j49d3wydfm41n5mb4hlhkx3iv2fy92zd-ocaml-config-2";
7 | "/nix/store/ad91sfjyk923k4z67b0sl3s5wl9xf18f-ocaml-base-compiler-4.14.0";
8 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1";
9 | "/nix/store/0f8xjcgi3611n74hxp7sd0bpn4zl4vcl-cmdliner-1.1.1";
10 | "/nix/store/i26f26cqb43wb0kvk7syv8sknai0cp54-dune-3.1.1";
11 | "/nix/store/76l042jhbmp4pavfj91fc3q5835zd1s2-easy-format-1.3.2";
12 | "/nix/store/rvfm6288jihfm78z4gpcdgxqkidl41f8-fpath-0.7.3";
13 | "/nix/store/graxs35pqmmmli4jf65jzc0drnwdz5kv-ocaml-4.14.0";
14 | "/nix/store/f91m283sqzh3g0hzcxh3fw7yc7piadlc-opam-0install-0.4.3";
15 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev";
16 | "/nix/store/vwaav64li06cmrgjvrg0w8r6xmbrv8hx-uri-4.2.0";
17 | "/nix/store/i7hmg44cvnfq0xa0f9dm1hx2262j9vyf-yojson-1.7.0";
18 | ]
19 |
20 | let ocaml_version = OpamPackage.Version.of_string "4.14.0"
21 |
22 | let self =
23 | Scope.make_pkg
24 | ~name:(OpamPackage.Name.of_string "onix-example")
25 | ~version:(OpamPackage.Version.of_string "root")
26 | ~opamfile:"/nix/store/93l01ab4xqjn6q4n0nf25yasp8jf2jhv-onix-example.opam"
27 | ~prefix:"/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root"
28 |
29 | let pkg_scope = Scope.with_onix_path ~onix_path ~ocaml_version self
30 |
31 | let eq_pkg_name n1 n2 =
32 | let eq = OpamPackage.Name.equal n1 n2 in
33 | if not eq then
34 | Fmt.epr "Package names not equal: %S and %S@."
35 | (OpamPackage.Name.to_string n1)
36 | (OpamPackage.Name.to_string n2);
37 | eq
38 |
39 | let eq_pkg_v v1 v2 =
40 | let eq = OpamPackage.Version.equal v1 v2 in
41 | if not eq then
42 | Fmt.epr "Package versions not equal: %S and %S@."
43 | (OpamPackage.Version.to_string v1)
44 | (OpamPackage.Version.to_string v2);
45 | eq
46 |
47 | let mk_pkg_name = OpamPackage.Name.of_string
48 | let mk_pkg_v = OpamPackage.Version.of_string
49 |
50 | let check_scope () =
51 | let check_pkg pkg_name =
52 | let mem =
53 | OpamPackage.Name.Map.mem
54 | (OpamPackage.Name.of_string pkg_name)
55 | pkg_scope.pkgs
56 | in
57 | if not mem then (
58 | Fmt.epr "Missing package in scope: %S@." pkg_name;
59 | raise Exit)
60 | in
61 | List.iter check_pkg
62 | [
63 | "onix-example";
64 | "bos";
65 | "cmdliner";
66 | "dune";
67 | "easy-format";
68 | "fpath";
69 | "ocaml";
70 | "opam-0install";
71 | "options";
72 | "uri";
73 | "yojson";
74 | ]
75 |
76 | let check_self () =
77 | let self = pkg_scope.self in
78 | assert (eq_pkg_name (mk_pkg_name "onix-example") self.name);
79 | assert (eq_pkg_v (mk_pkg_v "root") self.version)
80 |
81 | let check_vars () =
82 | let resolve =
83 | let jobs = "1" in
84 | let system = { System.arch = "my_arch"; os = "os" } in
85 | let user = "my_user" in
86 | let group = "my_group" in
87 | let build_dir = "/build" in
88 |
89 | Scope.resolve_many
90 | [
91 | Scope.resolve_global ~jobs ~system ~user ~group;
92 | Scope.resolve_pkg ~build_dir pkg_scope;
93 | ]
94 | in
95 |
96 | let check_var var_str expected =
97 | let full_var = OpamVariable.Full.of_string var_str in
98 | let actual =
99 | match resolve full_var with
100 | | Some var_contents ->
101 | OpamVariable.string_of_variable_contents var_contents
102 | | None -> ""
103 | in
104 | let eq = String.equal expected actual in
105 | if not eq then (
106 | Fmt.epr "Variable %S has incorrect value: expected=%S actual=%S@." var_str
107 | expected actual;
108 | raise Exit)
109 | in
110 | (* Global vars *)
111 | check_var "name" "onix-example";
112 | check_var "version" "root";
113 | check_var "make" "make";
114 | check_var "prefix"
115 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root";
116 | check_var "switch"
117 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root";
118 | (* check_var "root" *)
119 | (* "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root"; *)
120 | check_var "sys-ocaml-version" "4.14.0";
121 |
122 | (* check_var "user" (Sys.getenv "USER"); *)
123 |
124 | (* Self package *)
125 | check_var "installed" "false";
126 | check_var "pinned" "false";
127 | check_var "dev" "false";
128 | check_var "opamfile"
129 | "/nix/store/93l01ab4xqjn6q4n0nf25yasp8jf2jhv-onix-example.opam";
130 | check_var "lib"
131 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/lib/ocaml/4.14.0/site-lib";
132 | check_var "stublibs"
133 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/lib/ocaml/4.14.0/site-lib/stublibs";
134 | check_var "toplevel"
135 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/lib/ocaml/4.14.0/site-lib/toplevel";
136 | check_var "man"
137 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/man";
138 | check_var "doc"
139 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/doc";
140 | check_var "share"
141 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/share";
142 | check_var "etc"
143 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/etc";
144 |
145 | check_var "_:installed" "true";
146 | check_var "_:pinned" "false";
147 | check_var "_:dev" "false";
148 | check_var "_:opamfile"
149 | "/nix/store/93l01ab4xqjn6q4n0nf25yasp8jf2jhv-onix-example.opam";
150 | check_var "_:lib"
151 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/lib/ocaml/4.14.0/site-lib/onix-example";
152 | check_var "_:stublibs"
153 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/lib/ocaml/4.14.0/site-lib/stublibs/onix-example";
154 | check_var "_:toplevel"
155 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/lib/ocaml/4.14.0/site-lib/toplevel/onix-example";
156 | check_var "_:man"
157 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/man";
158 | check_var "_:doc"
159 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/doc/onix-example";
160 | check_var "_:share"
161 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/share/onix-example";
162 | check_var "_:etc"
163 | "/nix/store/yzy5ip0v895v7s2ld4i1dcv00cl8b7zf-onix-example-root/etc/onix-example";
164 |
165 | (* Pinned package *)
166 | check_var "options:name" "options";
167 | check_var "options:version" "dev";
168 | check_var "options:installed" "true";
169 | check_var "options:pinned" "true";
170 | check_var "options:dev" "true";
171 | check_var "options:opamfile"
172 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev/lib/ocaml/4.14.0/site-lib/options/opam";
173 | check_var "options:lib"
174 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev/lib/ocaml/4.14.0/site-lib/options";
175 | check_var "options:stublibs"
176 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev/lib/ocaml/4.14.0/site-lib/stublibs/options";
177 | check_var "options:toplevel"
178 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev/lib/ocaml/4.14.0/site-lib/toplevel/options";
179 | check_var "options:man"
180 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev/man";
181 | check_var "options:doc"
182 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev/doc/options";
183 | check_var "options:share"
184 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev/share/options";
185 | check_var "options:etc"
186 | "/nix/store/qvnnk93pgl184021bbysp7036rzx30rh-options-dev/etc/options";
187 |
188 | (* Not installed package *)
189 | check_var "_not_a_package:name" "_not_a_package";
190 | (* check_var "_not_a_package:name" ""; *)
191 | check_var "_not_a_package:version" "";
192 | check_var "_not_a_package:installed" "false";
193 | check_var "_not_a_package:pinned" "";
194 | check_var "_not_a_package:dev" "";
195 | check_var "_not_a_package:opamfile" "";
196 | check_var "_not_a_package:build-id" "";
197 | check_var "_not_a_package:lib" "";
198 | check_var "_not_a_package:stublibs" "";
199 | check_var "_not_a_package:toplevel" "";
200 | check_var "_not_a_package:man" "";
201 | check_var "_not_a_package:doc" "";
202 | check_var "_not_a_package:share" "";
203 | check_var "_not_a_package:etc" "";
204 |
205 | (* Installed package *)
206 | check_var "bos:name" "bos";
207 | check_var "bos:version" "0.2.1";
208 | check_var "bos:installed" "true";
209 | check_var "bos:pinned" "false";
210 | check_var "bos:dev" "false";
211 | check_var "bos:opamfile"
212 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1/lib/ocaml/4.14.0/site-lib/bos/opam";
213 | check_var "bos:build-id" "";
214 | check_var "bos:lib"
215 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1/lib/ocaml/4.14.0/site-lib/bos";
216 | check_var "bos:stublibs"
217 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1/lib/ocaml/4.14.0/site-lib/stublibs/bos";
218 | check_var "bos:toplevel"
219 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1/lib/ocaml/4.14.0/site-lib/toplevel/bos";
220 | check_var "bos:man"
221 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1/man";
222 | check_var "bos:doc"
223 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1/doc/bos";
224 | check_var "bos:share"
225 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1/share/bos";
226 | check_var "bos:etc"
227 | "/nix/store/xfmk9f2ykalizkgfg620gbya67fa09si-bos-0.2.1/etc/bos";
228 |
229 | check_var "ocaml-config:share"
230 | "/nix/store/j49d3wydfm41n5mb4hlhkx3iv2fy92zd-ocaml-config-2/share/ocaml-config"
231 |
232 | let () =
233 | check_scope ();
234 | check_self ();
235 | check_vars ()
236 |
--------------------------------------------------------------------------------
/src/onix_lock_nix/Pp.ml:
--------------------------------------------------------------------------------
1 | open Onix_core
2 | open Onix_core.Utils
3 |
4 | let install_phase_str_for_install_files ~ocaml_version =
5 | Fmt.str
6 | {|
7 |
8 | ${nixpkgs.opaline}/bin/opaline \
9 | -prefix="$out" \
10 | -libdir="$out/lib/ocaml/%s/site-lib"|}
11 | (OpamPackage.Version.to_string ocaml_version)
12 |
13 | let install_phase_str_for_config_files ~package_name =
14 | Fmt.str
15 | {|
16 | if [[ -e "./%s.config" ]]; then
17 | mkdir -p "$out/etc"
18 | cp "./%s.config" "$out/etc/%s.config"
19 | fi|}
20 | (OpamPackage.Name.to_string package_name)
21 | (OpamPackage.Name.to_string package_name)
22 | (OpamPackage.Name.to_string package_name)
23 |
24 | let pp_string_escape_with_enderscore formatter str =
25 | if Utils.String.starts_with_number str then Fmt.string formatter ("_" ^ str)
26 | else Fmt.Dump.string formatter str
27 |
28 | let pp_name_escape_with_enderscore formatter name =
29 | let name = OpamPackage.Name.to_string name in
30 | pp_string_escape_with_enderscore formatter name
31 |
32 | let pp_string_escape_quotted formatter str =
33 | if Utils.String.starts_with_number str then Fmt.Dump.string formatter str
34 | else Fmt.string formatter str
35 |
36 | let pp_version f version =
37 | let version = OpamPackage.Version.to_string version in
38 | (* We require that the version does NOT contain any '-' or '~' characters.
39 | - Note that nix will replace '~' to '-' automatically.
40 | The version is parsed with Nix_utils.parse_store_path by splitting bytes
41 | '- ' to obtain the Scope.package information.
42 | This is fine because the version in the lock file is mostly informative. *)
43 | let set_valid_char i =
44 | match String.get version i with
45 | | '-' | '~' -> '+'
46 | | valid -> valid
47 | in
48 | let version = String.init (String.length version) set_valid_char in
49 | Fmt.pf f "%S" version
50 |
51 | let pp_hash f (kind, hash) =
52 | match kind with
53 | | `SHA256 -> Fmt.pf f "sha256 = %S" hash
54 | | `SHA512 -> Fmt.pf f "sha512 = %S" hash
55 | | `MD5 -> Fmt.pf f "md5 = %S" hash
56 |
57 | let _pp_src ~gitignore f (t : Lock_pkg.t) =
58 | if Opam_utils.Opam_details.check_has_absolute_path t.opam_details then
59 | match t.src with
60 | | None -> Fmt.pf f "@,src = null;@,dontUnpack = true;"
61 | | Some (Git { url; rev }) ->
62 | Fmt.pf f
63 | "@,\
64 | src = @[builtins.fetchGit {@ url = %S;@ rev = %S;@ allRefs = \
65 | true;@]@ };"
66 | url rev
67 | (* MD5 hashes are not supported by Nix fetchers. Fetch without hash.
68 | This normally would not happen as we try to prefetch_src_if_md5. *)
69 | | Some (Http { url; hash = `MD5, _ }) ->
70 | Logs.warn (fun log ->
71 | log "Ignoring hash for %a. MD5 hashes are not supported by nix."
72 | Opam_utils.pp_package t.opam_details.package);
73 | Fmt.pf f "@,src = @[fetchurl {@ url = %a;@]@ };"
74 | (Fmt.quote Opam_utils.pp_url)
75 | url
76 | | Some (Http { url; hash }) ->
77 | Fmt.pf f "@,src = @[nixpkgs.fetchurl {@ url = %a;@ %a;@]@ };"
78 | (Fmt.quote Opam_utils.pp_url)
79 | url pp_hash hash
80 | else
81 | let path =
82 | let opam_path = t.opam_details.Opam_utils.path in
83 | let path = OpamFilename.(Dir.to_string (dirname opam_path)) in
84 | if String.equal path "." then "./../../.." else path
85 | in
86 | if gitignore then
87 | Fmt.pf f "@,src = nixpkgs.nix-gitignore.gitignoreSource [] %s;" path
88 | else Fmt.pf f "@,src = ./../../..;"
89 |
90 | let pp_src f (t : Lock_pkg.t) =
91 | if Opam_utils.Opam_details.check_has_absolute_path t.opam_details then
92 | (* Absolute path: use src. *)
93 | match t.src with
94 | | None -> ()
95 | | Some (Git { url; rev }) ->
96 | Fmt.pf f ",@,@[src = {@,url = \"git+%s\",@, rev = %S@]@,};" url rev
97 | (* MD5 hashes are not supported by Nix fetchers. Fetch without hash.
98 | This normally would not happen as we try to prefetch_src_if_md5. *)
99 | | Some (Http { url; hash = `MD5, _ }) ->
100 | Fmt.invalid_arg "Unexpected md5 hash: package=%a url=%a"
101 | Opam_utils.pp_package t.opam_details.package Opam_utils.pp_url url
102 | | Some (Http { url; hash }) ->
103 | Fmt.pf f ",@,@[src = {@,url = %a;@,%a;@]@,};"
104 | (Fmt.quote Opam_utils.pp_url)
105 | url pp_hash hash
106 | else
107 | (* Relative path: use file scheme. *)
108 | let path =
109 | let opam_path = t.opam_details.Opam_utils.path in
110 | let path = OpamFilename.(Dir.to_string (dirname opam_path)) in
111 | if String.equal path "./." || String.equal path "./" then "." else path
112 | in
113 | Fmt.pf f ",@,src = { url = \"file://%s\"; };" path
114 |
115 | let pp_depexts f req =
116 | let pp_req f =
117 | String_set.iter (fun dep ->
118 | Fmt.pf f "@ %a" pp_string_escape_with_enderscore dep)
119 | in
120 | if String_set.is_empty req then ()
121 | else Fmt.pf f "@ depexts = with nixpkgs; [@[%a@ @]];" pp_req req
122 |
123 | let pp_depends name f req =
124 | let pp_req f =
125 | Name_set.iter (fun dep ->
126 | Fmt.pf f "%a@ " pp_name_escape_with_enderscore dep)
127 | in
128 | if Name_set.is_empty req then ()
129 | else Fmt.pf f "@,@[%s = [@,@[%a@]@]@,];" name pp_req req
130 |
131 | let pp_file_inputs f inputs =
132 | let pp_inputs =
133 | Fmt.iter ~sep:Fmt.comma String_set.iter pp_string_escape_with_enderscore
134 | in
135 | Fmt.pf f "@[{ %a@] }" pp_inputs inputs
136 |
137 | let pp_ocamlfind_setup_hook f setup_hook =
138 | match setup_hook with
139 | | None -> ()
140 | | Some setup_hook ->
141 | Fmt.pf f {|@,setupHook = writeText "setup-hook.sh" ''%s '';|} setup_hook
142 |
143 | let pp_patches f patches =
144 | if List.is_empty patches then ()
145 | else
146 | let paths =
147 | List.map (fun base -> "./" ^ OpamFilename.Base.to_string base) patches
148 | in
149 | Fmt.pf f {|@,@[patches = [@,%a@]@,];|} (Fmt.list Fmt.string) paths
150 |
151 | (* Printers for the nix commands with filters. *)
152 |
153 | let pp_command f (command : string list * OpamTypes.filter option) =
154 | match command with
155 | | args_str, None ->
156 | Fmt.pf f "[%a]" (Fmt.using command_to_string Fmt.string) args_str
157 | | args_str, Some filter ->
158 | Fmt.pf f "[@[[%a] { when = ''%s''; }]@]"
159 | (Fmt.using command_to_string Fmt.string)
160 | args_str
161 | (Nix_filter.opam_filter_to_nix_string filter)
162 |
163 | let pp_commands f (commands : (string list * OpamTypes.filter option) list) =
164 | if List.is_empty commands then ()
165 | else Fmt.pf f "@,@[build = [@,%a@]@,];" (Fmt.list pp_command) commands
166 |
167 | let pp_extra_files formatter extra_files =
168 | if List.is_not_empty extra_files then
169 | let extra_files =
170 | List.map
171 | (fun file -> "./" ^ OpamFilename.(Base.to_string (basename file)))
172 | extra_files
173 | in
174 | Fmt.pf formatter "@,@[extra-files = [@,%a@]@,];" (Fmt.list Fmt.string)
175 | extra_files
176 |
177 | (* Package printer *)
178 |
179 | let pp_many (formatter : Format.formatter) list =
180 | List.iter (fun pp -> pp formatter) list
181 |
182 | let pp_item (pp : 'a Fmt.t) x formatter = pp formatter x
183 |
184 | (* TODO check ~gitignore arg *)
185 | let pp_pkg ~gitignore:_ f (nix_pkg : Nix_pkg.t) =
186 | let lock_pkg = nix_pkg.lock_pkg in
187 | let name = OpamPackage.name_to_string lock_pkg.opam_details.package in
188 | let version = OpamPackage.version lock_pkg.opam_details.package in
189 | Fmt.pf f "@[{@ name = %S;@ version = %a;%a@]@,}@." name pp_version version
190 | pp_many
191 | [
192 | pp_item pp_src lock_pkg;
193 | pp_item pp_patches nix_pkg.patches;
194 | pp_item (pp_depends "depends") nix_pkg.lock_pkg.depends;
195 | pp_item (pp_depends "build-depends") nix_pkg.lock_pkg.depends_build;
196 | pp_item (pp_depends "test-depends") nix_pkg.lock_pkg.depends_test;
197 | pp_item (pp_depends "doc-depends") nix_pkg.lock_pkg.depends_doc;
198 | pp_item
199 | (pp_depends "dev-setup-depends")
200 | nix_pkg.lock_pkg.depends_dev_setup;
201 | pp_item pp_depexts nix_pkg.lock_pkg.depexts_nix;
202 | pp_item pp_commands nix_pkg.build;
203 | pp_item pp_extra_files nix_pkg.extra_files;
204 | ]
205 |
206 | let pp_version f version = Fmt.pf f "version = %S;" version
207 |
208 | let pp_repo_uri f repo_url =
209 | match repo_url.OpamUrl.hash with
210 | | Some rev ->
211 | Fmt.pf f "@[repo = builtins.fetchGit {@ url = %a;@ rev = %S;@]@,};"
212 | (Fmt.quote Opam_utils.pp_url)
213 | { repo_url with OpamUrl.hash = None }
214 | rev
215 | | None ->
216 | Fmt.invalid_arg "Repo URI without fragment: %a" Opam_utils.pp_url repo_url
217 |
218 | let pp_index_pkg formatter (lock_pkg : Lock_pkg.t) =
219 | let name_str = OpamPackage.name_to_string lock_pkg.opam_details.package in
220 | let version = OpamPackage.version lock_pkg.opam_details.package in
221 | match name_str with
222 | | "ocaml-system" ->
223 | let nixpkgs_ocaml = Nix_utils.make_ocaml_packages_path version in
224 | Fmt.pf formatter "\"ocaml-system\" = nixpkgs.%s;" nixpkgs_ocaml
225 | | _ ->
226 | Fmt.pf formatter "%S = self.callPackage ./packages/%s { };" name_str
227 | name_str
228 |
229 | let pp_index f lock_pkgs =
230 | Fmt.pf f
231 | {|{ nixpkgs ? import { }, overlay ? import ./overlay nixpkgs }:
232 |
233 | let
234 | newScope = onixpkgs:
235 | nixpkgs.lib.callPackageWith ({ inherit nixpkgs; } // onixpkgs);
236 |
237 | packages = nixpkgs.lib.makeScope newScope (self: {@[ %a@]@,});
238 |
239 | in packages.overrideScope' overlay@.|}
240 | Fmt.(list ~sep:cut pp_index_pkg)
241 | lock_pkgs
242 |
--------------------------------------------------------------------------------
/src/onix/Main.ml:
--------------------------------------------------------------------------------
1 | let ( or ) opt default =
2 | match opt with
3 | | Some x -> x
4 | | None -> default
5 |
6 | let setup_logs style_renderer log_level =
7 | Fmt_tty.setup_std_outputs ?style_renderer ();
8 | Logs.set_level log_level;
9 | Logs.set_reporter (Logs_fmt.reporter ())
10 |
11 | open Cmdliner
12 |
13 | let ocaml_version_arg =
14 | let doc = "The version of OCaml to be used." in
15 | let docv = "VERSION" in
16 | Arg.(info ["ocaml-version"] ~docv ~doc |> opt (some string) None |> required)
17 |
18 | let package_prefix_arg =
19 | let doc = "Nix store prefix path of the package (i.e. the $out directory)." in
20 | let docv = "PATH" in
21 | Arg.(info ["path"] ~docv ~doc |> opt (some string) None |> required)
22 |
23 | let opam_package_arg =
24 | let doc = "The package information in the format `name.version'." in
25 | let docv = "PATH" in
26 | Arg.(info [] ~docv ~doc |> pos 0 (some string) None |> required)
27 |
28 | let lock_file_arg =
29 | let doc = "The path to the lock file (by default ./onix-lock.json)." in
30 | let docv = "FILE" in
31 | Arg.(info ["lock-file"] ~docv ~doc |> opt string "./onix-lock.json" |> value)
32 |
33 | let opam_lock_file_arg =
34 | let doc =
35 | "The path to the \".opam.locked\" file. The opam lock file will not be \
36 | generated if this option is not passed."
37 | in
38 | let docv = "FILE" in
39 | Arg.(info ["opam-lock-file"] ~docv ~doc |> opt (some string) None |> value)
40 |
41 | let opam_arg =
42 | let doc = "Path to the opam file of the package to be built." in
43 | let docv = "OPAM" in
44 | Arg.(info ["opam"] ~docv ~doc |> opt (some string) None |> required)
45 |
46 | let with_test_arg =
47 | let doc =
48 | "Include {with-test} constrained packages. Applies to the root packages \
49 | only."
50 | in
51 | Arg.(info ["with-test"] ~doc |> opt bool false |> value)
52 |
53 | let with_doc_arg =
54 | let doc =
55 | "Include {with-doc} constrained packages. Applies to the root packages \
56 | only."
57 | in
58 | Arg.(info ["with-doc"] ~doc |> opt bool false |> value)
59 |
60 | let with_dev_setup_arg =
61 | let doc =
62 | "Include {with-dev-setup} constrained packages. Applies to the root \
63 | packages only."
64 | in
65 | Arg.(info ["with-dev-setup"] ~doc |> opt bool false |> value)
66 |
67 | let make_scope ~ocaml_version ~opamfile ~prefix ~opam_pkg () =
68 | let onix_path = Sys.getenv_opt "ONIXPATH" or "" in
69 | let ocaml_version = OpamPackage.Version.of_string ocaml_version in
70 | let opam_pkg = OpamPackage.of_string opam_pkg in
71 | let self =
72 | Onix_core.Scope.make_pkg ~name:opam_pkg.name ~version:opam_pkg.version
73 | ~opamfile ~prefix
74 | in
75 | Onix_core.Scope.with_onix_path ~onix_path ~ocaml_version self
76 |
77 | module Opam_patch = struct
78 | let run style_renderer log_level ocaml_version opamfile prefix opam_pkg =
79 | setup_logs style_renderer log_level;
80 | Logs.info (fun log ->
81 | log "opam-patch: Running... pkg=%S ocaml=%S prefix=%S opam=%S" opam_pkg
82 | ocaml_version prefix opamfile);
83 | let scope = make_scope ~ocaml_version ~opamfile ~prefix ~opam_pkg () in
84 | Onix_core.Opam_actions.patch scope;
85 | Logs.info (fun log -> log "opam-patch: Done.")
86 |
87 | let info = Cmd.info "opam-patch" ~doc:"Apply opam package patches."
88 |
89 | let cmd =
90 | Cmd.v info
91 | Term.(
92 | const run
93 | $ Fmt_cli.style_renderer ()
94 | $ Logs_cli.level ~env:(Cmd.Env.info "ONIX_LOG_LEVEL") ()
95 | $ ocaml_version_arg
96 | $ opam_arg
97 | $ package_prefix_arg
98 | $ opam_package_arg)
99 | end
100 |
101 | module Opam_build = struct
102 | let run style_renderer log_level ocaml_version opamfile with_test with_doc
103 | with_dev_setup prefix opam_pkg =
104 | setup_logs style_renderer log_level;
105 | Logs.info (fun log ->
106 | log "opam-build: Running... pkg=%S ocaml=%S prefix=%S opam=%S" opam_pkg
107 | ocaml_version prefix opamfile);
108 | let scope = make_scope ~ocaml_version ~opamfile ~prefix ~opam_pkg () in
109 | Onix_core.Opam_actions.build ~with_test ~with_doc ~with_dev_setup scope
110 | |> List.iter Onix_core.Utils.Os.run_command;
111 | Logs.info (fun log -> log "opam-build: Done.")
112 |
113 | let info =
114 | Cmd.info "opam-build" ~doc:"Build a package from a package closure file."
115 |
116 | let cmd =
117 | Cmd.v info
118 | Term.(
119 | const run
120 | $ Fmt_cli.style_renderer ()
121 | $ Logs_cli.level ~env:(Cmd.Env.info "ONIX_LOG_LEVEL") ()
122 | $ ocaml_version_arg
123 | $ opam_arg
124 | $ with_test_arg
125 | $ with_doc_arg
126 | $ with_dev_setup_arg
127 | $ package_prefix_arg
128 | $ opam_package_arg)
129 | end
130 |
131 | module Opam_install = struct
132 | let run ocaml_version opamfile with_test with_doc with_dev_setup prefix
133 | opam_pkg =
134 | Logs.info (fun log ->
135 | log "opam-install: Running... pkg=%S ocaml=%S prefix=%S opam=%S"
136 | opam_pkg ocaml_version prefix opamfile);
137 | let scope = make_scope ~ocaml_version ~opamfile ~prefix ~opam_pkg () in
138 | Onix_core.Opam_actions.install ~with_test ~with_doc ~with_dev_setup scope
139 | |> List.iter Onix_core.Utils.Os.run_command;
140 | Logs.info (fun log -> log "opam-install: Done.")
141 |
142 | let info =
143 | Cmd.info "opam-install"
144 | ~doc:"Install a package from a package closure file."
145 |
146 | let cmd =
147 | Cmd.v info
148 | Term.(
149 | const run
150 | $ ocaml_version_arg
151 | $ opam_arg
152 | $ with_test_arg
153 | $ with_doc_arg
154 | $ with_dev_setup_arg
155 | $ package_prefix_arg
156 | $ opam_package_arg)
157 | end
158 |
159 | module Lock = struct
160 | let opam_file_paths_arg =
161 | let doc = "Input opam paths to be used during package resolution." in
162 | Arg.(value & pos_all file [] & info [] ~docv:"PATH" ~doc)
163 |
164 | let graphviz_file_path_arg =
165 | let doc = "Generate a dependency graph and save it at this path." in
166 | let docv = "FILE" in
167 | Arg.(info ["graphviz-file"] ~docv ~doc |> opt (some string) None |> value)
168 |
169 | let repository_urls_arg =
170 | let doc =
171 | "Comma-separated URLs of the OPAM repositories to be used when solving \
172 | the dependencies. Use the following format: \
173 | https://github.com/ocaml/opam-repository.git[#HASH]"
174 | in
175 | let docv = "LIST" in
176 | Arg.(
177 | info ["repository-urls"] ~docv ~doc
178 | |> opt (list string) ["https://github.com/ocaml/opam-repository.git"]
179 | |> value)
180 |
181 | let resolutions_arg =
182 | let conv =
183 | ( Onix_core.Resolutions.parse_resolution,
184 | Onix_core.Resolutions.pp_resolution )
185 | in
186 | let doc =
187 | "Additional packages and version constraints to be used during \
188 | dependency resolution."
189 | in
190 | Arg.info ["resolutions"] ~doc |> Arg.opt (Arg.list conv) [] |> Arg.value
191 |
192 | (* let repository_dir_arg = *)
193 | (* let doc = *)
194 | (* "Local path to the OPAm repository that will be used for package lookup \ *)
195 | (* resolution." *)
196 | (* in *)
197 | (* let docv = "LIST" in *)
198 | (* Arg.( *)
199 | (* info ["repository-dir"] ~docv ~doc |> opt (some string) None |> required) *)
200 |
201 | let is_opam_filename filename =
202 | String.equal (Filename.extension filename) ".opam"
203 | || String.equal (Filename.basename filename) "opam"
204 |
205 | let run style_renderer log_level lock_file_path opam_lock_file_path
206 | repository_urls resolutions with_test with_doc with_dev_setup
207 | opam_file_paths graphviz_file_path =
208 | setup_logs style_renderer log_level;
209 | Logs.info (fun log -> log "lock: Running...");
210 |
211 | let repository_urls = List.map OpamUrl.of_string repository_urls in
212 |
213 | let opam_file_paths =
214 | List.map
215 | (fun path ->
216 | if not (is_opam_filename path) then
217 | Fmt.failwith "Provided input path is not an opam file path.";
218 | (* IMPORTANT: Do not resolve to absolute path. *)
219 | OpamFilename.raw path)
220 | opam_file_paths
221 | in
222 |
223 | let lock_file =
224 | Onix_core.Solver.solve ~repository_urls ~resolutions ~with_test ~with_doc
225 | ~with_dev_setup opam_file_paths
226 | in
227 | Onix_lock_json.gen ~lock_file_path lock_file;
228 |
229 | let () =
230 | match opam_lock_file_path with
231 | | Some opam_lock_file_path ->
232 | Onix_lock_opam.gen ~opam_lock_file_path lock_file
233 | | None -> ()
234 | in
235 |
236 | let () =
237 | match graphviz_file_path with
238 | | Some graphviz_file_path ->
239 | Onix_lock_graphviz.gen ~graphviz_file_path lock_file
240 | | None -> ()
241 | in
242 | Logs.info (fun log -> log "Done.")
243 |
244 | let info = Cmd.info "lock" ~doc:"Solve dependencies and create a lock file."
245 |
246 | let cmd =
247 | Cmd.v info
248 | Term.(
249 | const run
250 | $ Fmt_cli.style_renderer ()
251 | $ Logs_cli.level ~env:(Cmd.Env.info "ONIX_LOG_LEVEL") ()
252 | $ lock_file_arg
253 | $ opam_lock_file_arg
254 | $ repository_urls_arg
255 | $ resolutions_arg
256 | $ with_test_arg
257 | $ with_doc_arg
258 | $ with_dev_setup_arg
259 | $ opam_file_paths_arg
260 | $ graphviz_file_path_arg)
261 | end
262 |
263 | let () =
264 | Printexc.record_backtrace true;
265 | let doc = "Manage OCaml projects with Nix" in
266 | let sdocs = Manpage.s_common_options in
267 |
268 | let info = Cmd.info "onix" ~version:Onix_core.Lib.version ~doc ~sdocs in
269 |
270 | let default =
271 | let run () = `Help (`Pager, None) in
272 | Term.(ret (const run $ const ()))
273 | in
274 | [Lock.cmd; Opam_build.cmd; Opam_install.cmd; Opam_patch.cmd]
275 | |> Cmdliner.Cmd.group info ~default
276 | |> Cmdliner.Cmd.eval
277 | |> Stdlib.exit
278 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 |
2 | # TODO
3 |
4 | - [x] Proper support for opam repo management.
5 | - [x] Implement depexts.
6 | - [x] Warn on md5 hashes.
7 | - [x] Refactor builder.nix.
8 | - [x] Refactor overrides.
9 | - [x] Better depexts mapping:
10 | - https://github.com/tweag/opam-nix/blob/8062dfe742f6636191017232ff504f6765a7f2d1/src/overlays/external/debian.nix#L35
11 | - [x] Filter invalid depexts names.
12 | - [x] Run opam actions natively with Bos.
13 | - [x] Apply "with-test" var when extracting install.
14 | - [x] Use native nixpkgs to build onix itself.
15 | - [x] Use the same compiler for onix and project build.
16 | - [x] Drop fpath, use OpamFilename.´
17 | - [x] Improve logging.
18 | - [x] Properly fix ocaml env var export for deps.
19 | - Using hooks seems is problematic due to multi-export (fails with "Argument list too long").
20 | - Computing the dep closure is more deterministic albeit potentially costly.
21 | - [x] Replace build ctx json with OCAMLPATH lookup.
22 | - [ ] Consider installing the opam file as $libdir/$pkg/onix-opam or even overwritting ./opam.
23 | - [ ] ~~Do we need to include `opam` in build ctx json or can we pass repo-url?~~
24 | - ~~Do we really want opam files for all deps (for opam's `depends` var)?~~
25 | - [ ] ~~Use builtins.filterSource for ./. src to exclude unwanted dirs (gitignore, _build).~~
26 | - [x] Define build/test/dev dependencies separately in lock file?
27 | - [x] Handle strange version names in nix paths (like dream-pure-1.0.0-alpha2).
28 | - So we can correctly set up buildInputs/nativeBuildInputs, etc.
29 | - [x] Changing project's opam file currently triggers full scope rebuild?
30 | - [x] Support gitignore.
31 | - api.lock could be a derivation.
32 | - [x] Support dependency flags: with-build with-test with-doc with-dev/with-tools?
33 | - Expose flags in the api module.
34 | - [x] Pass ignore file as a parameter: --ignore-file=(default=.gitignore).
35 | - [x] Read ONIX_REPO_URL.
36 | - [x] Allow setting dep flags for both the project and the deps.
37 | - [x] Nix store path parsing does not work for `nix develop`: pass nv to actions.
38 | - [x] Ensure valid pkg names (exclude ~).
39 | - [x] Use two sets for depexts: nix * other instead of nix | other.
40 | - [x] Support zip unpacking (tezos?).
41 | - [x] Use builtins.foldl’.
42 | - [x] Handle .config files (like conf-binutils.config). Is this related to opam's `flag: conf`.
43 | - https://github.com/tweag/opam-nix/blob/8062dfe742f6636191017232ff504f6765a7f2d1/src/builder.nix#L358
44 | - Generated on build: https://github.com/ocaml/opam-repository/blob/00777c1a37b2eac5e802e10570a76c89bc5d221e/packages/conf-binutils/conf-binutils.0.3/opam#L9
45 | - The vars from config files should be loaded into env/vars for a package that depends on the package providing the config file.
46 | - The `flags: conf` is probably to indicate to opam to load the .config files after build, if any.
47 | - Could be loaded via OPAM_VAR_xxx with a hook?
48 | - The .config files also contain additional files, do we really want to copy them?
49 | - This is because in some cases they will actually resolve to nix paths.
50 | - [x] An approach alternative to setting OPAM_VAR_xxx is to explicitly lookup vars in Pkg_ctx from the saved `onix-propagated-opam-vars` file.
51 | - [x] Cache the `Dot_config` file as it's likely to be accessed multiple times.
52 | - [x] Use nix-prefetch-url.
53 | - [x] Use nix-prefetch-git for repo fetching.
54 | - [ ] Force symlink resolution of locally vendored deps. Otherwise we get "chmod: changing permissions of 'melange': Operation not permitted"
55 | - [ ] Make sure that incremental/partial changes to the lock file (and opam file) are possible withou full rebuild.
56 | - [x] Make dev tools work: vscode/vim plugins.
57 | - [x] Fix ocaml env vars propagation for shell.
58 | - [ ] ~~Use nix-prefetch-git for pins.~~
59 | - [ ] Remove empty libdir after install (should at least have opam!!)?
60 | - [ ] Install repo's opam, the monads pkg does not install opam for example.
61 | - [ ] Document that opam vars can be set by defining OPAM_VAR_pkg_name.
62 | - [x] Consider using opaline.
63 | - Or we could even intall ourselves with OpamFile.Dot_install.
64 | - https://docs.ocaml.pro/docs/LIBRARY.opam_format@opam-format.2.0.8/OpamFile/Dot_install/index.html
65 | - [ ] Implement onix shell.
66 | - [ ] Implement onix build.
67 | - [x] Add --lock-file argument to actions.
68 | - [ ] Add a command similar to `opam var` to lookup package variables?
69 | - [ ] Improve error-handling.
70 | - [ ] Build any package without a project `onix build utop` or similar.
71 | - [x] ~~Make depends/depexts optional fields.~~ No longer needed for new build context.
72 | - [ ] Handle empty lock file.
73 | - [ ] Fetch opam extra-source files.
74 | - [ ] Always patch shebangs?
75 | - [x] ~~Do we need to pass --ocaml-version? Can we expect OCaml to be always in the path?~~
76 | - [ ] Pass package nv to opam actions. This will fix the develop $out issue.
77 | - [x] Add an example with overrides.
78 | - [ ] Support static compilation.
79 | - [ ] Handle setenv and build-env.
80 | - [ ] Support cross-compilation.
81 | - [ ] Add flakes support.
82 | - [x] ~~Use strictDeps. This will require handling conf- packages in the lock file.~~
83 | - Added a build option.
84 | - [ ] Handle lock file without ocaml.
85 | - [x] Version the lock file.
86 | - Add version, packages and repo fields.
87 | - [x] Consider using joinSymlinks/linkFarm(!) to create a build scope.
88 | - Use this for the `onix build` command, i.e., result will contain the root outputs.
89 | - [x] Consider using makeScope for the scope.
90 | - [ ] Handle pkg:installed?enable:disable.
91 | - [x] Stop using emptyPkg. Override the base compiler only?
92 | - [x] Would depending on the content of the opam files (as opposed to the opam file in the repo) improve cache reusability?
93 | - [x] Handle enable-ocaml-beta-repository.
94 | - In theory this is now working with multirepos.
95 | - [ ] ~~Should we include the opam field in the lock pkg? The opam file path can be reconstructed from the repo path and pkg nv.~~
96 | - [x] Hanlde "sys-ocaml-version" var.
97 | - [ ] Group dep flags.
98 | - [ ] Setup the opamverse test.
99 | - [ ] notty: seems to have a missing propagated build dep.
100 | - [ ] When adding nativeBuildInouts we need to include all transitive deps.
101 | - [ ] Run commands with shell?
102 | - https://github.com/ocaml/opam-repository/blob/a6d8661d6cb4dcb9017dea68bad0f5b2dedb2f09/packages/bap-llvm/bap-llvm.2.4.0/opam#L11
103 | - [ ] Filter out invalid nixpkgs names: "pkgs.devel/librdkafka"
104 | - [ ] Slacko base-no-ppx for lwt?
105 | - [ ] Support a vendoring/forking workflow (override src?).
106 | - [ ] ~~Opam-installer requires ocaml 4.13 by default. Use the same version as the rest of the build.~~
107 | - opam installer is now vendored in onix itself.
108 | - [ ] ~~Consider adding opam-installer to lock file to avoid pulling pkgs.opam-installer.~~
109 | - [ ] Document diffs:
110 | - Support compiler option packages.
111 | - Support dev tools, test, doc.
112 | - Support pins.
113 | - Support cross.
114 | - Support lock-file.
115 | - Support easy building of any package without nix lock.
116 | - Support offline mode.
117 | - Prefetch packages to compute sha256 when md5 is used.
118 | - Support multiple opam repositories.
119 | - [x] Help debug nix paths (eg show opam repo path)
120 | - [x] When the scope is built but there was a pkg override do we still build the pkg? Must not.
121 | - [x] Replace hardcoded ocaml nix versions with a call to get currently available version from ocaml-ng.
122 | - [ ] Make sure that unsupported compiler combinations error out.
123 | - compiler: base|system, options package (warn on pkg with compiler flag?)
124 | - compiler: variants, no valid version in nix
125 | - [ ] Add self pkg to OPAMPATH[0]
126 | - [x] Use rec in lock.
127 | - [x] Do not include opt deps in lock.
128 | - [ ] Include expanded patch/build/install commands in lock file.
129 | - This is not easy because substs need to happen during patch phase, which requires var lookup.
130 | - [x] Ensure that strings, not paths, are passed to build/opamFiles.
131 | - Using toString is not sufficient, the lock file will contain an absolute path.
132 | - [ ] If a dep is already in *buildInputs, does it need to be in nativeBuildInputs?
133 | - [ ] Test substs in a patch:
134 | - https://github.com/ocaml/opam-repository/blob/c0e9300f14d3570da85aad7e6e0ae47484a597a9/packages/fury-puyo/fury-puyo.0.5/opam
135 | - [x] There are no more nulls in lock file, avoid checks.
136 | - [ ] Consider using attrsets (name => x) for representing deps to avoid duplicates when (++).
137 | - [ ] Add support for local repos.
138 | - [ ] Host's OPAMPATH is used when -i is not passed to develop for shell target.
139 | - [x] Formatting dune files is slow with vscode.
140 | - Which uses `nix develop -i -c $prog $args`.
141 | - Use env file
142 | - [ ] Can we symlinkJoin an opam root?
143 | - [ ] How to get compatibility with OCaml Platform? Can we fake opam's CLI?
144 | - [x] Pass `verbosity` to top-level onix attrset.
145 | - [ ] Fetch the repository before passing it to onix lock.
146 | - [ ] Input path does not currently support `opam` files because the package name cannot be extracted.
147 | - [x] Do we need the "root" version or can we use "dev" in the lock file?
148 | - Removed root version.
149 | - [ ] It's currently impossible to avoid reading root packages when opam deps are passed.
150 | - [ ] Deps: support overriding to nixpkgs package and providing remote opam URL.
151 | - [ ] SIGSEGV when the path contains a large file (during gitignore?).
152 | - [ ] Better roots tracking (for deps with constraints)?
153 | - [ ] Activate dep flags for a selected list of deps?
154 | - [ ] Allow passing dep flags (test, doc, dev-setup) to each dependency.
155 | - [x] Changing the repo hash invalidates cache even if opam files did not change? Is it possible to depend on opam file contents?
156 | - Fixed.
157 | - [ ] Fix lock var generation when not all vars are passed.
158 | - [ ] When evaluating `arch` use a "delayed" env var version.
159 | - [ ] Document that opam vars can be added to drv.
160 | - [ ] Install repo's opam file.
161 | - [ ] When running `nix profile install github:odis-labs/onix` why are so manny deps fetched (like clang).
162 | - [x] Lookup all opam files by default on onix lock.
163 | - [ ] Improve verbosity of onix lock.
164 | - [ ] Add a shell hook to shell to check if there are any opam variables.
165 |
--------------------------------------------------------------------------------