├── .gitignore ├── .history ├── .ocamlformat ├── CHANGELOG.md ├── Makefile ├── Makefile.template ├── NOTES.md ├── README.md ├── TODO.md ├── bootstrap.nix ├── default.nix ├── docs ├── .nojekyll └── index.html ├── dune ├── dune-project ├── dune-workspace ├── flake.lock ├── flake.nix ├── nix ├── api.nix ├── core.nix ├── experiments │ ├── paths.nix │ ├── subst.nix │ ├── test-subst.nix │ ├── test-vars.nix │ └── vars.nix ├── onixPackages │ ├── 0install-solver.nix │ ├── cmdliner.nix │ ├── default.nix │ └── opam-0install.nix └── overlay │ ├── default.nix │ ├── ocamlfind │ ├── onix_install_topfind_192.patch │ ├── onix_install_topfind_193.patch │ ├── onix_install_topfind_194.patch │ ├── onix_install_topfind_195.patch │ └── onix_install_topfind_198.patch │ └── ocb-stubblr │ └── onix_disable_opam.patch ├── onix-lock.json ├── onix.opam ├── playground ├── cmdline.md ├── nix-api.md └── propagated.nix ├── shell.nix ├── src ├── onix │ ├── Main.ml │ └── dune ├── onix_core │ ├── Filter.ml │ ├── Lib.ml │ ├── Lock_file.ml │ ├── Lock_file.mli │ ├── Lock_pkg.ml │ ├── Lock_pkg.mli │ ├── Nix_utils.ml │ ├── Nix_utils.mli │ ├── Opam_actions.ml │ ├── Opam_actions.mli │ ├── Opam_utils.ml │ ├── Overrides.ml │ ├── Paths.ml │ ├── Pin_depends.ml │ ├── Pin_depends.mli │ ├── Repo.ml │ ├── Resolutions.ml │ ├── Resolutions.mli │ ├── Scope.ml │ ├── Scope.mli │ ├── Solver.ml │ ├── Solver.mli │ ├── Solver_context.ml │ ├── Solver_context.mli │ ├── System.ml │ ├── Utils.ml │ └── dune ├── onix_lock_graphviz │ ├── Onix_lock_graphviz.ml │ └── dune ├── onix_lock_json │ ├── Onix_lock_json.ml │ ├── Pp.ml │ ├── Pp.mli │ └── dune ├── onix_lock_nix │ ├── Nix_filter.ml │ ├── Nix_pkg.ml │ ├── Onix_lock_nix.ml │ ├── Overlay_files.ml │ ├── Pp.ml │ ├── Prelude.ml │ ├── Subst_and_patch.ml │ └── dune └── onix_lock_opam │ ├── Onix_lock_opam.ml │ ├── Pp.ml │ └── dune ├── tests ├── Opam_to_nix.ml ├── Test_lock.ml ├── Test_vars.ml ├── dune ├── multiple-opam-repos.nix ├── opam-roots │ ├── pkg_opam_dir │ │ ├── Makefile │ │ ├── default.nix │ │ ├── onix-lock.json │ │ └── opam │ │ │ ├── aaa.opam │ │ │ └── bbb.opam │ └── pkg_opam_file │ │ ├── Makefile │ │ ├── default.nix │ │ ├── onix-lock.json │ │ └── opam ├── transitive-deps │ └── default.nix ├── zarith.nix └── zarith.opam └── vendor └── opam-installer ├── dune └── opam_installer.ml /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | _tmp 3 | result* -------------------------------------------------------------------------------- /.history: -------------------------------------------------------------------------------- 1 | nix --offline -L build -f default.nix scope.hello-with-test-doc-tools 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { }, ocamlPackages ? pkgs.ocaml-ng.ocamlPackages_5_2 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 | opam-core 23 | opam-state 24 | opam-0install 25 | ]; 26 | 27 | meta = { 28 | description = "Build OCaml projects with Nix."; 29 | homepage = "https://github.com/odis-labs/onix"; 30 | license = pkgs.lib.licenses.isc; 31 | maintainers = [ ]; 32 | }; 33 | }; 34 | in onix 35 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rizo/onix/92775aa3564c16c6a720725ca5b5f7a894f7b767/docs/.nojekyll -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Sat 3 Dec 2022 14:43:55 GMT 2 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (dirs src tests vendor) 2 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.0) 2 | 3 | (using menhir 2.0) 4 | 5 | (implicit_transitive_deps false) 6 | -------------------------------------------------------------------------------- /dune-workspace: -------------------------------------------------------------------------------- 1 | (lang dune 3.0) 2 | 3 | (env 4 | (dev 5 | (flags 6 | (:standard -warn-error -A)))) 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nix/onixPackages/0install-solver.nix: -------------------------------------------------------------------------------- 1 | { lib, fetchurl, buildDunePackage }: 2 | 3 | buildDunePackage rec { 4 | pname = "0install-solver"; 5 | version = "2.17"; 6 | useDune2 = true; 7 | 8 | src = fetchurl { 9 | url = 10 | "https://github.com/0install/0install/releases/download/v2.17/0install-v2.17.tbz"; 11 | sha256 = "1704e5d852bad79ef9f5b5b31146846420270411c5396434f6fe26577f2d0923"; 12 | }; 13 | 14 | meta = with lib; { 15 | description = "Package dependency solver"; 16 | homepage = "https://github.com/0install/0install"; 17 | license = licenses.lgpl2; 18 | maintainers = [ ]; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nix/onixPackages/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { }, ocamlPackages ? pkgs.ocamlPackages }: 2 | ocamlPackages.overrideScope (self: super: { 3 | cmdliner = self.callPackage ./cmdliner.nix { }; 4 | zero-install-solver = self.callPackage ./0install-solver.nix { }; 5 | opam-0install = self.callPackage ./opam-0install.nix { }; 6 | alcotest = super.alcotest.overrideAttrs (_: { doCheck = false; }); 7 | yojson = super.yojson.overrideAttrs (_: { doCheck = false; }); 8 | uri = super.uri.overrideAttrs (_: { doCheck = false; }); 9 | angstrom = super.angstrom.overrideAttrs (_: { doCheck = false; }); 10 | opam-repository = super.opam-repository.overrideAttrs (_: { 11 | configureFlags = [ 12 | "--disable-checks" 13 | ]; 14 | doCheck = false; 15 | }); 16 | logs = super.logs.override { jsooSupport = false; }; 17 | }) 18 | -------------------------------------------------------------------------------- /nix/onixPackages/opam-0install.nix: -------------------------------------------------------------------------------- 1 | { lib, fetchurl, buildDunePackage, fmt, cmdliner, opam-state, opam-file-format 2 | , zero-install-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 zero-install-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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)"; }; \ -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { }, ocamlPackages ? pkgs.ocaml-ng.ocamlPackages_4_14 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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/Lib.ml: -------------------------------------------------------------------------------- 1 | let version = "0.0.5" 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | (* https://github.com/ocaml/opam/blob/e36650b3007e013cfb5b6bb7ed769a349af3ee97/src/client/opamAction.ml#L343 *) 26 | let prepare_package_build env opam nv dir = 27 | let open OpamFilename.Op in 28 | let open OpamProcess.Job.Op in 29 | let patches = OpamFile.OPAM.patches opam in 30 | 31 | let print_apply basename = 32 | Logs.debug (fun log -> 33 | log "%s: applying %s." 34 | (OpamPackage.name_to_string nv) 35 | (OpamFilename.Base.to_string basename)); 36 | if OpamConsole.verbose () then 37 | OpamConsole.msg "[%s: patch] applying %s@." 38 | (OpamConsole.colorise `green (OpamPackage.name_to_string nv)) 39 | (OpamFilename.Base.to_string basename) 40 | in 41 | let print_subst basename = 42 | let file = OpamFilename.Base.to_string basename in 43 | let file_in = file ^ ".in" in 44 | Logs.debug (fun log -> 45 | log "%s: expanding opam variables in %s, generating %s." 46 | (OpamPackage.name_to_string nv) 47 | file_in file) 48 | in 49 | 50 | let apply_patches () = 51 | Logs.debug (fun log -> 52 | log "Applying patches total=%d..." (List.length patches)); 53 | let patch base = 54 | OpamFilename.patch (dir // OpamFilename.Base.to_string base) dir 55 | in 56 | let rec aux = function 57 | | [] -> Done [] 58 | | (patchname, filter) :: rest -> 59 | if OpamFilter.opt_eval_to_bool env filter then ( 60 | print_apply patchname; 61 | patch patchname @@+ function 62 | | None -> aux rest 63 | | Some err -> aux rest @@| fun e -> (patchname, err) :: e) 64 | else aux rest 65 | in 66 | aux patches 67 | in 68 | let substs = OpamFile.OPAM.substs opam in 69 | let subst_patches, subst_others = 70 | List.partition (fun f -> List.mem_assoc f patches) substs 71 | in 72 | Logs.debug (fun log -> 73 | log "Found %d substs; patches=%d others=%d..." (List.length substs) 74 | (List.length subst_patches) 75 | (List.length subst_others)); 76 | let subst_errs = 77 | OpamFilename.in_dir dir @@ fun () -> 78 | List.fold_left 79 | (fun errs f -> 80 | try 81 | print_subst f; 82 | OpamFilter.expand_interpolations_in_file env f; 83 | errs 84 | with e -> (f, e) :: errs) 85 | [] subst_patches 86 | in 87 | 88 | (* Apply the patches *) 89 | let text = 90 | OpamProcess.make_command_text (OpamPackage.Name.to_string nv.name) "patch" 91 | in 92 | OpamProcess.Job.with_text text (apply_patches ()) @@+ fun patching_errors -> 93 | (* Substitute the configuration files. We should be in the right 94 | directory to get the correct absolute path for the 95 | substitution files (see [OpamFilter.expand_interpolations_in_file] and 96 | [OpamFilename.of_basename]. *) 97 | let subst_errs = 98 | OpamFilename.in_dir dir @@ fun () -> 99 | List.fold_left 100 | (fun errs f -> 101 | try 102 | print_subst f; 103 | OpamFilter.expand_interpolations_in_file env f; 104 | errs 105 | with e -> (f, e) :: errs) 106 | subst_errs subst_others 107 | in 108 | if patching_errors <> [] || subst_errs <> [] then 109 | let msg = 110 | (if patching_errors <> [] then 111 | Printf.sprintf "These patches didn't apply at %s:@.%s" 112 | (OpamFilename.Dir.to_string dir) 113 | (OpamStd.Format.itemize 114 | (fun (f, err) -> 115 | Printf.sprintf "%s: %s" 116 | (OpamFilename.Base.to_string f) 117 | (Printexc.to_string err)) 118 | patching_errors) 119 | else "") 120 | ^ 121 | if subst_errs <> [] then 122 | Printf.sprintf "String expansion failed for these files:@.%s" 123 | (OpamStd.Format.itemize 124 | (fun (b, err) -> 125 | Printf.sprintf "%s.in: %s" 126 | (OpamFilename.Base.to_string b) 127 | (Printexc.to_string err)) 128 | subst_errs) 129 | else "" 130 | in 131 | Done (Some (Failure msg)) 132 | else Done None 133 | 134 | let copy_extra_files ~opamfile ~build_dir extra_files = 135 | let bad_hash = 136 | OpamStd.List.filter_map 137 | (fun (basename, hash) -> 138 | let src = Opam_utils.make_opam_files_path ~opamfile basename in 139 | if OpamHash.check_file (OpamFilename.to_string src) hash then ( 140 | let dst = OpamFilename.create build_dir basename in 141 | Logs.debug (fun log -> 142 | log "Opam_actions.copy_extra_files: %a -> %a" 143 | Opam_utils.pp_filename src Opam_utils.pp_filename dst); 144 | OpamFilename.copy ~src ~dst; 145 | None) 146 | else Some src) 147 | extra_files 148 | in 149 | if List.is_not_empty bad_hash then 150 | Fmt.failwith "Bad hash for %s" 151 | (OpamStd.Format.itemize OpamFilename.to_string bad_hash) 152 | 153 | let copy_undeclared_files ~opamfile ~build_dir () = 154 | let ( ) = OpamFilename.Op.( / ) in 155 | let files_dir = OpamFilename.dirname opamfile "files" in 156 | List.iter 157 | (fun src -> 158 | let base = OpamFilename.basename src in 159 | let dst = OpamFilename.create build_dir base in 160 | Logs.debug (fun log -> 161 | log "Opam_actions.copy_undeclared_files: %a -> %a" 162 | Opam_utils.pp_filename src Opam_utils.pp_filename dst); 163 | OpamFilename.copy ~src ~dst) 164 | (OpamFilename.files files_dir) 165 | 166 | (* TODO: implement extra file fetching via lock-file?: 167 | - https://github.com/ocaml/opam/blob/e36650b3007e013cfb5b6bb7ed769a349af3ee97/src/client/opamAction.ml#L455 *) 168 | let run (pkg_scope : Scope.t) = 169 | let opamfile = OpamFilename.of_string pkg_scope.self.opamfile in 170 | let opam = Opam_utils.read_opam opamfile in 171 | let () = 172 | let build_dir = OpamFilename.Dir.of_string (Sys.getcwd ()) in 173 | match OpamFile.OPAM.extra_files opam with 174 | | None -> 175 | Logs.debug (fun log -> 176 | log "No extra files in opam file, checking for undeclared files..."); 177 | copy_undeclared_files ~opamfile ~build_dir () 178 | | Some extra_files -> copy_extra_files ~opamfile ~build_dir extra_files 179 | in 180 | let resolve = resolve_actions pkg_scope in 181 | let cwd = OpamFilename.Dir.of_string (Sys.getcwd ()) in 182 | let pkg = OpamPackage.create pkg_scope.self.name pkg_scope.self.version in 183 | prepare_package_build resolve opam pkg cwd 184 | |> OpamProcess.Job.run 185 | |> Option.if_some raise 186 | end 187 | 188 | let patch = Patch.run 189 | 190 | let build ~with_test ~with_doc ~with_dev_setup (pkg_scope : Scope.t) = 191 | let opam = 192 | Opam_utils.read_opam (OpamFilename.of_string pkg_scope.self.opamfile) 193 | in 194 | let resolve_with_dep_vars = 195 | resolve_actions 196 | ~local:(mk_dep_vars ~with_test ~with_doc ~with_dev_setup) 197 | pkg_scope 198 | in 199 | let resolve = 200 | resolve_actions 201 | ~local:(mk_dep_vars ~with_test ~with_doc ~with_dev_setup) 202 | pkg_scope 203 | in 204 | let commands = 205 | (OpamFilter.commands resolve_with_dep_vars (OpamFile.OPAM.build opam) 206 | @ (if with_test then 207 | OpamFilter.commands resolve (OpamFile.OPAM.run_test opam) 208 | else []) 209 | @ 210 | if with_doc then 211 | OpamFilter.commands resolve (OpamFile.OPAM.deprecated_build_doc opam) 212 | else []) 213 | |> List.filter List.is_not_empty 214 | in 215 | commands 216 | 217 | module Install = struct 218 | let run ~with_test ~with_doc ~with_dev_setup (pkg_scope : Scope.t) = 219 | let opam = 220 | Opam_utils.read_opam (OpamFilename.of_string pkg_scope.self.opamfile) 221 | in 222 | let resolve_with_dep_vars = 223 | resolve_actions 224 | ~local:(mk_dep_vars ~with_test ~with_doc ~with_dev_setup) 225 | pkg_scope 226 | in 227 | OpamFilter.commands resolve_with_dep_vars (OpamFile.OPAM.install opam) 228 | |> List.filter List.is_not_empty 229 | end 230 | 231 | let install = Install.run 232 | let all ~with_test ~with_doc ~with_dev_setup (pkg_scope : Scope.t) = () 233 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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/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/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/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_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_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 | -------------------------------------------------------------------------------- /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_core/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name onix_core) 3 | (libraries 4 | fmt 5 | bos 6 | logs 7 | yojson 8 | re 9 | opam-0install 10 | opam-format 11 | opam-file-format 12 | opam-state 13 | opam-core)) 14 | -------------------------------------------------------------------------------- /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_graphviz/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name onix_lock_graphviz) 3 | (libraries onix_core fmt logs opam-format)) 4 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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_lock_nix/Prelude.ml: -------------------------------------------------------------------------------- 1 | let ( ) = OpamFilename.Op.( / ) 2 | let ( ) = OpamFilename.Op.( // ) 3 | 4 | include Onix_core 5 | include Onix_core.Utils -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/opam-roots/pkg_opam_file/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lock 2 | lock: 3 | nix develop -f default.nix lock 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /vendor/opam-installer/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name opam_installer) 3 | (public_name onix-opam-installer) 4 | (libraries opam-format opam-core cmdliner)) 5 | --------------------------------------------------------------------------------