├── .gitattributes ├── .gitignore ├── .npmignore ├── Readme.md ├── esy.lock ├── esyi.lock.json ├── esyi.opam ├── jbuild-ignore ├── package.json ├── src ├── bin │ ├── esyi.re │ └── jbuild ├── fetch │ ├── Fetch.re │ ├── FetchUtils.re │ ├── Resolved.re │ └── jbuild ├── npm │ ├── NpmVersion.re │ ├── OpamConcrete.re │ ├── PackageJson.re │ ├── ParseNpm.re │ ├── Registry.re │ └── jbuild ├── opam │ ├── OpamAvailable.re │ ├── OpamFile.re │ ├── OpamOverrides.re │ ├── OpamVersion.re │ ├── Registry.re │ └── jbuild ├── shared │ ├── Env.re │ ├── ExecCommand.re │ ├── Files.re │ ├── GenericVersion.re │ ├── GithubVersion.re │ ├── Infix.re │ ├── Lockfile.re │ ├── Types.re │ ├── Wget.re │ └── jbuild └── solve │ ├── CudfVersions.re │ ├── Github.re │ ├── Manifest.re │ ├── Solve.re │ ├── SolveDeps.re │ ├── SolveUtils.re │ ├── VersionCache.re │ └── jbuild └── test ├── Test.re └── jbuild /.gitattributes: -------------------------------------------------------------------------------- 1 | esy.lock binary 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .merlin 2 | *.install 3 | _build 4 | node_modules 5 | .esy-cache 6 | .esy-modules 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | .merlin 3 | yarn-error.log 4 | node_modules/ 5 | _build 6 | _esybuild 7 | _esyinstall 8 | _release 9 | *.byte 10 | *.native 11 | esy.lock 12 | my-esy-project.install -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Esy Install 2 | 3 | But in ocaml this time. With a real solver, so we get good dependencies. 4 | 5 | ## Status: self-hosting! Also can install reason-wall-demo 6 | 7 | ## Completed 8 | 9 | - [x] parsing opam files & mostly converting the dependencies (I bail on the more complex boolean operations, pretending they are "any") 10 | - [x] parsing package.json files 11 | - [x] talking to the npm registry 12 | - [x] looking in a local copy of the opam registry 13 | - [x] using MCCS (a SAT solver) to find a valid assignment of dependencies! 14 | - [x] tracking buildDependencies separately 15 | - [x] sharing resolved buildDependencies when possible 16 | - [x] generating a lockfile! (it's yojson at the moment, so don't expect anything fancy) 17 | - [x] fetching all the things, with some opam -> package.json conversion 18 | - [x] write out override files 19 | - [x] get the files from opam too tho 20 | - [x] handle override patches too 21 | - [x] add a `_resolved` field 22 | - [x] opam conversion 23 | - [x] incorporate `esy-opam-overrides` 24 | - [x] get jbuilder building 25 | - [x] get [the test repository] building! 26 | - [x] handle opam versions correctly `(>= 1.2.0 & < 1.3.0)` 27 | - [x] respect the "available" flag in `opam` 28 | - [x] get tsdl building ok 29 | - [x] get reason-wall-demo building! 30 | - [x] be able to install its own deps! 31 | - [x] ~ should resolve before the empty string in opam land 32 | - [x] make the "fetch" step not depend on having `opam-repository` around 33 | - [x] grab & update esy-opam-overrides and opam-repository automatically 34 | 35 | ## After that 36 | 37 | - [ ] be able to process normal npm dependencies -- first by trying to do it without conflicts, and then relaxing the requirement and doing an after-pass to remove unneeded duplicates. 38 | - [ ] output a nice "esy.resolved" file that `esy b` can then read to know where dependencies live. 39 | 40 | ## Needed less urgently 41 | 42 | - [ ] use Lwt! So we can parallelize a ton of things 43 | - [ ] actually validate checksums 44 | - [ ] deciding what we want to do with devDependencies (currently they're dumped into build dependences) 45 | - [ ] make the generated lockfile a nicer format (maybe yaml/toml?) 46 | - [ ] parallelize some things, but make sure not to compromise reproducability 47 | - [ ] handle the not-fresh case 48 | - [ ] inflate from lockfile 49 | - [ ] check staleness 50 | - [ ] add/remove/upgrade deps 51 | 52 | ## Needed for cross-platform 53 | 54 | - [ ] use Cohttp instead of shelling out to curl 55 | - [ ] maybe use an ocaml git client? instead of shelling out to git 56 | - [ ] use an ocaml decompression library instead of shelling out to tar 57 | - [ ] audit for "/" vs Filename.concat 58 | 59 | ## Later on 60 | 61 | - [ ] maybe fetch tarballs from the opam mirror directly 62 | - [ ] maybe use a global cache for fetched things? currently using a project-local cache 63 | - [ ] support multiple architecture targets! 64 | -------------------------------------------------------------------------------- /esy.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@esy-ocaml/esy-installer@^0.0.0": 6 | version "0.0.0" 7 | resolved "https://registry.yarnpkg.com/@esy-ocaml/esy-installer/-/esy-installer-0.0.0.tgz#6b0e2bd4ee43531ac74793fe55cfcc3aca197a66" 8 | 9 | "@esy-ocaml/libffi@esy-ocaml/libffi#esy": 10 | version "3.2.10" 11 | resolved "https://codeload.github.com/esy-ocaml/libffi/tar.gz/14e1f863e929c3f8eaaa689cb724aa0c1c3e3f66" 12 | 13 | "@esy-ocaml/substs@^0.0.1": 14 | version "0.0.1" 15 | resolved "https://registry.yarnpkg.com/@esy-ocaml/substs/-/substs-0.0.1.tgz#59ebdbbaedcda123fc7ed8fb2b302b7d819e9a46" 16 | 17 | "@jaredly/testre@*": 18 | version "0.0.1" 19 | resolved "https://registry.yarnpkg.com/@jaredly/testre/-/testre-0.0.1.tgz#fb44ba4e05b7f462189b0ceae7cb25605babb765" 20 | dependencies: 21 | "@esy-ocaml/esy-installer" "^0.0.0" 22 | "@opam/jbuilder" "^1.0.0-beta16" 23 | "@opam/reason" "3.0.4" 24 | peerDependencies: 25 | ocaml "~4.6.000" 26 | 27 | "@opam/alcotest@*", "@opam/alcotest@0.8.1": 28 | version "0.8.1" 29 | uid "1adc2d5daa9852e7b2cd50de366a0f2a" 30 | resolved "@opam/alcotest@0.8.1-1adc2d5daa9852e7b2cd50de366a0f2a.tgz" 31 | dependencies: 32 | "@esy-ocaml/esy-installer" "^0.0.0" 33 | "@esy-ocaml/substs" "^0.0.1" 34 | "@opam/astring" "*" 35 | "@opam/cmdliner" "*" 36 | "@opam/fmt" " >= 0.8.0" 37 | "@opam/jbuilder" " >= 1.0.0-beta10" 38 | "@opam/result" "*" 39 | peerDependencies: 40 | ocaml " >= 4.2.3000" 41 | 42 | "@opam/astring@*": 43 | version "0.8.3" 44 | uid b06f886359a1b349ab4c8361ea831085 45 | resolved "@opam/astring@0.8.3-b06f886359a1b349ab4c8361ea831085.tgz" 46 | dependencies: 47 | "@esy-ocaml/esy-installer" "^0.0.0" 48 | "@esy-ocaml/substs" "^0.0.1" 49 | "@opam/ocamlbuild" "*" 50 | "@opam/ocamlfind" "*" 51 | "@opam/topkg" "*" 52 | peerDependencies: 53 | ocaml " >= 4.1.0" 54 | 55 | "@opam/base-bytes@*": 56 | version "0.0.0-base" 57 | uid bacd2a20f40952db4c545687d27548ab 58 | resolved "@opam/base-bytes@0.0.0-base-bacd2a20f40952db4c545687d27548ab.tgz" 59 | dependencies: 60 | "@esy-ocaml/esy-installer" "^0.0.0" 61 | "@esy-ocaml/substs" "^0.0.1" 62 | "@opam/ocamlfind" " >= 1.5.3" 63 | peerDependencies: 64 | ocaml " >= 4.2.0" 65 | 66 | "@opam/base-threads@*": 67 | version "0.0.0-base" 68 | uid "5601b815938b487242834ca2ddd7e3fc" 69 | resolved "@opam/base-threads@0.0.0-base-5601b815938b487242834ca2ddd7e3fc.tgz" 70 | dependencies: 71 | "@esy-ocaml/esy-installer" "^0.0.0" 72 | "@esy-ocaml/substs" "^0.0.1" 73 | peerDependencies: 74 | ocaml "*" 75 | 76 | "@opam/base-unix@*": 77 | version "0.0.0-base" 78 | uid "04bc004c59f2edca849d38ea6891bb7f" 79 | resolved "@opam/base-unix@0.0.0-base-04bc004c59f2edca849d38ea6891bb7f.tgz" 80 | dependencies: 81 | "@esy-ocaml/esy-installer" "^0.0.0" 82 | "@esy-ocaml/substs" "^0.0.1" 83 | peerDependencies: 84 | ocaml "*" 85 | 86 | "@opam/base@ >= 100000000.10.0 < 100000000.11.0": 87 | version "100000000.10.0" 88 | uid "61a1898496c450e7f69b8cc9372c37d3" 89 | resolved "@opam/base@100000000.10.0-61a1898496c450e7f69b8cc9372c37d3.tgz" 90 | dependencies: 91 | "@esy-ocaml/esy-installer" "^0.0.0" 92 | "@esy-ocaml/substs" "^0.0.1" 93 | "@opam/jbuilder" " >= 1.0.0-beta12" 94 | "@opam/sexplib" " >= 100000000.10.0 < 100000000.11.0" 95 | peerDependencies: 96 | ocaml " >= 4.4.1000" 97 | 98 | "@opam/biniou@ >= 1.2.0": 99 | version "1.2.0" 100 | uid af4880e80be63bbb3043fb7184882eb8 101 | resolved "@opam/biniou@1.2.0-75e2067086f4880a4c4d0808064da986.tgz" 102 | dependencies: 103 | "@esy-ocaml/esy-installer" "^0.0.0" 104 | "@esy-ocaml/substs" "^0.0.1" 105 | "@opam/conf-which" "*" 106 | "@opam/easy-format" "*" 107 | "@opam/jbuilder" " >= 1.0.0-beta7" 108 | peerDependencies: 109 | ocaml " >= 4.2.3000" 110 | 111 | "@opam/bisect_ppx@1.3.2": 112 | version "1.3.2" 113 | uid "1de82e21afcc30363d44c1aa2e25b9a9" 114 | resolved "@opam/bisect_ppx@1.3.2-1de82e21afcc30363d44c1aa2e25b9a9.tgz" 115 | dependencies: 116 | "@esy-ocaml/esy-installer" "^0.0.0" 117 | "@esy-ocaml/substs" "^0.0.1" 118 | "@opam/base-unix" "*" 119 | "@opam/jbuilder" " >= 1.0.0-beta13" 120 | "@opam/ocaml-migrate-parsetree" " >= 1.0.3" 121 | "@opam/ocamlbuild" "*" 122 | "@opam/ppx_tools_versioned" "*" 123 | peerDependencies: 124 | ocaml " >= 4.2.0" 125 | 126 | "@opam/camlp4@*": 127 | version "4.6.0-1" 128 | uid ec9e71b1e73d101ab8ef9a66cc9035e7 129 | resolved "@opam/camlp4@4.6.0-1-ec9e71b1e73d101ab8ef9a66cc9035e7.tgz" 130 | dependencies: 131 | "@esy-ocaml/esy-installer" "^0.0.0" 132 | "@esy-ocaml/substs" "^0.0.1" 133 | "@opam/ocamlbuild" "*" 134 | peerDependencies: 135 | ocaml " >= 4.6.0 < 4.7.0" 136 | 137 | "@opam/camomile@ >= 0.8.0", "@opam/camomile@*": 138 | version "0.8.7" 139 | uid c128e86f078793c4ef45907002f0cb86 140 | resolved "@opam/camomile@0.8.7-c128e86f078793c4ef45907002f0cb86.tgz" 141 | dependencies: 142 | "@esy-ocaml/esy-installer" "^0.0.0" 143 | "@esy-ocaml/substs" "^0.0.1" 144 | "@opam/base-bytes" "*" 145 | "@opam/camlp4" "*" 146 | "@opam/cppo" "*" 147 | "@opam/jbuilder" " >= 1.0.0-beta7" 148 | peerDependencies: 149 | ocaml " >= 4.2.3000" 150 | 151 | "@opam/cmdliner@*": 152 | version "1.0.2" 153 | uid "4b1c61475a9d1d2451b622ad0f3e58d2" 154 | resolved "@opam/cmdliner@1.0.2-4b1c61475a9d1d2451b622ad0f3e58d2.tgz" 155 | dependencies: 156 | "@esy-ocaml/esy-installer" "^0.0.0" 157 | "@esy-ocaml/substs" "^0.0.1" 158 | "@opam/ocamlbuild" "*" 159 | "@opam/ocamlfind" "*" 160 | "@opam/result" "*" 161 | "@opam/topkg" "*" 162 | peerDependencies: 163 | ocaml " >= 4.1.0" 164 | 165 | "@opam/conf-libcurl@*": 166 | version "1.0.0" 167 | uid "5e2f27ddfe026ef1895563e23e2585e4" 168 | resolved "@opam/conf-libcurl@1.0.0-5e2f27ddfe026ef1895563e23e2585e4.tgz" 169 | dependencies: 170 | "@esy-ocaml/esy-installer" "^0.0.0" 171 | "@esy-ocaml/substs" "^0.0.1" 172 | "@opam/conf-pkg-config" "*" 173 | peerDependencies: 174 | ocaml "*" 175 | 176 | "@opam/conf-m4@*": 177 | version "1.0.0" 178 | uid "97f0966b2e2652d02c0d9125781c31df" 179 | resolved "@opam/conf-m4@1.0.0-97f0966b2e2652d02c0d9125781c31df.tgz" 180 | dependencies: 181 | "@esy-ocaml/esy-installer" "^0.0.0" 182 | "@esy-ocaml/substs" "^0.0.1" 183 | peerDependencies: 184 | ocaml "*" 185 | 186 | "@opam/conf-perl@*": 187 | version "1.0.0" 188 | uid "806afe6b55fcdaa74aeedba996f132f9" 189 | resolved "@opam/conf-perl@1.0.0-806afe6b55fcdaa74aeedba996f132f9.tgz" 190 | dependencies: 191 | "@esy-ocaml/esy-installer" "^0.0.0" 192 | "@esy-ocaml/substs" "^0.0.1" 193 | peerDependencies: 194 | ocaml "*" 195 | 196 | "@opam/conf-pkg-config@*": 197 | version "1.0.0" 198 | uid "20a6ddadf5edc6719d07685f4b45d22e" 199 | resolved "@opam/conf-pkg-config@1.0.0-20a6ddadf5edc6719d07685f4b45d22e.tgz" 200 | dependencies: 201 | "@esy-ocaml/esy-installer" "^0.0.0" 202 | "@esy-ocaml/substs" "^0.0.1" 203 | yarn-pkg-config esy-ocaml/yarn-pkg-config#d488cd9321cd5036bd36ec96744ce78c5d45fc49 204 | peerDependencies: 205 | ocaml "*" 206 | 207 | "@opam/conf-which@*": 208 | version "1.0.0" 209 | uid b7770c8a3ebbb7f8a80c925753375bb8 210 | resolved "@opam/conf-which@1.0.0-b7770c8a3ebbb7f8a80c925753375bb8.tgz" 211 | dependencies: 212 | "@esy-ocaml/esy-installer" "^0.0.0" 213 | "@esy-ocaml/substs" "^0.0.1" 214 | peerDependencies: 215 | ocaml "*" 216 | 217 | "@opam/cppo@ >= 1.1.0", "@opam/cppo@ >= 1.1.2", "@opam/cppo@ >= 1.6.0", "@opam/cppo@*": 218 | version "1.6.1" 219 | uid "97dd53c54787a849941f7eae36018f99" 220 | resolved "@opam/cppo@1.6.1-97dd53c54787a849941f7eae36018f99.tgz" 221 | dependencies: 222 | "@esy-ocaml/esy-installer" "^0.0.0" 223 | "@esy-ocaml/substs" "^0.0.1" 224 | "@opam/base-bytes" "*" 225 | "@opam/base-unix" "*" 226 | "@opam/jbuilder" " >= 1.0.0-beta10" 227 | peerDependencies: 228 | ocaml "*" 229 | 230 | "@opam/cppo_ocamlbuild@*": 231 | version "1.6.0" 232 | uid "76c36d0e895b7ef5a207f158d017d4da" 233 | resolved "@opam/cppo_ocamlbuild@1.6.0-6fd85090225019f09a636edd7e6d011e.tgz" 234 | dependencies: 235 | "@esy-ocaml/esy-installer" "^0.0.0" 236 | "@esy-ocaml/substs" "^0.0.1" 237 | "@opam/cppo" " >= 1.6.0" 238 | "@opam/jbuilder" " >= 1.0.0-beta10" 239 | "@opam/ocamlbuild" "*" 240 | peerDependencies: 241 | ocaml "*" 242 | 243 | "@opam/ctypes@ >= 0.12.0": 244 | version "0.13.1" 245 | uid d4db4db14c3732a3e7e93ca2d9bb9fd9 246 | resolved "@opam/ctypes@0.13.1-d4db4db14c3732a3e7e93ca2d9bb9fd9.tgz" 247 | dependencies: 248 | "@esy-ocaml/esy-installer" "^0.0.0" 249 | "@esy-ocaml/libffi" esy-ocaml/libffi#esy 250 | "@esy-ocaml/substs" "^0.0.1" 251 | "@opam/base-bytes" "*" 252 | "@opam/conf-pkg-config" "*" 253 | "@opam/integers" "*" 254 | "@opam/ocamlfind" "*" 255 | peerDependencies: 256 | ocaml " >= 4.1.0" 257 | 258 | "@opam/cudf@ >= 0.7.0", "@opam/cudf@0.9": 259 | version "0.9.0" 260 | uid "68efd1b03b4e1495d74eeb4b716b913a" 261 | resolved "@opam/cudf@0.9.0-68efd1b03b4e1495d74eeb4b716b913a.tgz" 262 | dependencies: 263 | "@esy-ocaml/esy-installer" "^0.0.0" 264 | "@esy-ocaml/substs" "^0.0.1" 265 | "@opam/conf-perl" "*" 266 | "@opam/extlib" "*" 267 | "@opam/extlib-compat" "*" 268 | "@opam/ocamlbuild" "*" 269 | peerDependencies: 270 | ocaml "*" 271 | 272 | "@opam/easy-format@*": 273 | version "1.3.1" 274 | uid "0c2f9b085eba4175086013e7ef48ba05" 275 | resolved "@opam/easy-format@1.3.1-0c2f9b085eba4175086013e7ef48ba05.tgz" 276 | dependencies: 277 | "@esy-ocaml/esy-installer" "^0.0.0" 278 | "@esy-ocaml/substs" "^0.0.1" 279 | "@opam/jbuilder" "*" 280 | peerDependencies: 281 | ocaml " >= 4.2.3000" 282 | 283 | "@opam/extlib-compat@*": 284 | version "1.7.2" 285 | uid "4de51c250d2855fa798c7b97a99fca35" 286 | resolved "@opam/extlib-compat@1.7.2-4de51c250d2855fa798c7b97a99fca35.tgz" 287 | dependencies: 288 | "@esy-ocaml/esy-installer" "^0.0.0" 289 | "@esy-ocaml/substs" "^0.0.1" 290 | "@opam/base-bytes" "*" 291 | "@opam/cppo" "*" 292 | "@opam/ocamlfind" "*" 293 | peerDependencies: 294 | ocaml "*" 295 | 296 | "@opam/extlib@*": 297 | version "1.7.2" 298 | uid "45bbc6dbd2da3fee6f51027abb2d5086" 299 | resolved "@opam/extlib@1.7.2-45bbc6dbd2da3fee6f51027abb2d5086.tgz" 300 | dependencies: 301 | "@esy-ocaml/esy-installer" "^0.0.0" 302 | "@esy-ocaml/substs" "^0.0.1" 303 | "@opam/base-bytes" "*" 304 | "@opam/cppo" "*" 305 | "@opam/ocamlfind" "*" 306 | peerDependencies: 307 | ocaml "*" 308 | 309 | "@opam/fmt@ >= 0.8.0", "@opam/fmt@*": 310 | version "0.8.5" 311 | uid "5c1870ebd5137354d253a3abbaf020f0" 312 | resolved "@opam/fmt@0.8.5-5c1870ebd5137354d253a3abbaf020f0.tgz" 313 | dependencies: 314 | "@esy-ocaml/esy-installer" "^0.0.0" 315 | "@esy-ocaml/substs" "^0.0.1" 316 | "@opam/ocamlbuild" "*" 317 | "@opam/ocamlfind" "*" 318 | "@opam/result" "*" 319 | "@opam/topkg" " >= 0.9.0" 320 | "@opam/uchar" "*" 321 | peerDependencies: 322 | ocaml " >= 4.1.0" 323 | 324 | "@opam/integers@*": 325 | version "0.2.2" 326 | uid "3e92901965b01e7a9a3b77134c237794" 327 | resolved "@opam/integers@0.2.2-3e92901965b01e7a9a3b77134c237794.tgz" 328 | dependencies: 329 | "@esy-ocaml/esy-installer" "^0.0.0" 330 | "@esy-ocaml/substs" "^0.0.1" 331 | "@opam/ocamlbuild" "*" 332 | "@opam/ocamlfind" "*" 333 | "@opam/topkg" "*" 334 | peerDependencies: 335 | ocaml "*" 336 | 337 | "@opam/jbuilder@ >= 1.0.0-beta10", "@opam/jbuilder@ >= 1.0.0-beta12", "@opam/jbuilder@ >= 1.0.0-beta13", "@opam/jbuilder@ >= 1.0.0-beta14", "@opam/jbuilder@ >= 1.0.0-beta7", "@opam/jbuilder@ >= 1.0.0-beta9", "@opam/jbuilder@*", "@opam/jbuilder@^1.0.0-beta16": 338 | version "1.0.0-beta17" 339 | uid fa843d1276caa97411fa08fad16ea291 340 | resolved "@opam/jbuilder@1.0.0-beta17-fa843d1276caa97411fa08fad16ea291.tgz" 341 | dependencies: 342 | "@esy-ocaml/esy-installer" "^0.0.0" 343 | "@esy-ocaml/substs" "^0.0.1" 344 | "@opam/ocamlfind" "*" 345 | peerDependencies: 346 | ocaml " >= 4.2.3000" 347 | 348 | "@opam/lambda-term@ >= 1.2.0", "@opam/lambda-term@^1.11.0": 349 | version "1.12.0" 350 | uid "472857378659d1a1599183ff632c0797" 351 | resolved "@opam/lambda-term@1.12.0-472857378659d1a1599183ff632c0797.tgz" 352 | dependencies: 353 | "@esy-ocaml/esy-installer" "^0.0.0" 354 | "@esy-ocaml/substs" "^0.0.1" 355 | "@opam/camomile" "*" 356 | "@opam/jbuilder" " >= 1.0.0-beta9" 357 | "@opam/lwt" " >= 2.7.0" 358 | "@opam/lwt_react" "*" 359 | "@opam/react" "*" 360 | "@opam/zed" " >= 1.2.0" 361 | peerDependencies: 362 | ocaml " >= 4.2.3000" 363 | 364 | "@opam/logs@*": 365 | version "0.6.2" 366 | uid b3644eee19217825f652f61a658ca61e 367 | resolved "@opam/logs@0.6.2-b3644eee19217825f652f61a658ca61e.tgz" 368 | dependencies: 369 | "@esy-ocaml/esy-installer" "^0.0.0" 370 | "@esy-ocaml/substs" "^0.0.1" 371 | "@opam/ocamlbuild" "*" 372 | "@opam/ocamlfind" "*" 373 | "@opam/result" "*" 374 | "@opam/topkg" "*" 375 | peerDependencies: 376 | ocaml " >= 4.1.0" 377 | 378 | "@opam/lwt@ >= 2.7.0", "@opam/lwt@ >= 3.0.0", "@opam/lwt@*", "@opam/lwt@^3.1.0": 379 | version "3.2.1" 380 | uid "0dc31662087c7ed246f43c4c81ce681e" 381 | resolved "@opam/lwt@3.2.1-0dc31662087c7ed246f43c4c81ce681e.tgz" 382 | dependencies: 383 | "@esy-ocaml/esy-installer" "^0.0.0" 384 | "@esy-ocaml/substs" "^0.0.1" 385 | "@opam/cppo" " >= 1.1.0" 386 | "@opam/jbuilder" " >= 1.0.0-beta14" 387 | "@opam/ocaml-migrate-parsetree" "*" 388 | "@opam/ocamlfind" " >= 1.7.3--1" 389 | "@opam/ppx_tools_versioned" " >= 5.0.1" 390 | "@opam/result" "*" 391 | peerDependencies: 392 | ocaml " >= 4.2.0" 393 | 394 | "@opam/lwt@3.2.0": 395 | version "3.2.0" 396 | uid "510dc36735d8f830a1ca39474ca00957" 397 | resolved "@opam/lwt@3.2.0-510dc36735d8f830a1ca39474ca00957.tgz" 398 | dependencies: 399 | "@esy-ocaml/esy-installer" "^0.0.0" 400 | "@esy-ocaml/substs" "^0.0.1" 401 | "@opam/cppo" " >= 1.1.0" 402 | "@opam/jbuilder" " >= 1.0.0-beta14" 403 | "@opam/ocaml-migrate-parsetree" "*" 404 | "@opam/ocamlfind" " >= 1.7.3--1" 405 | "@opam/ppx_tools_versioned" " >= 5.0.1" 406 | "@opam/result" "*" 407 | peerDependencies: 408 | ocaml " >= 4.2.0" 409 | 410 | "@opam/lwt_react@*": 411 | version "1.1.0" 412 | uid "20a2a8d3c6e4d1038ae9d01666a15320" 413 | resolved "@opam/lwt_react@1.1.0-20a2a8d3c6e4d1038ae9d01666a15320.tgz" 414 | dependencies: 415 | "@esy-ocaml/esy-installer" "^0.0.0" 416 | "@esy-ocaml/substs" "^0.0.1" 417 | "@opam/jbuilder" " >= 1.0.0-beta10" 418 | "@opam/lwt" " >= 3.0.0" 419 | "@opam/react" " >= 1.0.0" 420 | peerDependencies: 421 | ocaml "*" 422 | 423 | "@opam/mccs@*": 424 | version "1.1.0-5" 425 | uid "18e19eaa4e21f431ce046becb28d0054" 426 | resolved "@opam/mccs@1.1.0-5-18e19eaa4e21f431ce046becb28d0054.tgz" 427 | dependencies: 428 | "@esy-ocaml/esy-installer" "^0.0.0" 429 | "@esy-ocaml/substs" "^0.0.1" 430 | "@opam/cudf" " >= 0.7.0" 431 | "@opam/jbuilder" " >= 1.0.0-beta14" 432 | peerDependencies: 433 | ocaml "*" 434 | 435 | "@opam/menhir@ >= 20170418.0.0 <= 20171013.0.0": 436 | version "20171013.0.0" 437 | uid "358990f51123ce9d3629e66b9ead67ee" 438 | resolved "@opam/menhir@20171013.0.0-358990f51123ce9d3629e66b9ead67ee.tgz" 439 | dependencies: 440 | "@esy-ocaml/esy-installer" "^0.0.0" 441 | "@esy-ocaml/substs" "^0.0.1" 442 | "@opam/ocamlbuild" "*" 443 | "@opam/ocamlfind" "*" 444 | peerDependencies: 445 | ocaml " >= 4.2.0" 446 | 447 | "@opam/merlin-extend@ >= 0.3.0": 448 | version "0.3.0" 449 | uid d688ddc39d2c47f4c3083de2fa51bf7e 450 | resolved "@opam/merlin-extend@0.3.0-d688ddc39d2c47f4c3083de2fa51bf7e.tgz" 451 | dependencies: 452 | "@esy-ocaml/esy-installer" "^0.0.0" 453 | "@esy-ocaml/substs" "^0.0.1" 454 | "@opam/cppo" "*" 455 | "@opam/ocamlfind" "*" 456 | peerDependencies: 457 | ocaml " >= 4.2.3000" 458 | 459 | "@opam/merlin@^3.0.5": 460 | version "3.0.5" 461 | uid "43a4ee2722d4484e3b65826d613451a7" 462 | resolved "@opam/merlin@3.0.5-43a4ee2722d4484e3b65826d613451a7.tgz" 463 | dependencies: 464 | "@esy-ocaml/esy-installer" "^0.0.0" 465 | "@esy-ocaml/substs" "^0.0.1" 466 | "@opam/ocamlfind" " >= 1.5.2" 467 | "@opam/yojson" "*" 468 | peerDependencies: 469 | ocaml " >= 4.2.1000 < 4.7.0" 470 | 471 | "@opam/num@*": 472 | version "1.1.0" 473 | uid c64f9fc6e953860619021ca270ab9d57 474 | resolved "@opam/num@1.1.0-c64f9fc6e953860619021ca270ab9d57.tgz" 475 | dependencies: 476 | "@esy-ocaml/esy-installer" "^0.0.0" 477 | "@esy-ocaml/substs" "^0.0.1" 478 | "@opam/ocamlfind" " >= 1.7.3" 479 | peerDependencies: 480 | ocaml " >= 4.6.0" 481 | 482 | "@opam/ocaml-compiler-libs@ >= 100000000.10.0 < 100000000.11.0": 483 | version "100000000.10.0" 484 | uid "8fa775ba888d5647dac83db8c2fcdfd6" 485 | resolved "@opam/ocaml-compiler-libs@100000000.10.0-8fa775ba888d5647dac83db8c2fcdfd6.tgz" 486 | dependencies: 487 | "@esy-ocaml/esy-installer" "^0.0.0" 488 | "@esy-ocaml/substs" "^0.0.1" 489 | "@opam/jbuilder" " >= 1.0.0-beta12" 490 | peerDependencies: 491 | ocaml " >= 4.4.1000" 492 | 493 | "@opam/ocaml-migrate-parsetree@ >= 0.4.0", "@opam/ocaml-migrate-parsetree@ >= 1.0.0", "@opam/ocaml-migrate-parsetree@ >= 1.0.3", "@opam/ocaml-migrate-parsetree@*": 494 | version "1.0.7" 495 | uid "8dd63da93ee2ed29313f5b50471c94ae" 496 | resolved "@opam/ocaml-migrate-parsetree@1.0.7-8dd63da93ee2ed29313f5b50471c94ae.tgz" 497 | dependencies: 498 | "@esy-ocaml/esy-installer" "^0.0.0" 499 | "@esy-ocaml/substs" "^0.0.1" 500 | "@opam/jbuilder" " >= 1.0.0-beta10" 501 | "@opam/ocamlfind" "*" 502 | "@opam/result" "*" 503 | peerDependencies: 504 | ocaml " >= 4.2.0" 505 | 506 | "@opam/ocamlbuild@*": 507 | version "0.12.0" 508 | uid "4d2353d6f9d4e9f658ffea64707c152a" 509 | resolved "@opam/ocamlbuild@0.12.0-4d2353d6f9d4e9f658ffea64707c152a.tgz" 510 | dependencies: 511 | "@esy-ocaml/esy-installer" "^0.0.0" 512 | "@esy-ocaml/substs" "^0.0.1" 513 | peerDependencies: 514 | ocaml " >= 4.3.0" 515 | 516 | "@opam/ocamlfind@", "@opam/ocamlfind@ >= 1.5.0", "@opam/ocamlfind@ >= 1.5.2", "@opam/ocamlfind@ >= 1.5.3", "@opam/ocamlfind@ >= 1.6.0", "@opam/ocamlfind@ >= 1.6.1", "@opam/ocamlfind@ >= 1.7.2", "@opam/ocamlfind@ >= 1.7.3", "@opam/ocamlfind@ >= 1.7.3--1", "@opam/ocamlfind@*": 517 | version "1.7.3--1" 518 | uid "2e5446e160cfd1aae7944b2f7add39b7" 519 | resolved "@opam/ocamlfind@1.7.3--1-2e5446e160cfd1aae7944b2f7add39b7.tgz" 520 | dependencies: 521 | "@esy-ocaml/esy-installer" "^0.0.0" 522 | "@esy-ocaml/substs" "^0.0.1" 523 | "@opam/conf-m4" "*" 524 | peerDependencies: 525 | ocaml " >= 3.12.0" 526 | 527 | "@opam/ocurl@*": 528 | version "0.8.0" 529 | uid ec8f6c4f09c599a49860a9e592b5d5cd 530 | resolved "@opam/ocurl@0.8.0-ec8f6c4f09c599a49860a9e592b5d5cd.tgz" 531 | dependencies: 532 | "@esy-ocaml/esy-installer" "^0.0.0" 533 | "@esy-ocaml/substs" "^0.0.1" 534 | "@opam/base-unix" "*" 535 | "@opam/conf-libcurl" "*" 536 | "@opam/ocamlfind" "*" 537 | peerDependencies: 538 | ocaml "*" 539 | 540 | "@opam/opam-file-format@*": 541 | version "2.0.0-beta5" 542 | uid "170510209ad07e4c39ceddb951d02da8" 543 | resolved "@opam/opam-file-format@2.0.0-beta5-170510209ad07e4c39ceddb951d02da8.tgz" 544 | dependencies: 545 | "@esy-ocaml/esy-installer" "^0.0.0" 546 | "@esy-ocaml/substs" "^0.0.1" 547 | peerDependencies: 548 | ocaml "*" 549 | 550 | "@opam/ppx_ast@ >= 100000000.10.0 < 100000000.11.0": 551 | version "100000000.10.0" 552 | uid c8ef9a150180e7f0b91193de2e8fcafd 553 | resolved "@opam/ppx_ast@100000000.10.0-c8ef9a150180e7f0b91193de2e8fcafd.tgz" 554 | dependencies: 555 | "@esy-ocaml/esy-installer" "^0.0.0" 556 | "@esy-ocaml/substs" "^0.0.1" 557 | "@opam/jbuilder" " >= 1.0.0-beta12" 558 | "@opam/ocaml-compiler-libs" " >= 100000000.10.0 < 100000000.11.0" 559 | "@opam/ocaml-migrate-parsetree" " >= 0.4.0" 560 | peerDependencies: 561 | ocaml " >= 4.4.1000" 562 | 563 | "@opam/ppx_core@ >= 100000000.10.0 < 100000000.11.0": 564 | version "100000000.10.0" 565 | uid "524ee9981e92b66ea1cfcccf3f8d9fdf" 566 | resolved "@opam/ppx_core@100000000.10.0-524ee9981e92b66ea1cfcccf3f8d9fdf.tgz" 567 | dependencies: 568 | "@esy-ocaml/esy-installer" "^0.0.0" 569 | "@esy-ocaml/substs" "^0.0.1" 570 | "@opam/base" " >= 100000000.10.0 < 100000000.11.0" 571 | "@opam/jbuilder" " >= 1.0.0-beta12" 572 | "@opam/ocaml-compiler-libs" " >= 100000000.10.0 < 100000000.11.0" 573 | "@opam/ppx_ast" " >= 100000000.10.0 < 100000000.11.0" 574 | "@opam/ppx_traverse_builtins" " >= 100000000.10.0 < 100000000.11.0" 575 | "@opam/stdio" " >= 100000000.10.0 < 100000000.11.0" 576 | peerDependencies: 577 | ocaml " >= 4.4.1000" 578 | 579 | "@opam/ppx_derivers@*": 580 | version "1.0.0" 581 | uid "8f34e516ed653f2d9d6fbae0d01e64d8" 582 | resolved "@opam/ppx_derivers@1.0.0-8f34e516ed653f2d9d6fbae0d01e64d8.tgz" 583 | dependencies: 584 | "@esy-ocaml/esy-installer" "^0.0.0" 585 | "@esy-ocaml/substs" "^0.0.1" 586 | "@opam/jbuilder" " >= 1.0.0-beta7" 587 | peerDependencies: 588 | ocaml "*" 589 | 590 | "@opam/ppx_deriving@ >= 4.0.0 < 5.0.0": 591 | version "4.2.1" 592 | uid "25184a5ac3bcba32981d0f83f1f03614" 593 | resolved "@opam/ppx_deriving@4.2.1-25184a5ac3bcba32981d0f83f1f03614.tgz" 594 | dependencies: 595 | "@esy-ocaml/esy-installer" "^0.0.0" 596 | "@esy-ocaml/substs" "^0.0.1" 597 | "@opam/cppo" "*" 598 | "@opam/cppo_ocamlbuild" "*" 599 | "@opam/ocaml-migrate-parsetree" "*" 600 | "@opam/ocamlbuild" "*" 601 | "@opam/ocamlfind" " >= 1.6.0" 602 | "@opam/ppx_derivers" "*" 603 | "@opam/ppx_tools" " >= 4.2.3" 604 | "@opam/result" "*" 605 | peerDependencies: 606 | ocaml " > 4.3.0" 607 | 608 | "@opam/ppx_deriving_yojson@*": 609 | version "3.1.0" 610 | uid "8c7c66c0c254daf2603e46b74b9a01b5" 611 | resolved "@opam/ppx_deriving_yojson@3.1.0-1d6b71b8c5e2a367eb62a2f5915d5dfe.tgz" 612 | dependencies: 613 | "@esy-ocaml/esy-installer" "^0.0.0" 614 | "@esy-ocaml/substs" "^0.0.1" 615 | "@opam/cppo" "*" 616 | "@opam/cppo_ocamlbuild" "*" 617 | "@opam/ocamlbuild" "*" 618 | "@opam/ocamlfind" "*" 619 | "@opam/ppx_deriving" " >= 4.0.0 < 5.0.0" 620 | "@opam/result" "*" 621 | "@opam/yojson" "*" 622 | peerDependencies: 623 | ocaml "*" 624 | 625 | "@opam/ppx_driver@ >= 100000000.10.0 < 100000000.11.0": 626 | version "100000000.10.2" 627 | uid "96cc26061aef5b90c72df269bc6090b1" 628 | resolved "@opam/ppx_driver@100000000.10.2-3672f1997608416e639d8938d803d7d1.tgz" 629 | dependencies: 630 | "@esy-ocaml/esy-installer" "^0.0.0" 631 | "@esy-ocaml/substs" "^0.0.1" 632 | "@opam/jbuilder" " >= 1.0.0-beta12" 633 | "@opam/ocaml-migrate-parsetree" " >= 1.0.0" 634 | "@opam/ppx_core" " >= 100000000.10.0 < 100000000.11.0" 635 | "@opam/ppx_optcomp" " >= 100000000.10.0 < 100000000.11.0" 636 | peerDependencies: 637 | ocaml " >= 4.4.1000" 638 | 639 | "@opam/ppx_metaquot@ >= 100000000.10.0 < 100000000.11.0": 640 | version "100000000.10.0" 641 | uid d4448b38e21b0cafd2931de4182a5627 642 | resolved "@opam/ppx_metaquot@100000000.10.0-d4448b38e21b0cafd2931de4182a5627.tgz" 643 | dependencies: 644 | "@esy-ocaml/esy-installer" "^0.0.0" 645 | "@esy-ocaml/substs" "^0.0.1" 646 | "@opam/jbuilder" " >= 1.0.0-beta12" 647 | "@opam/ocaml-migrate-parsetree" " >= 0.4.0" 648 | "@opam/ppx_core" " >= 100000000.10.0 < 100000000.11.0" 649 | "@opam/ppx_driver" " >= 100000000.10.0 < 100000000.11.0" 650 | "@opam/ppx_traverse_builtins" " >= 100000000.10.0 < 100000000.11.0" 651 | peerDependencies: 652 | ocaml " >= 4.4.1000" 653 | 654 | "@opam/ppx_optcomp@ >= 100000000.10.0 < 100000000.11.0": 655 | version "100000000.10.0" 656 | uid "053c049836f5eb4d7fb6ace06c7e890e" 657 | resolved "@opam/ppx_optcomp@100000000.10.0-053c049836f5eb4d7fb6ace06c7e890e.tgz" 658 | dependencies: 659 | "@esy-ocaml/esy-installer" "^0.0.0" 660 | "@esy-ocaml/substs" "^0.0.1" 661 | "@opam/jbuilder" " >= 1.0.0-beta12" 662 | "@opam/ppx_core" " >= 100000000.10.0 < 100000000.11.0" 663 | peerDependencies: 664 | ocaml " >= 4.4.1000" 665 | 666 | "@opam/ppx_sexp_conv@ >= 100000000.9.0": 667 | version "100000000.10.0" 668 | uid d2126218013e363cf6758551efe6e06b 669 | resolved "@opam/ppx_sexp_conv@100000000.10.0-d2126218013e363cf6758551efe6e06b.tgz" 670 | dependencies: 671 | "@esy-ocaml/esy-installer" "^0.0.0" 672 | "@esy-ocaml/substs" "^0.0.1" 673 | "@opam/jbuilder" " >= 1.0.0-beta12" 674 | "@opam/ocaml-migrate-parsetree" " >= 0.4.0" 675 | "@opam/ppx_core" " >= 100000000.10.0 < 100000000.11.0" 676 | "@opam/ppx_driver" " >= 100000000.10.0 < 100000000.11.0" 677 | "@opam/ppx_metaquot" " >= 100000000.10.0 < 100000000.11.0" 678 | "@opam/ppx_type_conv" " >= 100000000.10.0 < 100000000.11.0" 679 | "@opam/sexplib" " >= 100000000.10.0 < 100000000.11.0" 680 | peerDependencies: 681 | ocaml " >= 4.4.1000" 682 | 683 | "@opam/ppx_tools@ >= 4.2.3": 684 | version "5.1.0-4060" 685 | uid "137d38e24bcb38a34eb70b5137a7689d" 686 | resolved "@opam/ppx_tools@5.1.0-4060-137d38e24bcb38a34eb70b5137a7689d.tgz" 687 | dependencies: 688 | "@esy-ocaml/esy-installer" "^0.0.0" 689 | "@esy-ocaml/substs" "^0.0.1" 690 | "@opam/ocamlfind" " >= 1.5.0" 691 | peerDependencies: 692 | ocaml " >= 4.6.0 < 4.7.0" 693 | 694 | "@opam/ppx_tools_versioned@ >= 5.0.1", "@opam/ppx_tools_versioned@*": 695 | version "5.1.0" 696 | uid "8c474fc8622c3cdf7683533fae007ea5" 697 | resolved "@opam/ppx_tools_versioned@5.1.0-8c474fc8622c3cdf7683533fae007ea5.tgz" 698 | dependencies: 699 | "@esy-ocaml/esy-installer" "^0.0.0" 700 | "@esy-ocaml/substs" "^0.0.1" 701 | "@opam/ocaml-migrate-parsetree" " >= 0.4.0" 702 | "@opam/ocamlfind" " >= 1.5.0" 703 | peerDependencies: 704 | ocaml " >= 4.2.0" 705 | 706 | "@opam/ppx_traverse_builtins@ >= 100000000.10.0 < 100000000.11.0": 707 | version "100000000.10.0" 708 | uid "20e5d7a860d5908e647f9fec3ca62399" 709 | resolved "@opam/ppx_traverse_builtins@100000000.10.0-20e5d7a860d5908e647f9fec3ca62399.tgz" 710 | dependencies: 711 | "@esy-ocaml/esy-installer" "^0.0.0" 712 | "@esy-ocaml/substs" "^0.0.1" 713 | "@opam/jbuilder" " >= 1.0.0-beta12" 714 | peerDependencies: 715 | ocaml " >= 4.4.1000" 716 | 717 | "@opam/ppx_type_conv@ >= 100000000.10.0 < 100000000.11.0": 718 | version "100000000.10.0" 719 | uid "4370e2f746ff88a35f41c91b70dc313a" 720 | resolved "@opam/ppx_type_conv@100000000.10.0-4370e2f746ff88a35f41c91b70dc313a.tgz" 721 | dependencies: 722 | "@esy-ocaml/esy-installer" "^0.0.0" 723 | "@esy-ocaml/substs" "^0.0.1" 724 | "@opam/jbuilder" " >= 1.0.0-beta12" 725 | "@opam/ocaml-migrate-parsetree" " >= 0.4.0" 726 | "@opam/ppx_core" " >= 100000000.10.0 < 100000000.11.0" 727 | "@opam/ppx_derivers" "*" 728 | "@opam/ppx_driver" " >= 100000000.10.0 < 100000000.11.0" 729 | "@opam/ppx_metaquot" " >= 100000000.10.0 < 100000000.11.0" 730 | peerDependencies: 731 | ocaml " >= 4.4.1000" 732 | 733 | "@opam/re@*": 734 | version "1.7.1" 735 | uid e00c0af36ff712e9c64b1a50b313ee24 736 | resolved "@opam/re@1.7.1-e00c0af36ff712e9c64b1a50b313ee24.tgz" 737 | dependencies: 738 | "@esy-ocaml/esy-installer" "^0.0.0" 739 | "@esy-ocaml/substs" "^0.0.1" 740 | "@opam/base-bytes" "*" 741 | "@opam/ocamlbuild" "*" 742 | "@opam/ocamlfind" "*" 743 | peerDependencies: 744 | ocaml "*" 745 | 746 | "@opam/react@ >= 1.0.0", "@opam/react@*": 747 | version "1.2.1" 748 | uid "719921829ec3b46cecefeeaefcb6cb61" 749 | resolved "@opam/react@1.2.1-719921829ec3b46cecefeeaefcb6cb61.tgz" 750 | dependencies: 751 | "@esy-ocaml/esy-installer" "^0.0.0" 752 | "@esy-ocaml/substs" "^0.0.1" 753 | "@opam/ocamlbuild" "*" 754 | "@opam/ocamlfind" "*" 755 | "@opam/topkg" " >= 0.9.0" 756 | peerDependencies: 757 | ocaml " >= 4.1.0" 758 | 759 | "@opam/reason@3.0.4", "@opam/reason@^3.0.0": 760 | version "3.0.4" 761 | resolved "@opam/reason@3.0.4-679737717dd62f1873ec5f0bb1e3c293.tgz" 762 | dependencies: 763 | "@esy-ocaml/esy-installer" "^0.0.0" 764 | "@esy-ocaml/substs" "^0.0.1" 765 | "@opam/jbuilder" "*" 766 | "@opam/menhir" " >= 20170418.0.0 <= 20171013.0.0" 767 | "@opam/merlin-extend" " >= 0.3.0" 768 | "@opam/ocaml-migrate-parsetree" "*" 769 | "@opam/ocamlbuild" "*" 770 | "@opam/ocamlfind" "" 771 | "@opam/result" "=1.2.0" 772 | "@opam/utop" " >= 1.17.0" 773 | peerDependencies: 774 | ocaml " >= 4.2.0 < 4.7.0" 775 | 776 | "@opam/result@*", "@opam/result@1.2.0", "@opam/result@=1.2.0": 777 | version "1.2.0" 778 | uid "5c62071e67f4a84e28ceeaac2969d1ae" 779 | resolved "@opam/result@1.2.0-5c62071e67f4a84e28ceeaac2969d1ae.tgz" 780 | dependencies: 781 | "@esy-ocaml/esy-installer" "^0.0.0" 782 | "@esy-ocaml/substs" "^0.0.1" 783 | peerDependencies: 784 | ocaml "*" 785 | 786 | "@opam/rresult@*": 787 | version "0.5.0" 788 | uid "40ec7f5b4aa9eceb37541e80d3ff10d9" 789 | resolved "@opam/rresult@0.5.0-40ec7f5b4aa9eceb37541e80d3ff10d9.tgz" 790 | dependencies: 791 | "@esy-ocaml/esy-installer" "^0.0.0" 792 | "@esy-ocaml/substs" "^0.0.1" 793 | "@opam/ocamlbuild" "*" 794 | "@opam/ocamlfind" "*" 795 | "@opam/result" "*" 796 | "@opam/topkg" "*" 797 | peerDependencies: 798 | ocaml " >= 4.1.0" 799 | 800 | "@opam/sexplib@ >= 100000000.10.0 < 100000000.11.0": 801 | version "100000000.10.0" 802 | uid b9d188a02815394de53995872f854a63 803 | resolved "@opam/sexplib@100000000.10.0-b9d188a02815394de53995872f854a63.tgz" 804 | dependencies: 805 | "@esy-ocaml/esy-installer" "^0.0.0" 806 | "@esy-ocaml/substs" "^0.0.1" 807 | "@opam/jbuilder" " >= 1.0.0-beta12" 808 | "@opam/num" "*" 809 | peerDependencies: 810 | ocaml " >= 4.4.1000" 811 | 812 | "@opam/stdio@ >= 100000000.10.0 < 100000000.11.0": 813 | version "100000000.10.0" 814 | uid "1d609fa241244c86cd7029e29ee910b3" 815 | resolved "@opam/stdio@100000000.10.0-1d609fa241244c86cd7029e29ee910b3.tgz" 816 | dependencies: 817 | "@esy-ocaml/esy-installer" "^0.0.0" 818 | "@esy-ocaml/substs" "^0.0.1" 819 | "@opam/base" " >= 100000000.10.0 < 100000000.11.0" 820 | "@opam/jbuilder" " >= 1.0.0-beta12" 821 | "@opam/sexplib" " >= 100000000.10.0 < 100000000.11.0" 822 | peerDependencies: 823 | ocaml " >= 4.4.1000" 824 | 825 | "@opam/topkg@ >= 0.9.0", "@opam/topkg@*": 826 | version "0.9.1" 827 | uid "516192416bf683eb7edf2dbd649d014d" 828 | resolved "@opam/topkg@0.9.1-516192416bf683eb7edf2dbd649d014d.tgz" 829 | dependencies: 830 | "@esy-ocaml/esy-installer" "^0.0.0" 831 | "@esy-ocaml/substs" "^0.0.1" 832 | "@opam/ocamlbuild" "*" 833 | "@opam/ocamlfind" " >= 1.6.1" 834 | "@opam/result" "*" 835 | peerDependencies: 836 | ocaml " >= 4.1.0" 837 | 838 | "@opam/uchar@*": 839 | version "0.0.2" 840 | uid "234806fdd6b50b459a41e955c67d875d" 841 | resolved "@opam/uchar@0.0.2-234806fdd6b50b459a41e955c67d875d.tgz" 842 | dependencies: 843 | "@esy-ocaml/esy-installer" "^0.0.0" 844 | "@esy-ocaml/substs" "^0.0.1" 845 | "@opam/ocamlbuild" "*" 846 | peerDependencies: 847 | ocaml " >= 3.12.0" 848 | 849 | "@opam/utop@ >= 1.17.0": 850 | version "2.0.2" 851 | uid a769ef27599e130f0d356979ed5e07af 852 | resolved "@opam/utop@2.0.2-a769ef27599e130f0d356979ed5e07af.tgz" 853 | dependencies: 854 | "@esy-ocaml/esy-installer" "^0.0.0" 855 | "@esy-ocaml/substs" "^0.0.1" 856 | "@opam/base-threads" "*" 857 | "@opam/base-unix" "*" 858 | "@opam/camomile" "*" 859 | "@opam/cppo" " >= 1.1.2" 860 | "@opam/jbuilder" " >= 1.0.0-beta9" 861 | "@opam/lambda-term" " >= 1.2.0" 862 | "@opam/lwt" "*" 863 | "@opam/lwt_react" "*" 864 | "@opam/ocamlfind" " >= 1.7.2" 865 | "@opam/react" " >= 1.0.0" 866 | peerDependencies: 867 | ocaml " >= 4.2.3000" 868 | 869 | "@opam/yaml@*": 870 | version "0.1.0" 871 | uid ee9eef9b1d6261844a44b0649c0f771c 872 | resolved "@opam/yaml@0.1.0-ee9eef9b1d6261844a44b0649c0f771c.tgz" 873 | dependencies: 874 | "@esy-ocaml/esy-installer" "^0.0.0" 875 | "@esy-ocaml/substs" "^0.0.1" 876 | "@opam/ctypes" " >= 0.12.0" 877 | "@opam/fmt" "*" 878 | "@opam/jbuilder" " >= 1.0.0-beta10" 879 | "@opam/logs" "*" 880 | "@opam/ppx_sexp_conv" " >= 100000000.9.0" 881 | "@opam/rresult" "*" 882 | peerDependencies: 883 | ocaml " >= 4.3.0" 884 | 885 | "@opam/yojson@*": 886 | version "1.4.0" 887 | uid "139a44cad85290f31b72aecca4e60c65" 888 | resolved "@opam/yojson@1.4.0-139a44cad85290f31b72aecca4e60c65.tgz" 889 | dependencies: 890 | "@esy-ocaml/esy-installer" "^0.0.0" 891 | "@esy-ocaml/substs" "^0.0.1" 892 | "@opam/biniou" " >= 1.2.0" 893 | "@opam/cppo" "*" 894 | "@opam/easy-format" "*" 895 | "@opam/jbuilder" "*" 896 | peerDependencies: 897 | ocaml " >= 4.2.3000" 898 | 899 | "@opam/zed@ >= 1.2.0": 900 | version "1.6.0" 901 | uid "9a4c3157c2db719927f9648234487c6d" 902 | resolved "@opam/zed@1.6.0-9a4c3157c2db719927f9648234487c6d.tgz" 903 | dependencies: 904 | "@esy-ocaml/esy-installer" "^0.0.0" 905 | "@esy-ocaml/substs" "^0.0.1" 906 | "@opam/base-bytes" "*" 907 | "@opam/camomile" " >= 0.8.0" 908 | "@opam/jbuilder" " >= 1.0.0-beta9" 909 | "@opam/react" "*" 910 | peerDependencies: 911 | ocaml " >= 4.2.3000" 912 | 913 | lwt-node@*: 914 | version "0.2.0" 915 | resolved "https://registry.yarnpkg.com/lwt-node/-/lwt-node-0.2.0.tgz#7b5b90ff830d077ba70b201d3c0a345f7dcb0891" 916 | dependencies: 917 | "@esy-ocaml/esy-installer" "^0.0.0" 918 | "@opam/alcotest" "0.8.1" 919 | "@opam/bisect_ppx" "1.3.2" 920 | "@opam/jbuilder" "^1.0.0-beta16" 921 | "@opam/lwt" "3.2.0" 922 | "@opam/reason" "3.0.4" 923 | refmterr "3.0.4" 924 | peerDependencies: 925 | ocaml "4.6.1" 926 | 927 | ocaml@~4.6.000: 928 | version "4.6.1" 929 | resolved "https://registry.yarnpkg.com/ocaml/-/ocaml-4.6.1.tgz#8babea2c41a9f734313b4fc1841ec0963c9d7a07" 930 | 931 | refmterr@3.0.4: 932 | version "3.0.4" 933 | resolved "https://registry.yarnpkg.com/refmterr/-/refmterr-3.0.4.tgz#9fef071bdffd9946e78ba5f984e8cff4ff75e198" 934 | dependencies: 935 | "@opam/jbuilder" "*" 936 | "@opam/re" "*" 937 | "@opam/reason" "^3.0.0" 938 | peerDependencies: 939 | ocaml " >= 4.2.0 < 4.6.0" 940 | 941 | "testre-ppx@link:../ppx_test/ppx": 942 | version "0.0.0" 943 | uid "" 944 | 945 | yarn-pkg-config@esy-ocaml/yarn-pkg-config#d488cd9321cd5036bd36ec96744ce78c5d45fc49: 946 | version "0.29.5" 947 | resolved "https://codeload.github.com/esy-ocaml/yarn-pkg-config/tar.gz/d488cd9321cd5036bd36ec96744ce78c5d45fc49" 948 | -------------------------------------------------------------------------------- /esyi.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredly/esyi/217a57f30355370b86f56ac3ef4c4ec0ebe0baab/esyi.opam -------------------------------------------------------------------------------- /jbuild-ignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esyi", 3 | "version": "0.0.1", 4 | "description": "Package installer for reason", 5 | "author": "Jared Forsyth ", 6 | "license": "MIT", 7 | "scripts": { 8 | "watch": "esy b && esy jbuilder exec esyi ../testing", 9 | "watcher": "watchexec -w lib -w bin npm run watch -- -s", 10 | "run": "esy jbuilder build src/bin/esyi.exe && esy jbuilder exec esyi ../testing && (cd ../testing && npm start)", 11 | "clean-run": "rm -rf ../testing/.esy-modules && npm run run", 12 | "wall": "esy jbuilder build src/bin/esyi.exe && esy jbuilder exec esyi ../../games/reason-wall-demo", 13 | "wall-install": "(cd ../../games/reason-wall-demo && esy b)", 14 | "clean-wall": "rm -rf ../../game/reason-wall-demo/.esy-modules && npm run wall", 15 | "solve-wall": "esy jbuilder exec esyi solve ../../games/reason-wall-demo/", 16 | "self-install": "_build/install/default/bin/esyi .", 17 | "self-solve": "_build/install/default/bin/esyi solve .", 18 | "test": "esy jbuilder build test/Test.exe && esy jbuilder exec test/Test.exe" 19 | }, 20 | "esy": { 21 | "build": [ 22 | ["jbuilder", "build"] 23 | ], 24 | "install": [ 25 | "esy-installer" 26 | ], 27 | "buildsInSource": "_build" 28 | }, 29 | "dependencies": { 30 | "@opam/lambda-term": "^1.11.0", 31 | "@opam/yojson": "*", 32 | "@opam/cudf": "0.9", 33 | "@opam/ocurl": "*", 34 | "@opam/yaml": "*", 35 | "@opam/ppx_deriving_yojson": "*", 36 | "@opam/mccs": "*", 37 | "lwt-node": "*", 38 | "@jaredly/testre": "*", 39 | "testre-ppx": "*", 40 | "@opam/opam-file-format": "2.0.0~beta5", 41 | "@opam/lwt": "^3.1.0", 42 | "@opam/reason": "^3.0.0", 43 | "@opam/jbuilder": "^1.0+beta16", 44 | "@opam/merlin": "^3.0.5", 45 | "@opam/alcotest": "*", 46 | "ocaml": "~4.6.000" 47 | }, 48 | "// testre": "link:./../ppx_test/runtime", 49 | "// testre-ppx": "link:./../ppx_test/ppx", 50 | "buildDependencies": { 51 | }, 52 | "peerDependencies": { 53 | "ocaml": "~4.6.000" 54 | }, 55 | "devDependencies": { 56 | }, 57 | "resolutions": { 58 | "**/@opam/result": "1.2.0" 59 | }, 60 | "private": true 61 | } 62 | -------------------------------------------------------------------------------- /src/bin/esyi.re: -------------------------------------------------------------------------------- 1 | let (/+) = Filename.concat; 2 | 3 | let homeDir = () => { 4 | let uid = Unix.getuid(); 5 | let home = Unix.getpwuid(uid).Unix.pw_dir; 6 | /* TODO: fallback to $HOME here */ 7 | /* TODO: make it return resul (string, _) instead */ 8 | home; 9 | }; 10 | 11 | let solve = basedir => { 12 | let homeDir = homeDir(); 13 | let json = Yojson.Basic.from_file(basedir /+ "package.json"); 14 | let config = { 15 | Shared.Types.esyOpamOverrides: homeDir /+ ".esyi/esy-opam-override", 16 | opamRepository: homeDir /+ ".esyi/opam-repository", 17 | baseDirectory: basedir, 18 | }; 19 | let env = Solve.solve(config, `PackageJson(json)); 20 | let json = Shared.Env.to_yojson(Shared.Types.Source.to_yojson, env); 21 | let chan = open_out(basedir /+ "esyi.lock.json"); 22 | Yojson.Safe.pretty_to_channel(chan, json); 23 | close_out(chan); 24 | }; 25 | 26 | let fetch = (basedir) => { 27 | let json = Yojson.Safe.from_file(basedir /+ "esyi.lock.json"); 28 | let env = switch (Shared.Env.of_yojson(Shared.Types.Source.of_yojson, json)) { 29 | | Error(a) => failwith("Bad lockfile") 30 | | Ok(a) => a 31 | }; 32 | Shared.Files.removeDeep(basedir /+ "node_modules"); 33 | Fetch.fetch(basedir, env); 34 | }; 35 | 36 | Printexc.record_backtrace(true); 37 | 38 | switch (Sys.argv) { 39 | | [|_, "solve", basedir|] => solve(basedir) 40 | | [|_, "fetch", basedir|] => fetch(basedir) 41 | | [|_, basedir|] => { 42 | solve(basedir); 43 | fetch(basedir); 44 | } 45 | | _ => print_endline("Usage: esyi basedir") 46 | }; -------------------------------------------------------------------------------- /src/bin/jbuild: -------------------------------------------------------------------------------- 1 | (jbuild_version 1) 2 | 3 | (executable 4 | ((name esyi) 5 | (public_name esyi) 6 | (libraries (fetch solve)))) 7 | -------------------------------------------------------------------------------- /src/fetch/Fetch.re: -------------------------------------------------------------------------------- 1 | 2 | let (/+) = Filename.concat; 3 | 4 | let startsWith = (string, prefix) => String.length(string) >= String.length(prefix) && String.sub(string, 0, String.length(prefix)) == prefix; 5 | 6 | let fetch = (basedir, env) => { 7 | open Shared.Env; 8 | let packagesToFetch = Hashtbl.create(100); 9 | let addPackage = ({name, version, source}) => Hashtbl.replace(packagesToFetch, (name, version), source); 10 | env.targets |> List.iter(((_, {runtimeBag})) => runtimeBag |> List.iter(addPackage)); 11 | env.buildDependencies |> List.iter(({package, runtimeBag}) => { 12 | addPackage(package); 13 | List.iter(addPackage, runtimeBag) 14 | }); 15 | 16 | let nodeModules = basedir /+ "node_modules"; 17 | /** OOh want to remove everything except for */ 18 | /* Shared.Files.removeDeep(nodeModules); */ 19 | if (Shared.Files.exists(nodeModules)) { 20 | Shared.Files.readDirectory(nodeModules) |> List.filter(x => !startsWith(x, ".esy")) |> List.iter(x => Shared.Files.removeDeep(nodeModules /+ x)); 21 | }; 22 | Shared.Files.mkdirp(nodeModules); 23 | 24 | let cache = nodeModules /+ ".esy-cache-archives"; 25 | Shared.Files.mkdirp(cache); 26 | let modcache = nodeModules /+ ".esy-unpacked"; 27 | Shared.Files.mkdirp(modcache); 28 | 29 | Hashtbl.iter(((name, version), source) => { 30 | let dest = modcache /+ FetchUtils.absname(name, version); 31 | FetchUtils.unpackArchive(dest, cache, name, version, source); 32 | let nmDest = nodeModules /+ name; 33 | if (Shared.Files.exists(nmDest)) { 34 | failwith("Duplicate modules") 35 | }; 36 | Shared.Files.mkdirp(Filename.dirname(nmDest)); 37 | Shared.Files.symlink(dest, nmDest); 38 | }, packagesToFetch); 39 | 40 | let resolved = Resolved.fromEnv(env, modcache); 41 | Shared.Files.writeFile(modcache /+ "esy.resolved", Yojson.Basic.pretty_to_string(resolved)) |> ignore; 42 | }; 43 | -------------------------------------------------------------------------------- /src/fetch/FetchUtils.re: -------------------------------------------------------------------------------- 1 | 2 | open Shared; 3 | 4 | let (/+) = Filename.concat; 5 | 6 | let resolvedString = (name, version) => Types.resolvedPrefix ++ name ++ "--" ++ Lockfile.viewRealVersion(version); 7 | 8 | /** Hack to always cache the ocaml build :P */ 9 | let resolveString = (name, version) => name == "ocaml" 10 | ? "esyi4-" ++ name ++ "--" ++ Lockfile.viewRealVersion(version) 11 | : resolvedString(name, version); 12 | 13 | let addResolvedFieldToPackageJson = (filename, name, version) => { 14 | let json = switch (Yojson.Basic.from_file(filename)) { 15 | | `Assoc(items) => items 16 | | _ => failwith("bad json") 17 | }; 18 | let raw = Yojson.Basic.pretty_to_string(`Assoc([("_resolved", `String(resolvedString(name, version))), ...json])); 19 | Files.writeFile(filename, raw) |> Files.expectSuccess("Could not write back package json"); 20 | }; 21 | 22 | let absname = (name, version) => { 23 | name ++ "__" ++ Lockfile.viewRealVersion(version) 24 | }; 25 | 26 | /** 27 | * Unpack an archive into place, and then for opam projects create a package.json & apply files / patches. 28 | */ 29 | let unpackArchive = (dest, cache, name, version, source) => { 30 | if (Files.isDirectory(dest)) { 31 | print_endline("Dependency exists -- assuming it is fine " ++ dest) 32 | } else { 33 | Files.mkdirp(dest); 34 | 35 | let getSource = source => {switch source { 36 | | Types.Source.Archive(url, _checksum) => { 37 | let safe = Str.global_replace(Str.regexp("/"), "-", name); 38 | let withVersion = safe ++ Lockfile.viewRealVersion(version); 39 | let tarball = cache /+ withVersion ++ ".tarball"; 40 | if (!Files.isFile(tarball)) { 41 | ExecCommand.execSync(~cmd="curl -L --output "++ tarball ++ " " ++ url, ()) |> snd |> Files.expectSuccess("failed to fetch with curl"); 42 | }; 43 | ExecCommand.execSync(~cmd="tar xf " ++ tarball ++ " --strip-components 1 -C " ++ dest, ()) |> snd |> Files.expectSuccess("failed to untar"); 44 | } 45 | | Types.Source.NoSource => () 46 | | Types.Source.GithubSource(user, repo, ref) => { 47 | let safe = Str.global_replace(Str.regexp("/"), "-", name ++ "__" ++ user ++ "__" ++ repo ++ "__" ++ ref); 48 | let tarball = cache /+ safe ++ ".tarball"; 49 | if (!Files.isFile(tarball)) { 50 | let tarUrl = "https://api.github.com/repos/" ++ user ++ "/" ++ repo ++ "/tarball/" ++ ref; 51 | ExecCommand.execSync(~cmd="curl -L --output "++ tarball ++ " " ++ tarUrl, ()) |> snd |> Files.expectSuccess("failed to fetch with curl"); 52 | }; 53 | ExecCommand.execSync(~cmd="tar xf " ++ tarball ++ " --strip-components 1 -C " ++ dest, ()) |> snd |> Files.expectSuccess("failed to untar"); 54 | } 55 | | Types.Source.GitSource(gitUrl, commit) => { 56 | let safe = Str.global_replace(Str.regexp("/"), "-", name); 57 | let withVersion = safe ++ Lockfile.viewRealVersion(version); 58 | let tarball = cache /+ withVersion ++ ".tarball"; 59 | if (!Files.isFile(tarball)) { 60 | print_endline("[fetching git repo " ++ gitUrl ++ " at commit " ++ commit); 61 | let gitdest = cache /+ "git-" ++ withVersion; 62 | /** TODO we want to have the commit nailed down by this point tho */ 63 | ExecCommand.execSync(~cmd="git clone " ++ gitUrl ++ " " ++ gitdest, ()) |> snd |> Files.expectSuccess("Unable to clone git repo " ++ gitUrl); 64 | ExecCommand.execSync(~cmd="cd " ++ gitdest ++ " && git checkout " ++ commit ++ " && rm -rf .git", ()) |> snd |> Files.expectSuccess("Unable to checkout " ++ gitUrl ++ " at " ++ commit); 65 | ExecCommand.execSync(~cmd="tar czf " ++ tarball ++ " " ++ gitdest, ()) |> snd |> Files.expectSuccess("Unable to tar up"); 66 | ExecCommand.execSync(~cmd="mv " ++ gitdest ++ " " ++ dest, ()) |> snd |> Files.expectSuccess("Unable to move"); 67 | } else { 68 | ExecCommand.execSync(~cmd="tar xf " ++ tarball ++ " --strip-components 1 -C " ++ dest, ()) |> snd |> Files.expectSuccess("failed to untar"); 69 | } 70 | } 71 | | File(_) => failwith("Cannot handle a file source yet") 72 | }}; 73 | 74 | let packageJson = dest /+ "package.json"; 75 | let (source, maybeOpamFile) = source; 76 | getSource(source); 77 | switch maybeOpamFile { 78 | | Some((packageJson, files, patches)) => { 79 | if (Files.exists(dest /+ "esy.json")) { 80 | Unix.unlink(dest /+ "esy.json"); 81 | }; 82 | let raw = Yojson.Basic.pretty_to_string(Yojson.Safe.to_basic(packageJson)); 83 | Files.writeFile(dest /+ "package.json", raw) |> Files.expectSuccess("could not write package.json"); 84 | files |> List.iter(((relpath, contents)) => { 85 | Files.mkdirp(Filename.dirname(dest /+ relpath)); 86 | Files.writeFile(dest /+ relpath, contents) |> Files.expectSuccess("could not write file " ++ relpath) 87 | }); 88 | 89 | patches |> List.iter((abspath) => { 90 | ExecCommand.execSync( 91 | ~cmd=Printf.sprintf("sh -c 'cd %s && patch -p1 < %s'", dest, abspath), 92 | () 93 | ) |> snd |> Files.expectSuccess("Failed to patch") 94 | }); 95 | } 96 | | None => { 97 | if (!Files.exists(packageJson)) { 98 | failwith("No opam file or package.json"); 99 | }; 100 | addResolvedFieldToPackageJson(packageJson, name, version); 101 | } 102 | }; 103 | } 104 | }; -------------------------------------------------------------------------------- /src/fetch/Resolved.re: -------------------------------------------------------------------------------- 1 | 2 | open Shared.Env; 3 | 4 | let taggedName = (name, version) => name ++ ":" ++ Shared.Lockfile.viewRealVersion(version); 5 | let taggedPackage = ({name, version}) => taggedName(name, version); 6 | 7 | let findPackage = (packages, name) => List.find(p => p.name == name, packages); 8 | 9 | let rec depsForPackage = (package, runtimeBag) => { 10 | `Assoc(package.runtime |> List.map(((name, _, version)) => { 11 | let found = findPackage(runtimeBag, name); 12 | (taggedPackage(found), depsForPackage(found, runtimeBag)) 13 | })) 14 | }; 15 | 16 | /* let fromRootPackage = ({package, runtimeBag}) => depsForPackage */ 17 | 18 | let packagesForRoot = ({package, runtimeBag}) => [package, ...runtimeBag]; 19 | 20 | let collectPackages = env => { 21 | List.append( 22 | env.targets |> List.map(((_, root)) => packagesForRoot(root)) |> List.concat, 23 | env.buildDependencies |> List.map(packagesForRoot) |> List.concat 24 | ) 25 | }; 26 | 27 | let fromEnv = (env, modulesCache) => { 28 | let allPackages = collectPackages(env); 29 | `Assoc([ 30 | ("sources", allPackages |> List.map(package => (taggedPackage(package), `String(Filename.concat(modulesCache, FetchUtils.absname(package.name, package.version))))) |> x => `Assoc(x)), 31 | ("targets", env.targets |> List.map(((target, root)) => ("hello", depsForPackage(root.package, root.runtimeBag))) |> x => `Assoc(x)), 32 | ("buildPackages", env.buildDependencies |> List.map(root => (taggedPackage(root.package), depsForPackage(root.package, root.runtimeBag))) |> x => `Assoc(x)), 33 | ("buildDependencies", allPackages |> List.map(package => (taggedPackage(package), `List(package.build |> List.map(((name, _, realVersion)) => `String(taggedName(name, realVersion)) )))) |> x => `Assoc(x)), 34 | ]) 35 | }; 36 | -------------------------------------------------------------------------------- /src/fetch/jbuild: -------------------------------------------------------------------------------- 1 | (jbuild_version 1) 2 | 3 | (library 4 | ((name fetch) 5 | (preprocess (pps (ppx_deriving_yojson testre-ppx ppx_driver))) 6 | (libraries (shared opam str curl cudf yaml ppx_deriving_yojson.runtime mccs yojson opam-file-format)))) 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/npm/NpmVersion.re: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * High level handling of npm versions 4 | */ 5 | 6 | let viewConcrete = ((m, i, p, r)) => { 7 | ([m, i, p] |> List.map(string_of_int) |> String.concat(".")) 8 | ++ 9 | switch r { | None => "" | Some(a) => a} 10 | }; 11 | let viewRange = Shared.GenericVersion.view(viewConcrete); 12 | 13 | /** 14 | * Tilde: 15 | * Allows patch-level changes if a minor version is specified on the comparator. 16 | * Allows minor-level changes if not. 17 | ~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0 18 | ~1.2 := >=1.2.0 <1.(2+1).0 := >=1.2.0 <1.3.0 (Same as 1.2.x) 19 | ~1 := >=1.0.0 <(1+1).0.0 := >=1.0.0 <2.0.0 (Same as 1.x) 20 | ~0.2.3 := >=0.2.3 <0.(2+1).0 := >=0.2.3 <0.3.0 21 | ~0.2 := >=0.2.0 <0.(2+1).0 := >=0.2.0 <0.3.0 (Same as 0.2.x) 22 | ~0 := >=0.0.0 <(0+1).0.0 := >=0.0.0 <1.0.0 (Same as 0.x) 23 | ~1.2.3-beta.2 := >=1.2.3-beta.2 <1.3.0 Note that prereleases in the 1.2.3 version will be allowed, if they are greater than or equal to beta.2. So, 1.2.3-beta.4 would be allowed, but 1.2.4-beta.2 would not, because it is a prerelease of a different [major, minor, patch] tuple. 24 | */ 25 | 26 | [@test Shared.GenericVersion.([ 27 | ("~1.2.3", parseRange(">=1.2.3 <1.3.0")), 28 | ("~1.2", parseRange(">=1.2.0 <1.3.0")), 29 | ("~1.2", parseRange("1.2.x")), 30 | ("~1", parseRange(">=1.0.0 <2.0.0")), 31 | ("~1", parseRange("1.x")), 32 | ("~0.2.3", parseRange(">=0.2.3 <0.3.0")), 33 | ("~0", parseRange("0.x")), 34 | 35 | ("1.2.3", Exactly((1,2,3,None))), 36 | ("1.2.3-alpha2", Exactly((1,2,3,Some("-alpha2")))), 37 | ("1.2.3 - 2.3.4", And(AtLeast((1,2,3,None)), AtMost((2,3,4,None)))), 38 | ])] 39 | [@test.print (fmt, v) => Format.fprintf(fmt, "%s", viewRange(v))] 40 | let parseRange = version => { 41 | try (ParseNpm.parse(version)) { 42 | | Failure(message) => { 43 | print_endline("Failed with message: " ++ message ++ " : " ++ version); 44 | Any 45 | } 46 | | e => { 47 | print_endline("Invalid version! pretending its any: " ++ version ++ " " ++ Printexc.to_string(e)); 48 | Any 49 | } 50 | } 51 | }; 52 | 53 | let isint = v => try ({ignore(int_of_string(v)); true}) { | _ => false }; 54 | 55 | let getRest = parts => parts == [] ? None : Some(String.concat(".", parts)); 56 | 57 | let parseConcrete = version => { 58 | let parts = String.split_on_char('.', version); 59 | switch parts { 60 | | [major, minor, patch, ...rest] when isint(major) && isint(minor) && isint(patch) => 61 | (int_of_string(major), int_of_string(minor), int_of_string(patch), getRest(rest)) 62 | | [major, minor, ...rest] when isint(major) && isint(minor) => 63 | (int_of_string(major), int_of_string(minor), 0, getRest(rest)) 64 | | [major, ...rest] when isint(major) => 65 | (int_of_string(major), 0, 0, getRest(rest)) 66 | | rest => 67 | (0, 0, 0, getRest(rest)) 68 | } 69 | }; 70 | 71 | let after = (a, prefix) => { 72 | let al = String.length(a); 73 | let pl = String.length(prefix); 74 | if (al > pl && String.sub(a, 0, pl) == prefix) { 75 | Some(String.sub(a, pl, al - pl)) 76 | } else { 77 | None 78 | } 79 | }; 80 | 81 | let compareExtra = (a, b) => { 82 | switch (a, b) { 83 | | (Some(a), Some(b)) => { 84 | switch (after(a, "-beta"), after(b, "-beta")) { 85 | | (Some(a), Some(b)) => try(int_of_string(a) - int_of_string(b)) { | _ => compare(a, b) } 86 | | _ => switch (after(a, "-alpha"), after(b, "-alpha")) { 87 | | (Some(a), Some(b)) => try(int_of_string(a) - int_of_string(b)) { | _ => compare(a, b) } 88 | | _ => try(int_of_string(a) - int_of_string(b)) { | _ => compare(a, b) } 89 | } 90 | } 91 | } 92 | | _ => compare(a, b) 93 | } 94 | }; 95 | 96 | let compare = ((ma, ia, pa, ra), (mb, ib, pb, rb)) => { 97 | ma != mb 98 | ? (ma - mb) 99 | : ( 100 | ia != ib 101 | ? (ia - ib) 102 | : ( 103 | pa != pb 104 | ? (pa - pb) 105 | : compareExtra(ra, rb) 106 | ) 107 | ) 108 | }; 109 | 110 | let matches = Shared.GenericVersion.matches(compare); 111 | -------------------------------------------------------------------------------- /src/npm/OpamConcrete.re: -------------------------------------------------------------------------------- 1 | open Shared.Types; 2 | open Shared; 3 | 4 | /** 5 | * This file is about parsing opam versions from `package.json` specifications. 6 | * So the concrete versions are opam (following the "interspersed alpha and numeric" 7 | * pattern), but the range syntax is what you get in npm. 8 | * So ^1.0+beta5 becomes >= 1.0-beta5 && < 2 9 | */ 10 | 11 | let triple = (major, minor, patch) => { 12 | Shared.Types.opamFromNpmConcrete((major, minor, patch, None)) 13 | }; 14 | 15 | [@test [ 16 | (("123abc", 0), 3), 17 | (("a123abc", 1), 4), 18 | (("abc", 1), 1), 19 | (("abc", 3), 3), 20 | ]] 21 | let rec getNums = (text, pos) => { 22 | if (pos < String.length(text)) { 23 | switch (text.[pos]) { 24 | | '0'..'9' => getNums(text, pos + 1) 25 | | _ => pos 26 | } 27 | } else { 28 | pos 29 | } 30 | }; 31 | 32 | let rec getNonNums = (text, pos) => { 33 | if (pos < String.length(text)) { 34 | switch (text.[pos]) { 35 | | '0'..'9' => pos 36 | | _ => getNonNums(text, pos + 1) 37 | } 38 | } else { 39 | pos 40 | } 41 | }; 42 | 43 | [@test [ 44 | ("1.2.3", triple(1,2,3)), 45 | ("1.2.3~alpha", Shared.Types.opamFromNpmConcrete((1,2,3, Some("~alpha")))), 46 | ]] 47 | let parseConcrete = text => { 48 | let len = String.length(text); 49 | let rec getNum = (pos) => { 50 | if (pos >= len) { 51 | None 52 | } else { 53 | let tpos = getNums(text, pos); 54 | let num = String.sub(text, pos, tpos - pos); 55 | Some(Num(int_of_string(num), getString(tpos))) 56 | } 57 | } and getString = pos => { 58 | if (pos >= len) { 59 | None 60 | } else switch (text.[pos]) { 61 | | '0'..'9' => Some(Alpha("", getNum(pos))) 62 | | _ => { 63 | let tpos = getNonNums(text, pos); 64 | let t = String.sub(text, pos, tpos - pos); 65 | Some(Alpha(t, getNum(tpos))) 66 | } 67 | } 68 | }; 69 | switch (getString(0)) { 70 | | None => Alpha("", None) 71 | | Some(a) => a 72 | } 73 | }; 74 | 75 | let rec findNextForTilde = (Alpha(t, n)) => { 76 | if (t == "." || t == "") { 77 | switch n { 78 | | None => `End 79 | | Some(Num(n, rest)) => { 80 | switch rest { 81 | | None => `LastNum(Alpha(t, Some(Num(n + 1, None)))) 82 | | Some(rest) => switch (findNextForTilde(rest)) { 83 | | `End => `LastNum(Alpha(t, Some(Num(n + 1, None)))) 84 | | `LastNum(_) => `Done(Alpha(t, Some(Num(n + 1, None)))) 85 | | `Done(rest) => `Done(Alpha(t, Some(Num(n, Some(rest))))) 86 | } 87 | } 88 | } 89 | } 90 | } else { 91 | `End 92 | } 93 | }; 94 | 95 | [@test [ 96 | (parseConcrete("1.2.3"), parseConcrete("1.3")), 97 | (parseConcrete("1.5.4-alpha6"), parseConcrete("1.6")), 98 | (parseConcrete("1.2"), parseConcrete("2")), 99 | ]] 100 | [@test.print (fmt, t) => Format.fprintf(fmt, "%s", Types.viewOpamConcrete(t))] 101 | let findNextForTilde = (version) => switch (findNextForTilde(version)) { 102 | | `End => failwith("Cannot tilde a version with no numbers") 103 | | `LastNum(version) => version 104 | | `Done(version) => version 105 | }; 106 | 107 | [@test [ 108 | (parseConcrete("1.2.3"), parseConcrete("2")), 109 | (parseConcrete("0.2.3"), parseConcrete("0.3")), 110 | ]] 111 | [@test.print (fmt, t) => Format.fprintf(fmt, "%s", Types.viewOpamConcrete(t))] 112 | let rec findNextForCaret = (Alpha(t, n)) => { 113 | if (t == "." || t == "") { 114 | switch n { 115 | | None => failwith("No nonzero numbers") 116 | | Some(Num(0, rest)) => { 117 | switch rest { 118 | | None => failwith("No nonzero numbers") 119 | | Some(rest) => Alpha(t, Some(Num(0, Some(findNextForTilde(rest))))) 120 | } 121 | } 122 | | Some(Num(n, rest)) => { 123 | Alpha(t, Some(Num(n + 1, None))) 124 | } 125 | } 126 | } else { 127 | failwith("No nonzero numbers") 128 | } 129 | }; 130 | 131 | let parseOpamSimple = text => { 132 | if (text == "*") { 133 | GenericVersion.Any 134 | } else if (text == "") { 135 | GenericVersion.Any 136 | } else if (text.[0] == '^') { 137 | let version = parseConcrete(ParseNpm.sliceToEnd(text, 1)); 138 | let next = findNextForCaret(version); 139 | GenericVersion.( 140 | And(AtLeast(version), LessThan(next)) 141 | ) 142 | } else if (text.[0] == '~') { 143 | let version = parseConcrete(ParseNpm.sliceToEnd(text, 1)); 144 | let next = findNextForTilde(version); 145 | GenericVersion.( 146 | And(AtLeast(version), LessThan(next)) 147 | ) 148 | } else if (text.[0] == '=') { 149 | GenericVersion.Exactly(parseConcrete(ParseNpm.sliceToEnd(text, 1))) 150 | } else if (text.[0] == '<' && text.[1] == '=') { 151 | GenericVersion.AtMost(parseConcrete(ParseNpm.sliceToEnd(text, 2))) 152 | } else if (text.[0] == '<') { 153 | GenericVersion.LessThan(parseConcrete(ParseNpm.sliceToEnd(text, 1))) 154 | } else if (text.[0] == '>' && text.[1] == '=') { 155 | GenericVersion.AtLeast(parseConcrete(ParseNpm.sliceToEnd(text, 2))) 156 | } else if (text.[0] == '>') { 157 | GenericVersion.GreaterThan(parseConcrete(ParseNpm.sliceToEnd(text, 1))) 158 | } else { 159 | GenericVersion.Exactly(parseConcrete(text)) 160 | } 161 | }; 162 | 163 | let parseNpmRange = ParseNpm.parseOrs(text => ParseNpm.parseSimples(text, parseOpamSimple)); -------------------------------------------------------------------------------- /src/npm/PackageJson.re: -------------------------------------------------------------------------------- 1 | open Cudf; 2 | open Shared; 3 | open Types; 4 | 5 | /* 6 | * Get dependencies data out of a package.json 7 | */ 8 | 9 | let getOpam = name => { 10 | let ln = 6; 11 | if (String.length(name) > ln && String.sub(name, 0, ln) == "@opam/") { 12 | Some(name) 13 | /* Some(String.sub(name, ln, String.length(name) - ln)) */ 14 | } else { 15 | None 16 | } 17 | }; 18 | 19 | let isGithub = value => { 20 | Str.string_match(Str.regexp("[a-zA-Z][a-zA-Z0-9-]+/[a-zA-Z0-9_-]+(#.+)?"), value, 0) 21 | }; 22 | 23 | let startsWith = (value, needle) => String.length(value) > String.length(needle) && String.sub(value, 0, String.length(needle)) == needle; 24 | 25 | let parseNpmSource = ((name, value)) => { 26 | switch (getOpam(name)) { 27 | | Some(name) => (name, 28 | switch (Shared.GithubVersion.parseGithubVersion(value)) { 29 | | Some(gh) => gh 30 | | None => Opam( 31 | OpamConcrete.parseNpmRange(value) 32 | /* NpmVersion.parseRange(value) |> GenericVersion.map(Shared.Types.opamFromNpmConcrete) */ 33 | ) 34 | } 35 | ) 36 | | None => { 37 | (name, 38 | switch (Shared.GithubVersion.parseGithubVersion(value)) { 39 | | Some(gh) => gh 40 | | None => { 41 | if (startsWith(value, "git+")) { 42 | Git(value) 43 | } else { 44 | Npm(NpmVersion.parseRange(value)) 45 | } 46 | 47 | } 48 | } 49 | ) 50 | } 51 | } 52 | }; 53 | 54 | let toDep = ((name, value)) => { 55 | let value = switch value { 56 | | `String(value) => value 57 | | _ => failwith("Unexpected dep value: " ++ Yojson.Basic.to_string(value)) 58 | }; 59 | 60 | parseNpmSource((name, value)) 61 | }; 62 | 63 | /** 64 | * Parse the deps 65 | * 66 | * - For each dep 67 | * - grab the manifest (todo cache) 68 | * - add all the matching packages to the universe 69 | * - if a given version hasn't been added, then recursively process it 70 | * - how do I do this incrementally? 71 | * - or, how do I do it serially, but in a way that I can easily move to lwt? 72 | * 73 | */ 74 | let process = (parsed) => { 75 | /* TODO detect npm dependencies */ 76 | switch parsed { 77 | | `Assoc(items) => { 78 | let dependencies = switch (List.assoc("dependencies", items)) { 79 | | exception Not_found => [] 80 | | `Assoc(items) => items 81 | | _ => failwith("Unexpected value for dependencies") 82 | }; 83 | let buildDependencies = switch (List.assoc("buildDependencies", items)) { 84 | | exception Not_found => [] 85 | | `Assoc(items) => items 86 | | _ => failwith("Unexpected value for build deps") 87 | }; 88 | let devDependencies = switch (List.assoc("devDependencies", items)) { 89 | | exception Not_found => [] 90 | | `Assoc(items) => items 91 | | _ => failwith("Unexpected value for dev deps") 92 | }; 93 | { 94 | Types.runtime: dependencies |> List.map(toDep), 95 | build: buildDependencies |> List.map(toDep), 96 | dev: devDependencies |> List.map(toDep), 97 | npm: [] 98 | } 99 | } 100 | | _ => failwith("Invalid package.json") 101 | }; 102 | }; 103 | 104 | let getSource = (json) => { 105 | switch json { 106 | | `Assoc(items) => { 107 | switch (List.assoc("dist", items)) { 108 | | exception Not_found => { 109 | print_endline(Yojson.Basic.pretty_to_string(json)); 110 | failwith("No dist") 111 | } 112 | 113 | | `Assoc(items) => { 114 | let archive = switch(List.assoc("tarball", items)) { 115 | | `String(archive) => archive 116 | | _ => failwith("Bad tarball") 117 | }; 118 | let checksum = switch(List.assoc("shasum", items)) { 119 | | `String(checksum) => checksum 120 | | _ => failwith("Bad checksum") 121 | }; 122 | Types.PendingSource.Archive(archive, Some(checksum)) 123 | } 124 | | _ => failwith("bad dist") 125 | } 126 | } 127 | | _ => failwith("bad json manifest") 128 | } 129 | }; 130 | -------------------------------------------------------------------------------- /src/npm/ParseNpm.re: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Do the nitty gritty parsing of npm semver. 4 | * Follows this spec: https://docs.npmjs.com/misc/semver 5 | */ 6 | 7 | type partial = [ 8 | | `Major(int) 9 | | `Minor(int, int) 10 | | `Patch(int, int, int) 11 | | `Qualified(int, int, int, string) 12 | ]; 13 | 14 | let viewRange = Shared.GenericVersion.view(Shared.Types.viewNpmConcrete); 15 | 16 | let sliceToEnd = (text, num) => String.sub(text, num, String.length(text) - num); 17 | 18 | let isint = v => try ({ignore(int_of_string(v)); true}) { | _ => false }; 19 | 20 | let getRest = parts => parts == [] ? None : Some(String.concat(".", parts)); 21 | 22 | let splitRest = value => { 23 | try(switch (String.split_on_char('-', value)) { 24 | | [single] => switch (String.split_on_char('+', value)) { 25 | | [single] => switch (String.split_on_char('~', value)) { 26 | | [single] => (int_of_string(single), None) 27 | | [single, ...rest] => (int_of_string(single), Some("~" ++ String.concat("~", rest))) 28 | | _ => (0, Some(value)) 29 | } 30 | | [single, ...rest] => (int_of_string(single), Some("+" ++ String.concat("+", rest))) 31 | | _ => (0, Some(value)) 32 | } 33 | | [single, ...rest] => (int_of_string(single), Some("-" ++ String.concat("-", rest))) 34 | | _ => (0, Some(value)) 35 | }) { 36 | | _ => (0, Some(value)) 37 | } 38 | }; 39 | 40 | let showOpt = (n) => switch n { | None => "None" | Some(x) => Printf.sprintf("Some(%s)", x)}; 41 | 42 | let showPartial = x => switch x { 43 | | `AllStar => "AllStar" 44 | | `MajorStar(num) => Printf.sprintf("MajorStar %d" , num) 45 | | `MinorStar(m, i) => Printf.sprintf("MinorStar %d %d" , m, i) 46 | | `Major(m, q) => Printf.sprintf("Major %d %s" , m, showOpt(q)) 47 | | `Minor(m, i, q) => Printf.sprintf("Minor %d %d %s" , m, i, showOpt(q)) 48 | | `Patch(m, i, p, q) => Printf.sprintf("Minor %d %d %d %s" , m, i, p, showOpt(q)) 49 | | `Raw(s) => "Raw " ++ s 50 | }; 51 | 52 | let exactPartial = partial => switch partial { 53 | | `AllStar => failwith("* cannot be compared") 54 | | `MajorStar(num) => (num, 0, 0, None) 55 | | `MinorStar(m, i) => (m, i, 0, None) 56 | | `Major(m, q) => (m, 0, 0, q) 57 | | `Minor(m, i, q) => (m, i, 0, q) 58 | | `Patch(m, i, p, q) => (m, i, p, q) 59 | | `Raw(text) => (0, 0, 0, Some(text)) 60 | }; 61 | 62 | [@test [ 63 | ("*", `AllStar), 64 | ("2.x", `MajorStar(2)), 65 | ("1.3.X", `MinorStar(1,3)), 66 | ("v1.3.*", `MinorStar(1,3)), 67 | ("1", `Major(1, None)), 68 | ("1-beta.2", `Major(1, Some("-beta.2"))), 69 | ("1.2-beta.2", `Minor(1, 2, Some("-beta.2"))), 70 | ("1.4.23-alpha1", `Patch(1, 4, 23, Some("-alpha1"))), 71 | ("1.2.3alpha2", `Patch(1,2,3, Some("alpha2"))), 72 | ("what", `Raw("what")), 73 | ]] 74 | [@test.print (fmt, x) => Format.fprintf(fmt, "%s", showPartial(x))] 75 | let parsePartial = version => { 76 | let version = version.[0] == 'v' ? sliceToEnd(version, 1) : version; 77 | let parts = String.split_on_char('.', version); 78 | switch parts { 79 | | ["*" | "x" | "X", ...rest] => `AllStar 80 | | [major, "*" | "x" | "X", ...rest] when isint(major) => `MajorStar(int_of_string(major)) 81 | | [major, minor, "*" | "x" | "X", ...rest] when isint(major) && isint(minor) => `MinorStar(int_of_string(major), int_of_string(minor)) 82 | | _ => { 83 | let rx = Str.regexp({|^\([0-9]+\)\(\.\([0-9]+\)\(\.\([0-9]+\)\)?\)?\(\([-+~][a-z0-9\.]+\)\)?|}); 84 | switch (Str.search_forward(rx, version, 0)) { 85 | | exception Not_found => `Raw(version) 86 | | _ => { 87 | let major = int_of_string(Str.matched_group(1, version)); 88 | let qual = switch (Str.matched_group(7, version)) { 89 | | exception Not_found => { 90 | let last = Str.match_end(); 91 | if (last < String.length(version)) { 92 | Some(sliceToEnd(version, last)) 93 | } else { 94 | None 95 | } 96 | } 97 | | text => Some(text) 98 | }; 99 | switch (Str.matched_group(3, version)) { 100 | | exception Not_found => `Major(major, qual) 101 | | minor => { 102 | let minor = int_of_string(minor); 103 | switch (Str.matched_group(5, version)) { 104 | | exception Not_found => `Minor(major, minor, qual) 105 | | patch => `Patch(major, minor, int_of_string(patch), qual) 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | }; 114 | open Shared.GenericVersion; 115 | 116 | [@test [ 117 | (">=2.3.1", AtLeast((2,3,1,None))), 118 | ("<2.4", LessThan((2,4,0,None))), 119 | ]] 120 | let parsePrimitive = item => switch (item.[0]) { 121 | | '=' => Exactly(parsePartial(sliceToEnd(item, 1)) |> exactPartial) 122 | | '>' => switch (item.[1]) { 123 | | '=' => AtLeast(parsePartial(sliceToEnd(item, 2)) |> exactPartial) 124 | | _ => GreaterThan(parsePartial(sliceToEnd(item, 1)) |> exactPartial) 125 | } 126 | | '<' => switch (item.[1]) { 127 | | '=' => AtMost(parsePartial(sliceToEnd(item, 2)) |> exactPartial) 128 | | _ => LessThan(parsePartial(sliceToEnd(item, 1)) |> exactPartial) 129 | } 130 | | _ => failwith("Bad primitive") 131 | }; 132 | 133 | let parseSimple = item => { 134 | switch (item.[0]) { 135 | | '~' => switch (parsePartial(sliceToEnd(item, 1))) { 136 | | `Major(num, q) => And(AtLeast((num, 0, 0, q)), LessThan((num + 1, 0, 0, None))) 137 | | `Minor(m, i, q) => And(AtLeast((m, i, 0, q)), LessThan((m, i + 1, 0, None))) 138 | | `Patch(m, i, p, q) => And(AtLeast((m, i, p, q)), LessThan((m, i + 1, 0, None))) 139 | | `AllStar => failwith("* cannot be tilded") 140 | | `MajorStar(num) => And(AtLeast((num, 0, 0, None)), LessThan((num + 1, 0, 0, None))) 141 | | `MinorStar(m, i) => And(AtLeast((m, i, 0, None)), LessThan((m, i + 1, 0, None))) 142 | | `Raw(_) => failwith("Bad tilde") 143 | } 144 | | '^' => switch (parsePartial(sliceToEnd(item, 1))) { 145 | | `Major(num, q) => And(AtLeast((num, 0, 0, q)), LessThan((num + 1, 0, 0, None))) 146 | | `Minor(0, i, q) => And(AtLeast((0, i, 0, q)), LessThan((0, i + 1, 0, None))) 147 | | `Minor(m, i, q) => And(AtLeast((m, i, 0, q)), LessThan((m + 1, 0, 0, None))) 148 | | `Patch(0, 0, p, q) => And(AtLeast((0, 0, p, q)), LessThan((0, 0, p + 1, None))) 149 | | `Patch(0, i, p, q) => And(AtLeast((0, i, p, q)), LessThan((0, i + 1, 0, None))) 150 | | `Patch(m, i, p, q) => And(AtLeast((m, i, p, q)), LessThan((m + 1, 0, 0, None))) 151 | | `AllStar => failwith("* cannot be careted") 152 | | `MajorStar(num) => And(AtLeast((num, 0, 0, None)), LessThan((num + 1, 0, 0, None))) 153 | | `MinorStar(m, i) => And(AtLeast((m, i, 0, None)), LessThan((m + 1, i, 0, None))) 154 | | `Raw(_) => failwith("Bad tilde") 155 | } 156 | | '>' | '<' | '=' => parsePrimitive(item) 157 | | _ => switch(parsePartial(item)) { 158 | | `AllStar => Any 159 | /* TODO maybe handle the qualifier */ 160 | | `Major(m, Some(x)) => Exactly((m, 0, 0, Some(x))) 161 | | `Major(m, None) 162 | | `MajorStar(m) => And(AtLeast((m, 0, 0, None)), LessThan((m + 1, 0, 0, None))) 163 | | `Minor(m, i, Some(x)) => Exactly((m, i, 0, Some(x))) 164 | | `Minor(m, i, None) 165 | | `MinorStar(m, i) => And(AtLeast((m, i, 0, None)), LessThan((m, i + 1, 0, None))) 166 | | `Patch(m, i, p, q) => Exactly((m, i, p, q)) 167 | | `Raw(text) => Exactly((0, 0, 0, Some(text))) 168 | } 169 | } 170 | }; 171 | 172 | let parseSimples = (item, parseSimple) => { 173 | let item = item 174 | |> Str.global_replace(Str.regexp(">= +"), ">=") 175 | |> Str.global_replace(Str.regexp("<= +"), "<=") 176 | |> Str.global_replace(Str.regexp("> +"), ">") 177 | |> Str.global_replace(Str.regexp("< +"), "<") 178 | ; 179 | let items = String.split_on_char(' ', item); 180 | let rec loop = items => switch items { 181 | | [item] => parseSimple(item) 182 | | [item, ...items] => And(parseSimple(item), loop(items)) 183 | | [] => assert(false) 184 | }; 185 | loop(items) 186 | }; 187 | 188 | [@test Shared.GenericVersion.([ 189 | ("1.2.3", Exactly((1,2,3,None))), 190 | ("1.2.3-alpha2", Exactly((1,2,3,Some("-alpha2")))), 191 | ("1.2.3 - 2.3.4", And(AtLeast((1,2,3,None)), AtMost((2,3,4,None)))), 192 | ("1.2.3 - 2.3", And(AtLeast((1,2,3,None)), LessThan((2,4,0,None)))), 193 | ])] 194 | [@test.print (fmt, v) => Format.fprintf(fmt, "%s", viewRange(v))] 195 | let parseNpmRange = (simple) => { 196 | let items = Str.split(Str.regexp(" +- +"), simple); 197 | switch items { 198 | | [item] => parseSimples(item, parseSimple) 199 | | [left, right] => { 200 | let left = AtLeast(parsePartial(left) |> exactPartial); 201 | let right = switch (parsePartial(right)) { 202 | | `AllStar => Any 203 | /* TODO maybe handle the qualifier */ 204 | | `Major(m, _) 205 | | `MajorStar(m) => LessThan((m + 1, 0, 0, None)) 206 | | `Minor(m, i, _) 207 | | `MinorStar(m, i) => LessThan((m, i + 1, 0, None)) 208 | | `Patch(m, i, p, q) => AtMost((m, i, p, q)) 209 | | `Raw(text) => LessThan((0, 0, 0, Some(text))) 210 | }; 211 | And(left, right) 212 | } 213 | | _ => failwith("Invalid range") 214 | } 215 | }; 216 | 217 | [@test Shared.GenericVersion.([ 218 | ("1.2.3", Exactly((1,2,3,None))), 219 | ("1.2.3-alpha2", Exactly((1,2,3,Some("-alpha2")))), 220 | ("1.2.3 - 2.3.4", And(AtLeast((1,2,3,None)), AtMost((2,3,4,None)))), 221 | ("1.2.3 - 2.3 || 5.x", Or(And(AtLeast((1,2,3,None)), LessThan((2,4,0,None))), And(AtLeast((5, 0, 0, None)), LessThan((6, 0, 0, None))))), 222 | ])] 223 | [@test.call parseOrs(parseNpmRange)] 224 | [@test.print (fmt, v) => Format.fprintf(fmt, "%s", viewRange(v))] 225 | let parseOrs = (parseRange, version) => { 226 | if (version == "") { 227 | Shared.GenericVersion.Any 228 | } else { 229 | let items = Str.split(Str.regexp(" +|| +"), version); 230 | let rec loop = items => switch items { 231 | | [] => failwith("WAAAT " ++ version) 232 | | [item] => parseRange(item) 233 | | [item, ...items] => Or(parseRange(item), loop(items)) 234 | }; 235 | loop(items) 236 | } 237 | }; 238 | 239 | let parse = parseOrs(parseNpmRange); 240 | -------------------------------------------------------------------------------- /src/npm/Registry.re: -------------------------------------------------------------------------------- 1 | 2 | open Shared.Infix; 3 | 4 | /* TODO use lwt, maybe cache things */ 5 | 6 | let getFromNpmRegistry = name => { 7 | let name = Str.global_replace(Str.regexp("/"), "%2f", name); 8 | let json = Shared.Wget.get("http://registry.npmjs.org/" ++ name) |! "Unable to query registry for " ++ name |> Yojson.Basic.from_string; 9 | switch json { 10 | | `Assoc(items) => { 11 | switch (List.assoc("versions", items)) { 12 | | exception Not_found => { 13 | print_endline(Yojson.Basic.to_string(json)); 14 | failwith("No versions field in the registry result for " ++ name) 15 | } 16 | | `Assoc(items) => { 17 | List.map( 18 | ((name, json)) => (NpmVersion.parseConcrete(name), json), 19 | items 20 | ) 21 | } 22 | | _ => failwith("Invalid versions field for registry response to " ++ name) 23 | } 24 | } 25 | | _ => failwith("Invalid registry response for " ++ name) 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/npm/jbuild: -------------------------------------------------------------------------------- 1 | (jbuild_version 1) 2 | 3 | (library 4 | ((name npm) 5 | (preprocess (pps (ppx_deriving_yojson testre-ppx ppx_driver))) 6 | (libraries (shared str curl cudf yaml ppx_deriving_yojson.runtime mccs yojson opam-file-format)))) 7 | 8 | -------------------------------------------------------------------------------- /src/opam/OpamAvailable.re: -------------------------------------------------------------------------------- 1 | 2 | 3 | let parseConcrete = Npm.OpamConcrete.parseConcrete; 4 | let triple = Npm.OpamConcrete.triple; 5 | 6 | let fromPrefix = (op, version) => { 7 | open Shared.GenericVersion; 8 | let v = Npm.NpmVersion.parseConcrete(version); 9 | switch op { 10 | | `Eq => Exactly(v) 11 | | `Geq => AtLeast(v) 12 | | `Leq => AtMost(v) 13 | | `Lt => LessThan(v) 14 | | `Gt => GreaterThan(v) 15 | | `Neq => Or(GreaterThan(v), LessThan(v)) 16 | } 17 | }; 18 | 19 | let rec getOCamlVersion = opamvalue => { 20 | /* Shared.GenericVersion.Any */ 21 | open OpamParserTypes; 22 | open Shared.GenericVersion; 23 | switch opamvalue { 24 | | Logop(_, `And, left, right) => { 25 | And(getOCamlVersion(left), getOCamlVersion(right)) 26 | } 27 | | Logop(_, `Or, left, right) => Or(getOCamlVersion(left), getOCamlVersion(right)) 28 | | Relop(_, rel, Ident(_, "ocaml-version"), String(_, version)) => fromPrefix(rel, version) 29 | /* We don't support pre-4.02.3 anyway */ 30 | | Relop(_, `Neq, Ident(_, "compiler"), String(_, "4.02.1+BER")) => Any 31 | | Relop(_, `Eq, Ident(_, "compiler"), String(_, _)) => Any 32 | | Relop(_, _rel, Ident(_, "opam-version"), _) => Any /* TODO should I care about this? */ 33 | | Relop(_, rel, Ident(_, "os"), String(_, version)) => Any 34 | | Pfxop(_, `Not, Ident(_, "preinstalled")) => Any 35 | | Ident(_, "preinstalled" | "false") => Any 36 | | Bool(_, true) => Any 37 | | Bool(_, false) => Nothing 38 | | Option(_, contents, options) => { 39 | print_endline("Ignoring option: " ++ (options |> List.map(OpamPrinter.value) |> String.concat(" .. "))); 40 | getOCamlVersion(contents) 41 | } 42 | | List(_, items) => { 43 | let rec loop = items => switch items { 44 | | [] => Any 45 | | [item] => getOCamlVersion(item) 46 | | [item, ...rest] => And(getOCamlVersion(item), loop(rest)) 47 | }; 48 | loop(items) 49 | } 50 | | Group(_, items) => { 51 | let rec loop = items => switch items { 52 | | [] => Any 53 | | [item] => getOCamlVersion(item) 54 | | [item, ...rest] => And(getOCamlVersion(item), loop(rest)) 55 | }; 56 | loop(items) 57 | } 58 | | y => { 59 | print_endline("Unexpected option -- pretending its any " ++ OpamPrinter.value(opamvalue)); 60 | Any 61 | } 62 | } 63 | }; 64 | 65 | 66 | let rec getAvailability = opamvalue => { 67 | /* Shared.GenericVersion.Any */ 68 | open OpamParserTypes; 69 | switch opamvalue { 70 | | Logop(_, `And, left, right) => { 71 | getAvailability(left) && getAvailability(right) 72 | } 73 | | Logop(_, `Or, left, right) => getAvailability(left) || getAvailability(right) 74 | | Relop(_, rel, Ident(_, "ocaml-version"), String(_, version)) => true 75 | /* We don't support pre-4.02.3 anyway */ 76 | | Relop(_, `Neq, Ident(_, "compiler"), String(_, "4.02.1+BER")) => true 77 | | Relop(_, `Eq, Ident(_, "compiler"), String(_, compiler)) => { 78 | /* print_endline("Wants a compiler " ++ compiler ++ "... assuming we don't have it"); */ 79 | false 80 | } 81 | | Relop(_, _rel, Ident(_, "opam-version"), _) => true 82 | | Relop(_, `Eq, Ident(_, "os"), String(_, "darwin")) => true 83 | | Relop(_, `Neq, Ident(_, "os"), String(_, "darwin")) => false 84 | | Relop(_, rel, Ident(_, "os"), String(_, os)) => false 85 | | Pfxop(_, `Not, Ident(_, "preinstalled")) => true 86 | | Ident(_, "preinstalled") => false 87 | | Bool(_, false) => false 88 | | Bool(_, true) => true 89 | | Option(_, contents, options) => { 90 | print_endline("[[ AVAILABILITY ]] Ignoring option: " ++ (options |> List.map(OpamPrinter.value) |> String.concat(" .. "))); 91 | getAvailability(contents) 92 | } 93 | | List(_, items) => { 94 | let rec loop = items => switch items { 95 | | [] => true 96 | | [item] => getAvailability(item) 97 | | [item, ...rest] => getAvailability(item) && loop(rest) 98 | }; 99 | loop(items) 100 | } 101 | | Group(_, items) => { 102 | let rec loop = items => switch items { 103 | | [] => true 104 | | [item] => getAvailability(item) 105 | | [item, ...rest] => getAvailability(item) && loop(rest) 106 | }; 107 | loop(items) 108 | } 109 | | y => { 110 | print_endline("Unexpected availability option -- pretending its fine " ++ OpamPrinter.value(opamvalue)); 111 | true 112 | } 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /src/opam/OpamFile.re: -------------------------------------------------------------------------------- 1 | 2 | open Shared; 3 | open OpamParserTypes; 4 | open OpamOverrides.Infix; 5 | 6 | let expectSuccess = (msg, v) => if (v) { () } else { failwith(msg) }; 7 | 8 | type manifest = { 9 | fileName: string, 10 | build: list(list(string)), 11 | install: list(list(string)), 12 | patches: list(string), /* these should be absolute */ 13 | files: list((string, string)), /* relname, sourcetext */ 14 | deps: list(Types.dep), 15 | buildDeps: list(Types.dep), 16 | devDeps: list(Types.dep), 17 | peerDeps: list(Types.dep), 18 | optDependencies: list(Types.dep), 19 | available: bool, 20 | /* TODO optDependencies (depopts) */ 21 | source: Types.PendingSource.t, 22 | exportedEnv: list((string, (string, string))), 23 | }; 24 | 25 | type thinManifest = (string, string, string, Shared.Types.opamConcrete); 26 | 27 | let rec findVariable = (name, items) => switch items { 28 | | [] => None 29 | | [Variable(_, n, v), ..._] when n == name => Some(v) 30 | | [_, ...rest] => findVariable(name, rest) 31 | }; 32 | 33 | let opName = op => switch op { 34 | | `Leq => "<=" 35 | | `Lt => "<" 36 | | `Neq => "!=" 37 | | `Eq => "=" 38 | | `Geq => ">=" 39 | | `Gt => ">" 40 | }; 41 | 42 | let withScope = name => "@opam/" ++ name; 43 | 44 | let withoutScope = fullName => { 45 | let ln = 6; 46 | if (String.sub(fullName, 0, ln) != "@opam/") { 47 | failwith("Opam name not prefixed: " ++ fullName) 48 | }; 49 | String.sub(fullName, ln, String.length(fullName) - ln); 50 | }; 51 | 52 | let toDep = opamvalue => { 53 | let (name, s, typ) = OpamVersion.toDep(opamvalue); 54 | (withScope(name), s, typ) 55 | }; 56 | 57 | let processDeps = (fileName, deps) => { 58 | let deps = switch (deps) { 59 | | None => [] 60 | | Some(List(_, items)) => items 61 | | Some(Group(_, items)) => items 62 | | Some(String(pos, value)) => [String(pos, value)] 63 | | Some(contents) => failwith("Can't handle the dependencies " ++ fileName ++ " " ++ OpamPrinter.value(contents)) 64 | }; 65 | 66 | List.fold_left( 67 | ((deps, buildDeps, devDeps), dep) => { 68 | let (name, dep, typ) = try(toDep(dep)) { 69 | | Failure(f) => { 70 | print_endline("Failed to process dep: " ++ f); 71 | print_endline(fileName); 72 | failwith("bad") 73 | } 74 | }; 75 | switch typ { 76 | | `Link => ([(name, dep), ...deps], buildDeps, devDeps) 77 | | `Build => (deps, [(name, dep), ...buildDeps], devDeps) 78 | | `Test => (deps, buildDeps, [(name, dep), ...devDeps]) 79 | } 80 | }, 81 | ([], [], []), 82 | deps 83 | ); 84 | }; 85 | 86 | let filterMap = (fn, items) => { 87 | List.map(fn, items) |> List.filter(x => x != None) |> List.map(x => switch (x) { | Some(x) => x | None => assert(false)}) 88 | }; 89 | 90 | /** TODO handle more variables */ 91 | let variables = ((name, version)) => [ 92 | ("jobs", "4"), 93 | ("make", "make"), 94 | ("ocaml-native", "true"), 95 | ("ocaml-native-dynlink", "true"), 96 | ("bin", "$cur__install/bin"), 97 | ("lib", "$cur__install/lib"), 98 | ("man", "$cur__install/man"), 99 | ("share", "$cur__install/share"), 100 | ("pinned", "false"), 101 | ("name", name), 102 | ("version", OpamVersion.viewAlpha(version)), 103 | ("prefix", "$cur__install"), 104 | ]; 105 | 106 | let cleanEnvName = Str.global_replace(Str.regexp("-"), "_"); 107 | 108 | [@test [ 109 | ((Str.regexp("a\\(.\\)"), String.uppercase_ascii, "applae"), "PplE"), 110 | ((Str.regexp("A\\(.\\)"), String.lowercase_ascii, "HANDS"), "HnDS"), 111 | ]] 112 | let replaceGroupWithTransform = (rx, transform, string) => { 113 | Str.global_substitute(rx, s => transform(Str.matched_group(1, s)), string) 114 | }; 115 | 116 | [@test [ 117 | ((("awesome", Shared.Types.Alpha("", None)), "--%{fmt:enable}%-fmt"), "--${fmt_enable:-disable}-fmt") 118 | ]] 119 | let replaceVariables = (info, string) => { 120 | let string = string 121 | |> replaceGroupWithTransform(Str.regexp("%{\\([^}]+\\):installed}%"), name => "${" ++ cleanEnvName(name) ++ "_installed:-false}") 122 | |> replaceGroupWithTransform(Str.regexp("%{\\([^}]+\\):enable}%"), name => "${" ++ cleanEnvName(name) ++ "_enable:-disable}") 123 | |> replaceGroupWithTransform(Str.regexp("%{\\([^}]+\\):version}%"), name => "${" ++ cleanEnvName(name) ++ "_version}") 124 | |> replaceGroupWithTransform(Str.regexp("%{\\([^}]+\\):bin}%"), name => "${" ++ cleanEnvName(name) ++ ".bin}") 125 | |> replaceGroupWithTransform(Str.regexp("%{\\([^}]+\\):share}%"), name => "${" ++ cleanEnvName(name) ++ ".share}") 126 | |> replaceGroupWithTransform(Str.regexp("%{\\([^}]+\\):lib}%"), name => "${" ++ cleanEnvName(name) ++ ".lib}") 127 | ; 128 | List.fold_left( 129 | (string, (name, res)) => { 130 | Str.global_replace( 131 | Str.regexp_string("%{" ++ name ++ "}%"), 132 | res, 133 | string 134 | ) 135 | }, 136 | string, 137 | variables(info) 138 | ) 139 | }; 140 | 141 | [@test [ 142 | ({|"install" {!preinstalled}|}, Some("install")), 143 | ]] 144 | [@test.call (string) => processCommandItem(("something", Alpha("a", None)), OpamParser.value_from_string(string, "wat"))] 145 | let processCommandItem = (info, item) => { 146 | switch item { 147 | | String(_, name) => Some(replaceVariables(info, name)) 148 | | Ident(_, ident) => { 149 | switch (List.assoc_opt(ident, variables(info))) { 150 | | Some(string) => Some(string) 151 | | None => { 152 | print_endline("⚠️ Missing vbl " ++ ident); 153 | None 154 | } 155 | } 156 | }; 157 | | Option(_, _, [Ident(_, "preinstalled")]) => { 158 | /** Skipping preinstalled */ 159 | None 160 | } 161 | | Option(_, _, [String(_, something)]) => { 162 | /* String options like "%{react:installed}%" are not currently supported */ 163 | None 164 | } 165 | | Option(_, String(_, name), [Pfxop(_, `Not, Ident(_, "preinstalled"))]) => { 166 | /** Not skipping not preinstalled */ 167 | Some(replaceVariables(info, name)) 168 | } 169 | | _ => { 170 | /** TODO handle "--%{text:enable}%-text" {"%{react:installed}%"} correctly */ 171 | print_endline("Bad build arg " ++ OpamPrinter.value(item)); 172 | None 173 | } 174 | }; 175 | }; 176 | 177 | let processCommand = (info, items) => { 178 | items |> filterMap(processCommandItem(info)) 179 | }; 180 | 181 | /** TODO handle optional build things */ 182 | let processCommandList = (info, item) => { 183 | switch(item) { 184 | | None => [] 185 | | Some(List(_, items)) 186 | | Some(Group(_, items)) => { 187 | switch items { 188 | | [String(_) | Ident(_), ...rest] => { 189 | [items |> processCommand(info)] 190 | } 191 | 192 | | _ => 193 | items |> filterMap(item => { 194 | switch item { 195 | | List(_, items) => { 196 | Some(processCommand(info, items)) 197 | } 198 | | Option(_, List(_, items), _) => { 199 | Some(processCommand(info, items)) 200 | } 201 | | _ => { 202 | print_endline("Skipping a non-list build thing " ++ OpamPrinter.value(item)); 203 | None 204 | } 205 | } 206 | }); 207 | } 208 | 209 | } 210 | 211 | | Some(Ident(_, ident)) => { 212 | switch (List.assoc_opt(ident, variables(info))) { 213 | | Some(string) => [[string]] 214 | | None => { 215 | print_endline("⚠️ Missing vbl " ++ ident); 216 | [] 217 | } 218 | } 219 | 220 | } 221 | | Some(item) => failwith("Unexpected type for a command list: " ++ OpamPrinter.value(item)) 222 | }; 223 | }; 224 | 225 | /** TODO handle "patch-ocsigen-lwt-101.diff" {os = "darwin"} correctly */ 226 | [@test [ 227 | ({|["patch-ocsigen-lwt-101.diff" {os = "darwin"}]|}, ["patch-ocsigen-lwt-101.diff"]), 228 | ({|["openbsd.diff" {os = "openbsd"}]|}, []), 229 | ]] 230 | [@test.call (string) => processStringList(Some(OpamParser.value_from_string(string, "wat")))] 231 | [@test.print (fmt, x) => Format.fprintf(fmt, "%s", String.concat(", ", x))] 232 | let processStringList = item => { 233 | let items = switch(item) { 234 | | None => [] 235 | | Some(List(_, items)) 236 | | Some(Group(_, items)) => items 237 | | Some(item) => failwith("Unexpected type for a string list: " ++ OpamPrinter.value(item)) 238 | }; 239 | items |> filterMap(item => { 240 | switch item { 241 | | String(_, name) => Some(name) 242 | | Option(_, String(_, name), [Relop(_, `Eq, Ident(_, "os"), String(_, "darwin"))]) => Some(name) 243 | | Option(_, String(_, name), [Relop(_, `Eq, Ident(_, "os"), String(_, _))]) => None 244 | | Option(_, String(_, name), [Ident(_, "preinstalled")]) => None 245 | | Option(_, String(_, name), [Pfxop(_, `Not, Ident(_, "preinstalled"))]) => Some(name) 246 | | _ => { 247 | print_endline("Bad string list item arg " ++ OpamPrinter.value(item)); 248 | None 249 | } 250 | } 251 | }); 252 | }; 253 | 254 | let findArchive = (contents, file_name) => { 255 | switch (findVariable("archive", contents)) { 256 | | Some(String(_, archive)) => Some(archive) 257 | | _ => { 258 | switch (findVariable("http", contents)) { 259 | | Some(String(_, archive)) => Some(archive) 260 | | _ => 261 | switch (findVariable("src", contents)) { 262 | | Some(String(_, archive)) => Some(archive) 263 | | _ => None 264 | } 265 | } 266 | } 267 | } 268 | }; 269 | 270 | let parseUrlFile = ({file_contents, file_name}) => { 271 | switch (findArchive(file_contents, file_name)) { 272 | | None => { 273 | switch (findVariable("git", file_contents)) { 274 | | Some(String(_, git)) => Types.PendingSource.GitSource(git, None /* TODO parse out commit info */) 275 | | _ => failwith("Invalid url file - no archive: " ++ file_name) 276 | } 277 | } 278 | | Some(archive) => { 279 | let checksum = switch (findVariable("checksum", file_contents)) { 280 | | Some(String(_, checksum)) => Some(checksum) 281 | | _ => None 282 | }; 283 | Types.PendingSource.Archive(archive, checksum) 284 | } 285 | } 286 | }; 287 | 288 | let toDepSource = ((name, semver)) => (name, Types.Opam(semver)); 289 | 290 | let getOpamFiles = (opam_name) => { 291 | let dir = Filename.concat(Filename.dirname(opam_name), "files"); 292 | if (Files.isDirectory(dir)) { 293 | let collected = ref([]); 294 | Files.crawl(dir, (rel, full) => { 295 | collected := [(rel, Files.readFile(full) |! "opam file unreadable"), ...collected^] 296 | }); 297 | collected^; 298 | } else { 299 | [] 300 | } 301 | }; 302 | 303 | let getSubsts = opamvalue => (switch opamvalue { 304 | | None => [] 305 | | Some(List(_, items)) => items |> List.map(item => switch item { | String(_, text) => text | _ => failwith("Bad substs item")}) 306 | | Some(String(_, text)) => [text] 307 | | Some(other) => failwith("Bad substs value " ++ OpamPrinter.value(other)) 308 | }) |> List.map(filename => ["substs", filename ++ ".in"]); 309 | 310 | let parseManifest = (info, {file_contents, file_name}) => { 311 | /* let baseDir = Filename.dirname(file_name); */ 312 | /* NOTE: buildDeps are not actually buildDeps as we think of them, because they can also have runtime components. */ 313 | let (deps, buildDeps, devDeps) = processDeps(file_name, findVariable("depends", file_contents)); 314 | let (depopts, _, _) = processDeps(file_name, findVariable("depopts", file_contents)); 315 | let files = getOpamFiles(file_name); 316 | let patches = processStringList(findVariable("patches", file_contents)); 317 | /** OPTIMIZE: only read the files when generating the lockfile */ 318 | /* print_endline("Patches for " ++ file_name ++ " " ++ string_of_int(List.length(patches))); */ 319 | let ocamlRequirement = findVariable("available", file_contents) |?>> OpamAvailable.getOCamlVersion |? GenericVersion.Any; 320 | /* We just don't support anything before 4.2.3 */ 321 | let ourMinimumOcamlVersion = Npm.NpmVersion.parseConcrete("4.02.3"); 322 | let isAVersionWeSupport = !Shared.GenericVersion.isTooLarge(Npm.NpmVersion.compare, ocamlRequirement, ourMinimumOcamlVersion); 323 | let isAvailable = isAVersionWeSupport && findVariable("available", file_contents) |?>> OpamAvailable.getAvailability |? true; 324 | /* Npm.NpmVersion.matches(ocamlRequirement, ourMinimumOcamlVersion); */ 325 | { 326 | fileName: file_name, 327 | build: 328 | getSubsts(findVariable("substs", file_contents)) @ 329 | processCommandList(info, findVariable("build", file_contents)) @ [ 330 | ["sh", "-c", "(esy-installer || true)"] 331 | ], 332 | install: processCommandList(info, findVariable("install", file_contents)), 333 | patches, 334 | files, 335 | deps: ((deps @ buildDeps) |> List.map(toDepSource)) @ [ 336 | /* HACK? Not sure where/when this should be specified */ 337 | ("@esy-ocaml/substs", Npm(GenericVersion.Any)), 338 | ("@esy-ocaml/esy-installer", Npm(GenericVersion.Any)), 339 | ("ocaml", Npm(And(GenericVersion.AtLeast(ourMinimumOcamlVersion), ocamlRequirement))), 340 | ], 341 | buildDeps: [], 342 | /* buildDeps |> List.map(toDepSource), */ 343 | devDeps: devDeps |> List.map(toDepSource), 344 | peerDeps: [], /* TODO peer deps */ 345 | optDependencies: depopts |> List.map(toDepSource), 346 | available: isAvailable, /* TODO */ 347 | source: Types.PendingSource.NoSource, 348 | exportedEnv: [], 349 | }; 350 | }; 351 | 352 | let parseDepVersion = ((name, version)) => { 353 | Npm.PackageJson.parseNpmSource((name, version)) 354 | }; 355 | 356 | module StrSet = Set.Make(String); 357 | let assignAssoc = (target, override) => { 358 | let replacing = List.fold_left( 359 | ((set, (name, _)) => StrSet.add(name, set)), 360 | StrSet.empty, 361 | override 362 | ); 363 | List.filter(((name, _)) => !StrSet.mem(name, replacing), target) @ override 364 | }; 365 | 366 | module O = OpamOverrides; 367 | let mergeOverride = (manifest, override) => { 368 | let source = override.O.opam |?> (opam => opam.O.source) |? manifest.source; 369 | { 370 | ...manifest, 371 | build: override.O.build |? manifest.build, 372 | install: override.O.install |? manifest.install, 373 | deps: assignAssoc(manifest.deps, override.O.dependencies |> List.map(parseDepVersion)), 374 | peerDeps: assignAssoc(manifest.peerDeps, override.O.peerDependencies |> List.map(parseDepVersion)), 375 | files: manifest.files @ (override.O.opam |?>> (o => o.O.files) |? []), 376 | source: source, 377 | exportedEnv: override.O.exportedEnv 378 | } 379 | }; 380 | 381 | let getManifest = (opamOverrides, (opam, url, name, version)) => { 382 | let manifest = { 383 | ...parseManifest((name, version), OpamParser.file(opam)), 384 | source: Files.exists(url) ? parseUrlFile(OpamParser.file(url)) : Types.PendingSource.NoSource 385 | }; 386 | switch (OpamOverrides.findApplicableOverride(opamOverrides, name, version)) { 387 | | None => { 388 | /* print_endline("No override for " ++ name ++ " " ++ VersionNumber.viewVersionNumber(version)); */ 389 | manifest 390 | } 391 | | Some(override) => { 392 | /* print_endline("!! Found override for " ++ name); */ 393 | let m = mergeOverride(manifest, override); 394 | m 395 | } 396 | } 397 | }; 398 | 399 | let getSource = ({source}) => source; 400 | 401 | let process = ({deps, buildDeps, devDeps}) => { 402 | {Types.runtime: deps @ buildDeps, build: [], dev: devDeps, npm: []} 403 | /* (deps, buildDeps, devDeps) */ 404 | }; 405 | 406 | let commandListToJson = e => e |> List.map(items => `List(List.map(item => `String(item), items))); 407 | 408 | let toPackageJson = (manifest, name, version) => { 409 | /* let manifest = getManifest(opamOverrides, (filename, "", withoutScope(name), switch version { 410 | | `Opam(t) => t 411 | | _ => failwith("unexpected opam version") 412 | })); */ 413 | 414 | (`Assoc([ 415 | ("name", `String(name)), 416 | ("version", `String(Lockfile.plainVersionNumber(version))), 417 | ("esy", `Assoc([ 418 | ("build", `List(commandListToJson(manifest.build))), 419 | ("install", `List(commandListToJson(manifest.install))), 420 | ("buildsInSource", `Bool(true)), 421 | ("exportedEnv", `Assoc( 422 | ([ 423 | (cleanEnvName(withoutScope(name)) ++ "_version", (Lockfile.plainVersionNumber(version), "global")), 424 | (cleanEnvName(withoutScope(name)) ++ "_installed", ("true", "global")), 425 | (cleanEnvName(withoutScope(name)) ++ "_enable", ("enable", "global")), 426 | ] @ 427 | manifest.exportedEnv) 428 | |> List.map(((name, (val_, scope))) => ( 429 | name, 430 | `Assoc([ 431 | ("val", `String(val_)), 432 | ("scope", `String(scope)) 433 | ]) 434 | )) 435 | )) 436 | /* ("buildsInSource", "_build") */ 437 | ])), 438 | ("_resolved", `String(Types.resolvedPrefix ++ name ++ "--" ++ Lockfile.viewRealVersion(version))), 439 | ("peerDependencies", `Assoc([ 440 | ("ocaml", `String("*")) /* HACK probably get this somewhere */ 441 | ])), 442 | ("optDependencies", `Assoc( 443 | (manifest.optDependencies |> List.map(((name, _)) => (name, `String("*")))) 444 | )), 445 | ("dependencies", `Assoc( 446 | (manifest.deps |> List.map(((name, _)) => (name, `String("*")))) 447 | @ 448 | (manifest.buildDeps |> List.map(((name, _)) => (name, `String("*")))) 449 | )) 450 | ]), manifest.files, manifest.patches) 451 | }; 452 | -------------------------------------------------------------------------------- /src/opam/OpamOverrides.re: -------------------------------------------------------------------------------- 1 | open Shared; 2 | 3 | module Infix = { 4 | let (|?>) = (a, b) => switch a { | None => None | Some(x) => b(x) }; 5 | let (|?>>) = (a, b) => switch a { | None => None | Some(x) => Some(b(x)) }; 6 | let (|?) = (a, b) => switch a { | None => b | Some(a) => a }; 7 | let (|??) = (a, b) => switch a { | None => b | Some(a) => Some(a) }; 8 | let (|!) = (a, b) => switch a { | None => failwith(b) | Some(a) => a }; 9 | }; 10 | open Infix; 11 | 12 | type opamSection = { 13 | source: option(Types.PendingSource.t), 14 | files: list((string, string)), /* relpath, contents */ 15 | /* patches: list((string, string)) relpath, abspath */ 16 | }; 17 | 18 | type opamPackageOverride = { 19 | build: option(list(list(string))), 20 | install: option(list(list(string))), 21 | dependencies: list((string, string)), 22 | peerDependencies: list((string, string)), 23 | exportedEnv: list((string, (string, string))), 24 | opam: option(opamSection) 25 | }; 26 | 27 | let expectResult = (message, res) => switch res { 28 | | Rresult.Ok(x) => x 29 | | _ => failwith(message) 30 | }; 31 | 32 | let rec yamlToJson = value => switch value { 33 | | `A(items) => `List(List.map(yamlToJson, items)) 34 | | `O(items) => `Assoc(List.map(((name, value)) => (name, yamlToJson(value)), items)) 35 | | `String(s) => `String(s) 36 | | `Float(s) => `Float(s) 37 | | `Bool(b) => `Bool(b) 38 | | `Null => `Null 39 | }; 40 | 41 | let module ProcessJson = { 42 | 43 | let arr = json => switch json { 44 | | `List(items) => Some(items) 45 | | _ => None 46 | }; 47 | let obj = json => switch json { 48 | | `Assoc(items) => Some(items) 49 | | _ => None 50 | }; 51 | let str = json => switch json { 52 | | `String(str) => Some(str) 53 | | _ => None 54 | }; 55 | let get = List.assoc_opt; 56 | let (|.!) = (fn, message) => opt => fn(opt) |! message; 57 | 58 | let parseExportedEnv = items => { 59 | items 60 | |> List.assoc_opt("exportedEnv") 61 | |?>> (obj |.! "exportedEnv should be an object") |?>> List.map(((name, value)) => { 62 | (name, switch value { 63 | | `String(s) => (s, "global") 64 | | `Assoc(items) => ( 65 | List.assoc_opt("val", items) |?> str |! "must have val", 66 | List.assoc_opt("scope", items) |?> str |? "global" 67 | ) 68 | | _ => failwith("env value should be a string or an object") 69 | }) 70 | }) 71 | }; 72 | 73 | let parseCommandList = json => json 74 | |> (arr |.! "should be a list") 75 | |> List.map(items => items |> (fun 76 | | `String(s) => [`String(s)] 77 | | `List(s) => s 78 | | _ => failwith("must be a string or list of strings") 79 | ) |> List.map(str |.! "command list item should be a string")); 80 | 81 | let parseDependencies = json => json 82 | |> (obj |.! "dependencies should be an object") 83 | |> List.map(((name, value)) => (name, value |> str |! "dep value must be a string")); 84 | 85 | let parseOpam = json => { 86 | json 87 | |> (obj |.! "opam should be an object") 88 | |> items => { 89 | let maybeArchiveSource = items |> get("url") |?>> (str |.! "url should be a string") 90 | |?>> (url => Types.PendingSource.Archive(url, items |> get("checksum") |?>> (str |.! "checksum should be a string"))); 91 | let maybeGitSource = (items |> get("git") |?>> (str |.! "git should be a string") |?>> (git => Types.PendingSource.GitSource(git, None /* TODO parse out commit if there */))); 92 | { 93 | source: maybeArchiveSource |?? maybeGitSource, 94 | files: items |> get("files") |?>> (arr |.! "files must be an array") |? [] 95 | |> List.map(obj |.! "files item must be an obj") 96 | |> List.map(items => ( 97 | items |> get("name") |?>> (str |.! "name must be a str") |! "name required for files", 98 | items |> get("content") |?>> (str |.! "content must be a str") |! "content required for files" 99 | )), 100 | }} 101 | }; 102 | 103 | let process = json => { 104 | let items = json |> obj |! "Json must be an object"; 105 | let attr = name => items |> List.assoc_opt(name); 106 | { 107 | build: attr("build") |?>> parseCommandList, 108 | install: attr("install") |?>> parseCommandList, 109 | dependencies: attr("dependencies") |?>> parseDependencies |? [], 110 | peerDependencies: attr("peerDependencies") |?>> parseDependencies |? [], 111 | exportedEnv: parseExportedEnv(items) |? [], 112 | opam: attr("opam") |?>> parseOpam 113 | } 114 | }; 115 | }; 116 | 117 | let module ParseName = { 118 | let stripDash = num => { 119 | if (num.[0] == '-') { 120 | String.sub(num, 1, String.length(num) - 1) 121 | } else { 122 | num 123 | } 124 | }; 125 | 126 | let stripPrefix = (text, prefix) => { 127 | let tl = String.length(text); 128 | let pl = String.length(prefix); 129 | if (tl > pl && String.sub(text, 0, pl) == prefix) { 130 | Some(String.sub(text, pl, tl - pl)) 131 | } else { 132 | None 133 | } 134 | }; 135 | 136 | let prefixes = ["<=", ">=", "<", ">"]; 137 | 138 | let prefix = (name) => { 139 | let rec loop = (prefixes) => { 140 | switch prefixes { 141 | | [] => (None, name) 142 | | [one, ...rest] => { 143 | switch (stripPrefix(name, one)) { 144 | | None => loop(rest) 145 | | Some(text) => (Some(one), text) 146 | } 147 | } 148 | } 149 | }; 150 | loop(prefixes) 151 | }; 152 | 153 | /* yaml https://github.com/avsm/ocaml-yaml 154 | this file https://github.com/esy/esy-install/blob/master/src/resolvers/exotics/opam-resolver/opam-repository-override.js 155 | also this one https://github.com/esy/esy-install/blob/master/src/resolvers/exotics/opam-resolver/opam-repository.js */ 156 | 157 | let splitExtra = (patch) => { 158 | switch (String.split_on_char('-', patch)) { 159 | | [] => assert(false) 160 | | [one] => (one, None) 161 | | [one, ...rest] => (one, Some(String.concat("-", rest))) 162 | } 163 | }; 164 | 165 | let parseDirectoryName = (name) => { 166 | open Shared.GenericVersion; 167 | switch (String.split_on_char('.', name)) { 168 | | [] => assert(false) 169 | | [single] => (single, Any) 170 | | [name, num, "x", "x" | "x-"] => { 171 | (name, 172 | And( 173 | AtLeast(OpamVersion.triple(int_of_string(num), 0, 0)), 174 | LessThan(OpamVersion.triple(int_of_string(num) + 1, 0, 0)) 175 | ) 176 | ) 177 | } 178 | | [name, num, minor, "x" | "x-"] => { 179 | (name, 180 | And( 181 | AtLeast(OpamVersion.triple(int_of_string(num), int_of_string(minor), 0)), 182 | LessThan(OpamVersion.triple(int_of_string(num), int_of_string(minor) + 1, 0)) 183 | ) 184 | ) 185 | } 186 | | [name, major, minor, patch] => { 187 | let (prefix, major) = prefix(major); 188 | let (patch, extra) = splitExtra(patch); 189 | let version = Shared.Types.opamFromNpmConcrete((int_of_string(major), int_of_string(minor), int_of_string(patch), extra)); 190 | (name, switch prefix { 191 | | None => Exactly(version) 192 | | Some(">") => GreaterThan(version) 193 | | Some(">=") => AtLeast(version) 194 | | Some("<" )=> LessThan(version) 195 | | Some("<=") => AtMost(version) 196 | | _ => assert(false) 197 | }) 198 | } 199 | | _ => failwith("Bad override version " ++ name) 200 | } 201 | }; 202 | }; 203 | 204 | let tee = (fn, value) => if (fn(value)) { Some(value) } else { None }; 205 | 206 | let getContents = baseDir => { 207 | switch (tee(Files.isFile, Filename.concat(baseDir, "package.json"))) { 208 | | Some(name) => try (ProcessJson.process(Yojson.Basic.from_file(name))) { 209 | | Failure(message) => failwith("Bad json " ++ baseDir ++ " " ++ message) 210 | } 211 | | None => 212 | switch (Filename.concat(baseDir, "package.yaml") |> tee(Files.isFile)) { 213 | | None => failwith("must have either package.json or package.yaml " ++ baseDir) 214 | | Some(name) => { 215 | let json = Yaml.of_string(Files.readFile(name) |! "unable to read yaml") |> expectResult("Bad yaml file") |> yamlToJson; 216 | /* print_endline(Yojson.Basic.to_string(json)); */ 217 | try(ProcessJson.process(json)) { 218 | | Failure(message) => failwith("Bad yaml jsom " ++ baseDir ++ " " ++ message) 219 | } 220 | } 221 | } 222 | } 223 | }; 224 | 225 | let getOverrides = (checkoutDir) => { 226 | let dir = Filename.concat(checkoutDir, "packages"); 227 | Files.readDirectory(dir) |> List.map(name => { 228 | let (realName, semver) = ParseName.parseDirectoryName(name); 229 | (realName, semver, Filename.concat(dir, name)) 230 | }) 231 | }; 232 | 233 | let findApplicableOverride = (overrides, name, version) => { 234 | let rec loop = fun 235 | | [] => None 236 | | [(oname, semver, fullPath), ..._] when name == oname && OpamVersion.matches(semver, version) => Some(getContents(fullPath)) 237 | | [_, ...rest] => loop(rest); 238 | loop(overrides) 239 | }; -------------------------------------------------------------------------------- /src/opam/OpamVersion.re: -------------------------------------------------------------------------------- 1 | 2 | open Shared.Types; 3 | 4 | let parseConcrete = Npm.OpamConcrete.parseConcrete; 5 | let triple = Npm.OpamConcrete.triple; 6 | 7 | let fromPrefix = (op, version) => { 8 | open Shared.GenericVersion; 9 | let v = parseConcrete(version); 10 | switch op { 11 | | `Eq => Exactly(v) 12 | | `Geq => AtLeast(v) 13 | | `Leq => AtMost(v) 14 | | `Lt => LessThan(v) 15 | | `Gt => GreaterThan(v) 16 | | `Neq => failwith("Can't do neq in opam version constraints") 17 | } 18 | }; 19 | 20 | let rec parseRange = opamvalue => { 21 | open OpamParserTypes; 22 | open Shared.GenericVersion; 23 | switch opamvalue { 24 | | Prefix_relop(_, op, String(_, version)) => fromPrefix(op, version) 25 | | Logop(_, `And, left, right) => { 26 | And(parseRange(left), parseRange(right)) 27 | } 28 | | Logop(_, `Or, left, right) => Or(parseRange(left), parseRange(right)) 29 | | String(_, version) => Exactly(parseConcrete(version)) 30 | | Option(_, contents, options) => { 31 | print_endline("Ignoring option: " ++ (options |> List.map(OpamPrinter.value) |> String.concat(" .. "))); 32 | parseRange(contents) 33 | } 34 | | y => { 35 | print_endline("Unexpected option -- pretending its any " ++ 36 | OpamPrinter.value(opamvalue)); 37 | Any 38 | } 39 | } 40 | }; 41 | 42 | let rec toDep = opamvalue => { 43 | open OpamParserTypes; 44 | open Shared.GenericVersion; 45 | switch opamvalue { 46 | | String(_, name) => (name, Any, `Link) 47 | | Option(_, String(_, name), [Ident(_, "build")]) => (name, Any, `Build) 48 | | Option(_, String(_, name), [Logop(_, `And, Ident(_, "build"), version)]) => (name, parseRange(version), `Build) 49 | | Option(_, String(_, name), [Ident(_, "test")]) => (name, Any, `Test) 50 | | Option(_, String(_, name), [Logop(_, `And, Ident(_, "test"), version)]) => (name, parseRange(version), `Test) 51 | | Group(_, [Logop(_, `Or, String(_, "base-no-ppx"), otherThing)]) => { 52 | /* yep we allow ppxs */ 53 | toDep(otherThing) 54 | } 55 | | Group(_, [Logop(_, `Or, String(_, one), String(_, two))]) => { 56 | print_endline("Arbitrarily choosing the second of two options: " ++ one ++ " and " ++ two); 57 | (two, Any, `Link) 58 | } 59 | | Group(_, [Logop(_, `Or, first, second)]) => { 60 | print_endline("Arbitrarily choosing the first of two options: " ++ OpamPrinter.value(first) ++ " and " ++ OpamPrinter.value(second)); 61 | toDep(first) 62 | } 63 | | Option(_, String(_, name), [option]) => { 64 | (name, parseRange(option), `Link) 65 | } 66 | | _ => { 67 | failwith("Can't parse this opam dep " ++ OpamPrinter.value(opamvalue)) 68 | } 69 | }; 70 | }; 71 | 72 | let splitInTwo = (string, char) => switch (String.split_on_char(char, string)) { 73 | | [""] => `Empty 74 | | [one] => `Just(one) 75 | | [one, two] => `Two(one, two) 76 | | [one, ...rest] => `Two(one, String.concat("~", rest)) 77 | }; 78 | 79 | [@test [ 80 | (("a", "b"), -1), 81 | (("aa", "a"), 1), 82 | (("a~b", "a"), -1), 83 | (("", "~beta1"), 1) 84 | ]] 85 | let compareWithTilde = (a, b) => { 86 | let atilde = String.contains(a, '~'); 87 | let btilde = String.contains(b, '~'); 88 | if (a == b) { 89 | 0 90 | } else if (atilde || btilde) { 91 | switch (splitInTwo(a, '~'), splitInTwo(b, '~')) { 92 | | (`Empty, `Two("", _)) => 1 93 | | (`Two("", _), `Empty) => -1 94 | | (`Empty, _) => -1 95 | | (_, `Empty) => 1 96 | 97 | | (`Two(a, _), `Just(b)) when a == String.sub(b, 0, String.length(a)) => -1 98 | | (`Two(a, _), `Just(b)) => compare(a, String.sub(b, 0, String.length(a))) 99 | 100 | | (`Just(a), `Just(b)) => assert(false) 101 | 102 | | (`Just(a), `Two(b, _)) when String.sub(a, 0, String.length(b)) == b => -1 103 | | (`Just(a), `Two(b, _)) => compare(String.sub(a, 0, String.length(b)), b) 104 | 105 | | (`Two(a, aa), `Two(b, bb)) when a == b => compare(aa, bb) 106 | | (`Two(a, _), `Two(b, _)) => compare(a, b) 107 | }; 108 | } else { 109 | compare(a, b) 110 | } 111 | }; 112 | 113 | [@test [ 114 | ((parseConcrete("1.2.3"), parseConcrete("1.2.4")), -1), 115 | ((parseConcrete("1.2.4"), parseConcrete("1.2.4")), 0), 116 | ((parseConcrete("1.2~alpha1"), parseConcrete("1.2.0~beta3")), -1), 117 | ((parseConcrete("1.2~alpha1"), parseConcrete("1.2")), -1), 118 | ]] 119 | let rec compare = (Alpha(a, na), Alpha(b, nb)) => { 120 | if (a == b) { 121 | switch (na, nb) { 122 | | (None, None) => 0 123 | | (None, _) => -1 124 | | (_, None) => 1 125 | | (Some(na), Some(nb)) => compareNums(na, nb) 126 | } 127 | } else { 128 | compareWithTilde(a, b) 129 | } 130 | } and compareNums = (Num(a, aa), Num(b, ab)) => { 131 | if (a == b) { 132 | switch (aa, ab) { 133 | | (None, None) => 0 134 | | (None, Some(Alpha(a, _))) when a != "" && a.[0] == '~' => 1 135 | | (Some(Alpha(a, _)), None) when a != "" && a.[0] == '~' => -1 136 | | (None, _) => -1 137 | | (_, None) => 1 138 | | (Some(aa), Some(ab)) => compare(aa, ab) 139 | } 140 | } else { 141 | a - b 142 | } 143 | }; 144 | 145 | let rec viewAlpha = (Alpha(a, na)) => { 146 | switch na { 147 | | None => a 148 | | Some(b) => a ++ viewNum(b) 149 | } 150 | } and viewNum = (Num(a, na)) => { 151 | string_of_int(a) ++ switch na { 152 | | None => "" 153 | | Some(a) => viewAlpha(a) 154 | } 155 | }; 156 | 157 | let matches = Shared.GenericVersion.matches(compare); 158 | 159 | let viewRange = Shared.GenericVersion.view(viewAlpha); -------------------------------------------------------------------------------- /src/opam/Registry.re: -------------------------------------------------------------------------------- 1 | 2 | open Shared; 3 | 4 | let filterNils = items => items |> List.filter(x => x != None) |> List.map(item => switch item { | Some(x) => x | None => assert(false)}); 5 | 6 | let getFromOpamRegistry = (config, fullName) => { 7 | let name = OpamFile.withoutScope(fullName); 8 | let (/+) = Filename.concat; 9 | let base = config.Types.opamRepository /+ "packages" /+ name; 10 | switch (Files.readDirectory(base)) { 11 | | exception _ => failwith("Opam package not in registry: " ++ name) 12 | | entries => { 13 | List.map( 14 | entry => { 15 | let semver = switch (String.split_on_char('.', entry)) { 16 | | [] | [_] => Types.Alpha("", None) 17 | | [_name, ...items] => { 18 | OpamVersion.parseConcrete(String.concat(".", items)) 19 | } 20 | }; 21 | /* PERF: we should cache this, instead of re-parsing it later again */ 22 | let manifest = OpamFile.parseManifest((name, semver), OpamParser.file(base /+ entry /+ "opam")); 23 | if (!manifest.OpamFile.available) { 24 | None 25 | } else { 26 | Some( 27 | (semver, 28 | ( 29 | base /+ entry /+ "opam", 30 | base /+ entry /+ "url", 31 | name, 32 | semver 33 | )) 34 | ) 35 | } 36 | }, 37 | entries 38 | ) |> filterNils 39 | } 40 | } 41 | }; -------------------------------------------------------------------------------- /src/opam/jbuild: -------------------------------------------------------------------------------- 1 | (jbuild_version 1) 2 | 3 | (library 4 | ((name opam) 5 | (preprocess (pps (ppx_deriving_yojson testre-ppx ppx_driver))) 6 | (libraries (npm shared lwt str curl cudf yaml ppx_deriving_yojson.runtime yojson opam-file-format)))) 7 | -------------------------------------------------------------------------------- /src/shared/Env.re: -------------------------------------------------------------------------------- 1 | 2 | module Npm = { 3 | [@deriving yojson] 4 | type t('sourceType) = { 5 | source: 'sourceType, 6 | resolved: Lockfile.realVersion, 7 | requested: Types.requestedDep, 8 | dependencies: list((string, option(t('sourceType)))) 9 | }; 10 | }; 11 | 12 | [@deriving yojson] 13 | type resolved = (string, Types.requestedDep, Lockfile.realVersion); 14 | 15 | [@deriving yojson] 16 | type fullPackage('sourceType) = { 17 | name: string, 18 | version: Lockfile.realVersion, 19 | source: 'sourceType, /* pending until I need to lock it down */ 20 | requested: Types.depsByKind, 21 | runtime: list(resolved), 22 | build: list(resolved), 23 | npm: list((string, Npm.t('sourceType))), 24 | }; 25 | 26 | [@deriving yojson] 27 | type rootPackage('sourceType) = { 28 | package: fullPackage('sourceType), 29 | runtimeBag: list(fullPackage('sourceType)) 30 | }; 31 | 32 | [@deriving yojson] 33 | type target = Default | Arch(string) | ArchSubArch(string, string); 34 | 35 | [@deriving yojson] 36 | type t('sourceType) = { 37 | targets: list((target, rootPackage('sourceType))), 38 | buildDependencies: list(rootPackage('sourceType)) 39 | }; 40 | 41 | let mapSnd = (mapper, (a, b)) => (a, mapper(b)); 42 | let mapOpt = (mapper, a) => switch a { | None => None | Some(x) => Some(mapper(x))}; 43 | 44 | let rec mapNpm = (mapper, npm) => { 45 | ...npm, 46 | Npm.source: mapper(npm.Npm.source), 47 | dependencies: List.map(mapSnd(mapOpt(mapNpm(mapper))), npm.Npm.dependencies) 48 | }; 49 | 50 | let mapFull = (mapper, full) => { 51 | ...full, 52 | source: mapper(full.source), 53 | npm: List.map(mapSnd(mapNpm(mapper)), full.npm) 54 | }; 55 | 56 | let mapRoot = (mapper, root) => { 57 | package: mapFull(mapper, root.package), 58 | runtimeBag: List.map(mapFull(mapper), root.runtimeBag) 59 | }; 60 | 61 | let map = (mapper, t) => { 62 | targets: List.map(mapSnd(mapRoot(mapper)), t.targets), 63 | buildDependencies: List.map(mapRoot(mapper), t.buildDependencies), 64 | }; -------------------------------------------------------------------------------- /src/shared/ExecCommand.re: -------------------------------------------------------------------------------- 1 | 2 | /** TODO use lwt or something */ 3 | 4 | /** 5 | * Get the output of a command, in lines. 6 | */ 7 | let execSync = (~cmd, ~onOut=?, ()) => { 8 | let chan = Unix.open_process_in(cmd); 9 | try { 10 | let rec loop = () => 11 | switch (Pervasives.input_line(chan)) { 12 | | exception End_of_file => [] 13 | | line => { 14 | switch onOut { 15 | | None => () 16 | | Some(fn) => fn(line) 17 | }; 18 | [line, ...loop()] 19 | } 20 | }; 21 | let lines = loop(); 22 | switch (Unix.close_process_in(chan)) { 23 | | WEXITED(0) => (lines, true) 24 | | WEXITED(_) 25 | | WSIGNALED(_) 26 | | WSTOPPED(_) => (lines, false) 27 | } 28 | } { 29 | | End_of_file => ([], false) 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/shared/Files.re: -------------------------------------------------------------------------------- 1 | 2 | /* TODO use lwt-node */ 3 | 4 | let maybeStat = (path) => 5 | try (Some(Unix.stat(path))) { 6 | | Unix.Unix_error(Unix.ENOENT, _, _) => None 7 | }; 8 | 9 | let expectSuccess = (msg, v) => if (v) { () } else { failwith(msg) }; 10 | 11 | [@test [ 12 | (("/a/b/c", "/a/b/d"), "../d"), 13 | (("/a/b/c", "/a/b/d/e"), "../d/e"), 14 | (("/a/b/c", "/d/e/f"), "../../../d/e/f"), 15 | (("/a/b/c", "/a/b/c/d/e"), "./d/e"), 16 | ]] 17 | let relpath = (base, path) => { 18 | let rec loop = (bp, pp) => { 19 | switch (bp, pp) { 20 | | ([a, ...ra], [b, ...rb]) when a == b => loop(ra, rb) 21 | | _ => (bp, pp) 22 | } 23 | }; 24 | let (base, path) = loop(String.split_on_char('/', base), String.split_on_char('/', path)); 25 | String.concat("/", 26 | (base == [] ? ["."] : List.map((_) => "..", base)) 27 | @ path 28 | ) 29 | }; 30 | 31 | let symlink = (source, dest) => { 32 | Unix.symlink(relpath(Filename.dirname(dest), source), dest) 33 | }; 34 | 35 | let readFile = path => { 36 | switch (maybeStat(path)) { 37 | | Some({Unix.st_kind: Unix.S_REG}) => 38 | let ic = open_in(path); 39 | let try_read = () => 40 | switch (input_line(ic)) { 41 | | exception End_of_file => None 42 | | x => Some(x) 43 | }; 44 | let rec loop = (acc) => 45 | switch (try_read()) { 46 | | Some(s) => loop([s, ...acc]) 47 | | None => 48 | close_in(ic); 49 | List.rev(acc) 50 | }; 51 | let text = loop([]) |> String.concat(String.make(1, '\n')); 52 | Some(text) 53 | | _ => None 54 | } 55 | }; 56 | 57 | let writeFile = (path, contents) => { 58 | try { 59 | let out = open_out(path); 60 | output_string(out, contents); 61 | close_out(out); 62 | true 63 | } { 64 | | _ => false 65 | } 66 | }; 67 | 68 | let copy = (~source, ~dest) => 69 | switch (maybeStat(source)) { 70 | | None => false 71 | | Some({Unix.st_perm}) => 72 | let fs = Unix.openfile(source, [Unix.O_RDONLY], st_perm); 73 | let fd = Unix.openfile(dest, [Unix.O_WRONLY, Unix.O_CREAT, Unix.O_TRUNC], st_perm); 74 | let buffer_size = 8192; 75 | let buffer = Bytes.create(buffer_size); 76 | let rec copy_loop = () => 77 | switch (Unix.read(fs, buffer, 0, buffer_size)) { 78 | | 0 => () 79 | | r => 80 | ignore(Unix.write(fd, buffer, 0, r)); 81 | copy_loop() 82 | }; 83 | copy_loop(); 84 | Unix.close(fs); 85 | Unix.close(fd); 86 | true 87 | }; 88 | 89 | let exists = (path) => 90 | switch (maybeStat(path)) { 91 | | None => false 92 | | Some(_) => true 93 | }; 94 | 95 | let isFile = path => switch (maybeStat(path)) { 96 | | Some({Unix.st_kind: Unix.S_REG}) => true 97 | | _ => false 98 | }; 99 | 100 | let isDirectory = path => switch (maybeStat(path)) { 101 | | Some({Unix.st_kind: Unix.S_DIR}) => true 102 | | _ => false 103 | }; 104 | 105 | let readDirectory = (dir) => { 106 | let maybeGet = (handle) => 107 | try (Some(Unix.readdir(handle))) { 108 | | End_of_file => None 109 | }; 110 | let rec loop = (handle) => 111 | switch (maybeGet(handle)) { 112 | | None => 113 | Unix.closedir(handle); 114 | [] 115 | | Some(name) when name == Filename.current_dir_name || name == Filename.parent_dir_name => loop(handle) 116 | | Some(name) => [name, ...loop(handle)] 117 | }; 118 | loop(Unix.opendir(dir)) 119 | }; 120 | 121 | let rec mkdirp = (dest) => 122 | if (! exists(dest)) { 123 | let parent = Filename.dirname(dest); 124 | mkdirp(parent); 125 | Unix.mkdir(dest, 0o740) 126 | }; 127 | 128 | let rec copyDeep = (~source, ~dest) => { 129 | mkdirp(Filename.dirname(dest)); 130 | switch (maybeStat(source)) { 131 | | None => () 132 | | Some({Unix.st_kind: Unix.S_DIR}) => 133 | readDirectory(source) 134 | |> List.iter( 135 | (name) => 136 | copyDeep(~source=Filename.concat(source, name), ~dest=Filename.concat(dest, name)) 137 | ) 138 | | Some({Unix.st_kind: Unix.S_REG}) => copy(~source, ~dest) |> ignore 139 | | _ => () 140 | } 141 | }; 142 | 143 | let rec removeDeep = path => { 144 | switch (Unix.lstat(path)) { 145 | | exception Unix.Unix_error(Unix.ENOENT, _, _) => () 146 | | {Unix.st_kind: Unix.S_LNK} => { 147 | Unix.unlink(path) 148 | } 149 | | {Unix.st_kind: Unix.S_DIR} => 150 | readDirectory(path) 151 | |> List.iter((name) => removeDeep(Filename.concat(path, name))); 152 | Unix.rmdir(path); 153 | | _ => Unix.unlink(path) 154 | } 155 | }; 156 | 157 | let (/+) = Filename.concat; 158 | 159 | let crawl = (base) => { 160 | let rec inner = (base, rel, fn) => { 161 | readDirectory(base) |> List.iter(name => { 162 | let full = base /+ name; 163 | if (isDirectory(full)) { 164 | inner(full, rel /+ name, fn) 165 | } else { 166 | fn(rel /+ name, full) 167 | } 168 | }) 169 | }; 170 | inner(base, "") 171 | }; -------------------------------------------------------------------------------- /src/shared/GenericVersion.re: -------------------------------------------------------------------------------- 1 | 2 | [@deriving yojson] 3 | type range('inner) = 4 | | Or(range('inner), range('inner)) 5 | | And(range('inner), range('inner)) 6 | | Exactly('inner) 7 | | GreaterThan('inner) 8 | | AtLeast('inner) 9 | | LessThan('inner) 10 | | AtMost('inner) 11 | | Nothing 12 | | Any; 13 | /* | UntilNextMajor('concrete) | UntilNextMinor('concrete); */ 14 | 15 | /** TODO want a way to exclude npm -alpha items when they don't apply */ 16 | 17 | let rec matches = (compareInner, range, concrete) => { 18 | switch range { 19 | | Exactly(a) => compareInner(a, concrete) == 0 20 | | Any => true 21 | | Nothing => false 22 | | GreaterThan(a) => compareInner(a, concrete) < 0 23 | | AtLeast(a) => compareInner(a, concrete) <= 0 24 | | LessThan(a) => compareInner(a, concrete) > 0 25 | | AtMost(a) => compareInner(a, concrete) >= 0 26 | | And(a, b) => matches(compareInner, a, concrete) && matches(compareInner, b, concrete) 27 | | Or(a, b) => matches(compareInner, a, concrete) || matches(compareInner, b, concrete) 28 | } 29 | }; 30 | 31 | let rec isTooLarge = (compareInner, range, concrete) => { 32 | switch range { 33 | | Exactly(a) => compareInner(a, concrete) < 0 34 | | Any => false 35 | | Nothing => false 36 | | GreaterThan(a) => false 37 | | AtLeast(a) => false 38 | | LessThan(a) => compareInner(a, concrete) <= 0 39 | | AtMost(a) => compareInner(a, concrete) < 0 40 | | And(a, b) => isTooLarge(compareInner, a, concrete) || isTooLarge(compareInner, b, concrete) 41 | | Or(a, b) => isTooLarge(compareInner, a, concrete) && isTooLarge(compareInner, b, concrete) 42 | } 43 | }; 44 | 45 | let rec view = (viewInner, range) => { 46 | switch range { 47 | | Exactly(a) => viewInner(a) 48 | | Any => "*" 49 | | Nothing => "none" 50 | | GreaterThan(a) => "> " ++ viewInner(a) 51 | | AtLeast(a) => ">= " ++ viewInner(a) 52 | | LessThan(a) => "< " ++ viewInner(a) 53 | | AtMost(a) => "<= " ++ viewInner(a) 54 | | And(a, b) => view(viewInner, a) ++ " && " ++ view(viewInner, b) 55 | | Or(a, b) => view(viewInner, a) ++ " || " ++ view(viewInner, b) 56 | } 57 | }; 58 | 59 | let rec map = (transform, range) => { 60 | switch range { 61 | | Exactly(a) => Exactly(transform(a)) 62 | | Any => Any 63 | | Nothing => Nothing 64 | | GreaterThan(a) => GreaterThan(transform(a)) 65 | | AtLeast(a) => AtLeast(transform(a)) 66 | | LessThan(a) => LessThan(transform(a)) 67 | | AtMost(a) => AtMost(transform(a)) 68 | | And(a, b) => And(map(transform, a), map(transform, b)) 69 | | Or(a, b) => Or(map(transform, a), map(transform, b)) 70 | } 71 | }; -------------------------------------------------------------------------------- /src/shared/GithubVersion.re: -------------------------------------------------------------------------------- 1 | 2 | [@test [ 3 | ("let-def/wall#8b47b5ce898f6b35d6cbf92aa12baadd52f05350", Some(Types.Github("let-def", "wall", Some("8b47b5ce898f6b35d6cbf92aa12baadd52f05350")))), 4 | ("bsancousi/bsb-native#fast", Some(Types.Github("bsancousi", "bsb-native", Some("fast")))), 5 | ("v1.2.3", None) 6 | ]] 7 | let parseGithubVersion = text => { 8 | let parts = Str.split(Str.regexp_string("/"), text); 9 | switch parts { 10 | | [org, rest] => { 11 | switch (Str.split(Str.regexp_string("#"), rest)) { 12 | | [repo, ref] => { 13 | Some(Types.Github(org, repo, Some(ref))) 14 | } 15 | | [repo] => { 16 | Some(Types.Github(org, repo, None)) 17 | } 18 | | _ => None 19 | } 20 | } 21 | | _ => None 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/shared/Infix.re: -------------------------------------------------------------------------------- 1 | let (|?>) = (a, b) => switch a { | None => None | Some(x) => b(x) }; 2 | let (|?>>) = (a, b) => switch a { | None => None | Some(x) => Some(b(x)) }; 3 | let (|?) = (a, b) => switch a { | None => b | Some(a) => a }; 4 | let (|??) = (a, b) => switch a { | None => b | Some(a) => Some(a) }; 5 | let (|!) = (a, b) => switch a { | None => failwith(b) | Some(a) => a }; -------------------------------------------------------------------------------- /src/shared/Lockfile.re: -------------------------------------------------------------------------------- 1 | 2 | [@deriving yojson] 3 | type realVersion = [ 4 | | `Github(string, string, option(string)) 5 | | `Npm(Types.npmConcrete) 6 | | `Opam(Types.opamConcrete) 7 | | `Git(string) 8 | | `File(string) 9 | ]; 10 | 11 | let viewRealVersion: realVersion => string = v => switch v { 12 | | `Github(user, repo, ref) => "github-" ++ user ++ "__" ++ repo ++ (switch ref { | Some(x) => "__" ++ x | None => ""}) 13 | | `Git(s) => "git-" ++ s 14 | | `Npm(t) => "npm-" ++ Types.viewNpmConcrete(t) 15 | | `Opam(t) => "opam-" ++ Types.viewOpamConcrete(t) 16 | | `File(s) => "local-file" 17 | }; 18 | 19 | let plainVersionNumber = v => switch v { 20 | | `Github(user, repo, ref) => user ++ "__" ++ repo ++ (switch ref { | Some(x) => "__" ++ x | None => ""}) 21 | | `Git(s) => s 22 | | `Npm(t) => Types.viewNpmConcrete(t) 23 | | `Opam(t) => Types.viewOpamConcrete(t) 24 | /* TODO hash the file path or something */ 25 | | `File(s) => "local-file-0000" 26 | }; -------------------------------------------------------------------------------- /src/shared/Types.re: -------------------------------------------------------------------------------- 1 | 2 | [@deriving yojson] 3 | type npmConcrete = (int, int, int, option(string)); 4 | 5 | type alpha = Alpha(string, option(num)) 6 | and num = Num(int, option(alpha)); 7 | 8 | let alpha_to_yojson = (Alpha(text, num)) => { 9 | let rec lnum = num => switch num { 10 | | None => [] 11 | | Some(Num(n, a)) => [`Int(n), ...lalpha(a)] 12 | } and lalpha = alpha => switch alpha { 13 | | None => [] 14 | | Some(Alpha(a, n)) => [`String(a), ...lnum(n)] 15 | }; 16 | `List([`String(text), ...(lnum(num))]) 17 | }; 18 | 19 | let module ResultInfix = { 20 | let (|!>) = (item, fn) => switch item { 21 | | Result.Ok(value) => fn(value) 22 | | Error(e) => Result.Error(e) 23 | }; 24 | let (|!>>) = (item, fn) => switch item { 25 | | Result.Ok(value) => Result.Ok(fn(value)) 26 | | Error(e) => Result.Error(e) 27 | }; 28 | let ok = v => Result.Ok(v); 29 | let fail = v => Result.Error(v); 30 | }; 31 | 32 | let alpha_of_yojson = (json) => switch json { 33 | | `List(items) => { 34 | open ResultInfix; 35 | let rec lnum = items => switch items { 36 | | [] => ok(None) 37 | | [`Int(n), ...rest] => lalpha(rest) |!>> r => Some(Num(n, r)) 38 | | _ => fail("Num should be a number") 39 | } and lalpha = items => switch items { 40 | | [] => ok(None) 41 | | [`String(n), ...rest] => lnum(rest) |!>> r => Some(Alpha(n, r)) 42 | | _ => fail("Alpha should be string") 43 | }; 44 | lalpha(items) |!> v => switch v { 45 | | None => fail("No alpha") 46 | | Some(v) => ok(v) 47 | }; 48 | } 49 | | _ => Result.Error("Alpha should be a list") 50 | }; 51 | 52 | [@deriving yojson] 53 | type opamConcrete = alpha; 54 | 55 | [@deriving yojson] 56 | type opamRange = GenericVersion.range(opamConcrete); 57 | [@deriving yojson] 58 | type npmRange = GenericVersion.range(npmConcrete); 59 | 60 | let viewNpmConcrete = ((m, i, p, r)) => { 61 | ([m, i, p] |> List.map(string_of_int) |> String.concat(".")) 62 | ++ 63 | switch r { | None => "" | Some(a) => a} 64 | }; 65 | 66 | let rec viewOpamConcrete = (Alpha(a, na)) => { 67 | switch na { 68 | | None => a 69 | | Some(b) => a ++ viewNum(b) 70 | } 71 | } and viewNum = (Num(a, na)) => { 72 | string_of_int(a) ++ switch na { 73 | | None => "" 74 | | Some(a) => viewOpamConcrete(a) 75 | } 76 | }; 77 | 78 | type json = Yojson.Safe.json; 79 | let json_to_yojson = x => x; 80 | let json_of_yojson = x => Result.Ok(x); 81 | 82 | [@deriving yojson] 83 | type opamFile = (json, list((string, string)), list(string)); 84 | 85 | module PendingSource = { 86 | [@deriving yojson] 87 | type t = 88 | | WithOpamFile(t, opamFile) 89 | /* url & checksum */ 90 | | Archive(string, option(string)) 91 | /* url & ref */ 92 | | GitSource(string, option(string)) 93 | | GithubSource(string, string, option(string)) 94 | | File(string) 95 | | NoSource; 96 | }; 97 | 98 | /** Lock that down */ 99 | module Source = { 100 | [@deriving yojson] 101 | type inner = 102 | /* url & checksum */ 103 | | Archive(string, string) 104 | /* url & commit */ 105 | | GitSource(string, string) 106 | | GithubSource(string, string, string) 107 | | File(string) 108 | | NoSource; 109 | [@deriving yojson] 110 | type t = (inner, option(opamFile)); 111 | }; 112 | 113 | [@deriving yojson] 114 | type requestedDep = 115 | | Npm(GenericVersion.range(npmConcrete)) 116 | | Github(string, string, option(string)) /* user, repo, ref (branch/tag/commit) */ 117 | | Opam(GenericVersion.range(opamConcrete)) /* opam allows a bunch of weird stuff. for now I'm just doing semver */ 118 | | Git(string) 119 | ; 120 | 121 | let resolvedPrefix = "esyi5-"; 122 | 123 | [@deriving yojson] 124 | type dep = (string, requestedDep); 125 | 126 | [@deriving yojson] 127 | type depsByKind = { 128 | runtime: list(dep), 129 | dev: list(dep), 130 | build: list(dep), 131 | /* This is for npm deps of an esy package. npm deps of an npm package are classified as "runtime". */ 132 | npm: list(dep), 133 | /* TODO targets or something */ 134 | }; 135 | 136 | let viewReq = req => switch req { 137 | | Github(org, repo, ref) => "github: " ++ org ++ "/" ++ repo 138 | | Git(s) => "git: " ++ s 139 | | Npm(t) => "npm: " ++ GenericVersion.view(viewNpmConcrete, t) 140 | | Opam(t) => "opam: " ++ GenericVersion.view(viewOpamConcrete, t) 141 | }; 142 | 143 | type config = { 144 | esyOpamOverrides: string, 145 | opamRepository: string, 146 | baseDirectory: string, 147 | }; 148 | 149 | 150 | let opamFromNpmConcrete = ((major, minor, patch, rest)) => { 151 | Alpha("", 152 | Some( 153 | Num(major, Some(Alpha(".", Some( 154 | Num(minor, Some(Alpha(".", Some( 155 | Num(patch, switch rest { 156 | | None => None 157 | | Some(rest) => Some(Alpha(rest, None)) 158 | }) 159 | )))) 160 | )))) 161 | ) 162 | ) 163 | }; 164 | -------------------------------------------------------------------------------- /src/shared/Wget.re: -------------------------------------------------------------------------------- 1 | 2 | let get = url => { 3 | let (lines, good) = ExecCommand.execSync(~cmd="curl -s -f -L " ++ url, ()); 4 | if (good) { 5 | Some(String.concat("\n", lines)) 6 | } else { 7 | None 8 | } 9 | } -------------------------------------------------------------------------------- /src/shared/jbuild: -------------------------------------------------------------------------------- 1 | (jbuild_version 1) 2 | 3 | (library 4 | ((name shared) 5 | (preprocess (pps (ppx_deriving_yojson testre-ppx ppx_driver))) 6 | (libraries (str curl cudf yaml ppx_deriving_yojson.runtime yojson opam-file-format)))) 7 | 8 | -------------------------------------------------------------------------------- /src/solve/CudfVersions.re: -------------------------------------------------------------------------------- 1 | open Shared; 2 | 3 | type t = { 4 | lookupRealVersion: Hashtbl.t((string, int), Lockfile.realVersion), 5 | lookupIntVersion: Hashtbl.t((string, Lockfile.realVersion), int), 6 | }; 7 | 8 | let init = () => { 9 | lookupRealVersion: Hashtbl.create(100), 10 | lookupIntVersion: Hashtbl.create(100), 11 | }; 12 | 13 | let update = (t, name, realVersion, version) => { 14 | Hashtbl.replace(t.lookupIntVersion, (name, realVersion), version); 15 | Hashtbl.replace(t.lookupRealVersion, (name, version), realVersion); 16 | }; 17 | 18 | let getRealVersion = (cudfVersions, package) => { 19 | switch (Hashtbl.find(cudfVersions.lookupRealVersion, (package.Cudf.package, package.Cudf.version))) { 20 | | exception Not_found => { 21 | failwith("Tried to find a package that wasn't listed in the versioncache " ++ package.Cudf.package ++ " " ++ string_of_int(package.Cudf.version)) 22 | } 23 | | version => version 24 | }; 25 | }; 26 | 27 | let matchesSource = (source, cudfVersions, package) => { 28 | SolveUtils.satisfies(getRealVersion(cudfVersions, package), source) 29 | }; 30 | -------------------------------------------------------------------------------- /src/solve/Github.re: -------------------------------------------------------------------------------- 1 | 2 | open Shared.Infix; 3 | 4 | let githubFileUrl = (user, repo, ref, file) => { 5 | "https://raw.githubusercontent.com/" ++ user ++"/" ++ repo ++ "/" ++ (ref |? "master") ++ "/" ++ file 6 | }; 7 | 8 | let getManifest = (name, user, repo, ref) => { 9 | let getFile = name => Shared.Wget.get(githubFileUrl(user, repo, ref, name)); 10 | switch (getFile("esy.json")) { 11 | | Some(text) => `PackageJson(Yojson.Basic.from_string(text)) 12 | | None => switch (getFile("package.json")) { 13 | | Some(text) => `PackageJson(Yojson.Basic.from_string(text)) 14 | | None => switch (getFile(name ++ ".opam")) { 15 | | Some(text) => failwith("No opam parsing yet for github repos") 16 | | None => switch (getFile("opam")) { 17 | | Some(text) => failwith("No opam parsing yet for github repos") 18 | | None => failwith("No manifest found in github repo") 19 | } 20 | } 21 | } 22 | } 23 | }; -------------------------------------------------------------------------------- /src/solve/Manifest.re: -------------------------------------------------------------------------------- 1 | open Opam; 2 | open Npm; 3 | 4 | let getDeps = manifest => { 5 | let depsByKind = switch manifest { 6 | | `OpamFile(opam) => OpamFile.process(opam) 7 | | `PackageJson(json) => PackageJson.process(json) 8 | }; 9 | depsByKind 10 | }; 11 | 12 | let getSource = (manifest, name, version) => 13 | switch version { 14 | | `Github(user, repo, ref) => Shared.Types.PendingSource.GithubSource(user, repo, ref) 15 | | `File(path) => Shared.Types.PendingSource.File(path) 16 | | _ => switch manifest { 17 | | `OpamFile(opam) => Shared.Types.PendingSource.WithOpamFile(OpamFile.getSource(opam), OpamFile.toPackageJson(opam, name, version)) 18 | | `PackageJson(json) => PackageJson.getSource(json) 19 | }; 20 | }; -------------------------------------------------------------------------------- /src/solve/Solve.re: -------------------------------------------------------------------------------- 1 | open Shared; 2 | 3 | let unsatisfied = (map, (name, range)) => { 4 | switch (Hashtbl.find(map, name)) { 5 | | exception Not_found => true 6 | | versions => !List.exists(v => SolveUtils.satisfies(v, range), versions) 7 | } 8 | }; 9 | 10 | let findSatisfyingInMap = (map, name, range) => { 11 | List.find(v => SolveUtils.satisfies(v, range), Hashtbl.find(map, name)) 12 | }; 13 | 14 | let justDepsn = ((_, _, _, deps)) => deps; 15 | 16 | let module StringMap = Map.Make({type t = string; let compare = compare}); 17 | 18 | let makeNpm = ((npmVersionMap, npmToVersions), packages) => { 19 | let thisLevel = packages |> List.map(((name, range)) => (name, range, findSatisfyingInMap(npmToVersions, name, range))); 20 | let parentMap = thisLevel |> List.fold_left((map, (name, _, version)) => { 21 | StringMap.add(name, version, map) 22 | }, StringMap.empty); 23 | let rec toNpm = (parentage, name, range, realVersion) => { 24 | let (manifest, deps) = Hashtbl.find(npmVersionMap, (name, realVersion)); 25 | let thisLevel = deps.Types.npm |> List.map(((name, range)) => { 26 | switch (StringMap.find_opt(name, parentage)) { 27 | | Some(v) when SolveUtils.satisfies(v, range) => (name, None) 28 | | _ => (name, Some((range, findSatisfyingInMap(npmToVersions, name, range)))) 29 | } 30 | }); 31 | let childMap = thisLevel |> List.fold_left((map, (name, maybe)) => { 32 | switch maybe { 33 | | None => map 34 | | Some((range, version)) => StringMap.add(name, version, map) 35 | } 36 | }, parentage); 37 | Env.Npm.({ 38 | source: Manifest.getSource(manifest, name, realVersion), 39 | resolved: realVersion, 40 | requested: range, 41 | dependencies: thisLevel |> List.map(((name, contents)) => { 42 | switch contents { 43 | | None => (name, None) 44 | | Some((range, real)) => (name, Some(toNpm(childMap, name, range, real))) 45 | } 46 | }) 47 | }) 48 | }; 49 | thisLevel |> List.map(((name, range, real)) => (name, toNpm(parentMap, name, range, real))) 50 | }; 51 | 52 | let makeFullPackage = (name, version, manifest, deps, solvedDeps, buildToVersions, npmPair) => { 53 | let nameToVersion = Hashtbl.create(100); 54 | solvedDeps |> List.iter(((name, version, _, _)) => Hashtbl.replace(nameToVersion, name, version)); 55 | 56 | Env.({ 57 | package: { 58 | name, 59 | version, 60 | source: Manifest.getSource(manifest, name, version), 61 | requested: deps, 62 | runtime: deps.Types.runtime |> List.map(((name, range)) => (name, range, Hashtbl.find(nameToVersion, name))), 63 | build: deps.Types.build |> List.map(((name, range)) => (name, range, findSatisfyingInMap(buildToVersions, name, range))), 64 | npm: makeNpm(npmPair, deps.Types.npm), 65 | }, 66 | runtimeBag: solvedDeps |> List.map(((name, version, manifest, deps)) => { 67 | name, 68 | version, 69 | source: Manifest.getSource(manifest, name, version), 70 | requested: deps, 71 | runtime: deps.Types.runtime |> List.map(((name, range)) => (name, range, Hashtbl.find(nameToVersion, name))), 72 | build: deps.Types.build |> List.map(((name, range)) => (name, range, findSatisfyingInMap(buildToVersions, name, range))), 73 | npm: [], 74 | }), 75 | }) 76 | }; 77 | 78 | let settleBuildDeps = (cache, solvedDeps, requestedBuildDeps) => { 79 | let allTransitiveBuildDeps = solvedDeps |> List.map(justDepsn) |> List.map(deps => deps.Types.build) |> List.concat; 80 | /* let allTransitiveBuildDeps = allNeededBuildDeps @ ( 81 | solvedTargets |> List.map(((_, deps)) => getBuildDeps(deps)) |> List.concat |> List.concat 82 | ); */ 83 | 84 | let buildDepsToInstall = allTransitiveBuildDeps @ requestedBuildDeps; 85 | let nameToVersions = Hashtbl.create(100); 86 | let versionMap = Hashtbl.create(100); 87 | let rec loop = (buildDeps) => { 88 | let toAdd = buildDeps |> List.filter(unsatisfied(nameToVersions)); 89 | if (toAdd != []) { 90 | let solved = SolveDeps.solveLoose(~cache, ~requested=toAdd, ~current=nameToVersions, ~deep=false); 91 | solved |> List.map(((name, version, manifest, deps)) => { 92 | if (!Hashtbl.mem(versionMap, (name, version))) { 93 | Hashtbl.replace(nameToVersions, name, [ 94 | version, 95 | ...(Hashtbl.mem(nameToVersions, name) ? Hashtbl.find(nameToVersions, name) : []) 96 | ]); 97 | 98 | let solvedDeps = SolveDeps.solve(~cache, ~requested=deps.Types.runtime); 99 | Hashtbl.replace(versionMap, (name, version), (manifest, deps, solvedDeps)); 100 | let childBuilds = solvedDeps |> List.map(justDepsn) |> List.map(deps => deps.Types.build) |> List.concat; 101 | childBuilds @ deps.Types.build 102 | } else { 103 | [] 104 | } 105 | }) |> List.concat |> buildDeps => { 106 | loop(buildDeps) 107 | }; 108 | } 109 | }; 110 | 111 | loop(buildDepsToInstall); 112 | 113 | (versionMap, nameToVersions) 114 | }; 115 | 116 | let resolveNpm = (cache, npmRequests) => { 117 | /* Allow relaxing the constraint */ 118 | let solvedDeps = SolveDeps.solve(~cache, ~requested=npmRequests); 119 | let npmVersionMap = Hashtbl.create(100); 120 | let npmToVersions = Hashtbl.create(100); 121 | solvedDeps |> List.iter(((name, version, manifest, deps)) => { 122 | Hashtbl.replace(npmVersionMap, (name, version), (manifest, deps)); 123 | Hashtbl.replace(npmToVersions, name, [ 124 | version, 125 | ...Hashtbl.mem(npmToVersions, name) ? Hashtbl.find(npmToVersions, name) : [] 126 | ]) 127 | }); 128 | (npmVersionMap, npmToVersions) 129 | }; 130 | 131 | let solve = (config, manifest) => { 132 | SolveUtils.checkRepositories(config); 133 | let cache = SolveDeps.initCache(config); 134 | let depsByKind = Manifest.getDeps(manifest); 135 | 136 | let solvedDeps = SolveDeps.solve(~cache, ~requested=depsByKind.runtime); 137 | 138 | /** TODO should targets be determined completely separately? 139 | * seems like we'll want to be able to ~fetch~ independently... 140 | * but maybe solve all at once? 141 | * yeah probably. makes things a little harder for me. 142 | */ 143 | /* 144 | let solvedTargets = targets |> List.map(target => { 145 | let targetDeps = SolveDeps.solveWithAsMuchOverlapAsPossible( 146 | ~cache, 147 | ~requested=target.dependencies.runtime, 148 | ~current=solvedDeps 149 | ); 150 | (target, targetDeps) 151 | }); 152 | */ 153 | 154 | let (buildVersionMap, buildToVersions) = settleBuildDeps(cache, solvedDeps, depsByKind.build); 155 | 156 | 157 | /* Ok, time for npm. */ 158 | let allNpmRequests = 159 | Hashtbl.fold(((name, version), (manifest, deps, solvedDeps), result) => deps.Types.npm @ (List.map(((_, _, _, deps)) => deps.Types.npm, solvedDeps) |> List.concat) @ result, buildVersionMap, []) 160 | @ List.concat(List.map(((_, _, _, deps)) => deps.Types.npm, solvedDeps)) 161 | @ depsByKind.npm 162 | ; 163 | let npmPair = resolveNpm(cache, allNpmRequests); 164 | 165 | 166 | 167 | let allBuildPackages = Hashtbl.fold(((name, version), (manifest, deps, solvedDeps), result) => { 168 | [makeFullPackage(name, version, manifest, deps, solvedDeps, buildToVersions, npmPair), ...result] 169 | }, buildVersionMap, []); 170 | 171 | let env = { 172 | Env.targets: [(Env.Default, makeFullPackage("*root*", `File("./"), manifest, depsByKind, solvedDeps, buildToVersions, npmPair))], 173 | buildDependencies: allBuildPackages, 174 | }; 175 | 176 | Env.map(SolveUtils.lockDownSource, env) 177 | }; 178 | -------------------------------------------------------------------------------- /src/solve/SolveDeps.re: -------------------------------------------------------------------------------- 1 | open Opam; 2 | open Npm; 3 | open Shared; 4 | 5 | open SolveUtils; 6 | 7 | module T = { 8 | type manifest = [ 9 | | `OpamFile(OpamFile.manifest) 10 | | `PackageJson(Yojson.Basic.json) 11 | ]; 12 | 13 | type cache = { 14 | opamOverrides: list((string, Types.opamRange, string)), 15 | npmPackages: Hashtbl.t(string, Yojson.Basic.json), 16 | opamPackages: Hashtbl.t(string, OpamFile.manifest), 17 | versions: VersionCache.t, 18 | manifests: Hashtbl.t((string, Lockfile.realVersion), (manifest, Types.depsByKind)), 19 | }; 20 | 21 | type state = { 22 | cache, 23 | /* universe: Cudf.universe, */ 24 | cudfVersions: CudfVersions.t, 25 | }; 26 | }; 27 | open T; 28 | 29 | let initCache = config => { 30 | versions: { 31 | availableNpmVersions: Hashtbl.create(100), 32 | availableOpamVersions: Hashtbl.create(100), 33 | config, 34 | }, 35 | opamOverrides: OpamOverrides.getOverrides(config.Types.esyOpamOverrides), 36 | npmPackages: Hashtbl.create(100), 37 | opamPackages: Hashtbl.create(100), 38 | manifests: Hashtbl.create(100), 39 | }; 40 | 41 | 42 | /** 43 | * 44 | * Order of operations: 45 | * - solve for real deps of the main module 46 | * - [list of solved deps], [list of build deps requests for MAIN] 47 | * - can look in the manifest cache for build deps of the solved deps 48 | * 49 | * - now I want to dedup where possible, so I'm installing the minimum amount of build deps 50 | * - now I have a list of list((name, list(realVersion))) that is the versions of the build deps to install 51 | * - for each of those, do `solveDeps(cache, depsOfThatOneRealVersion)` 52 | * - build deps aren't allowed to depend on each other I don't think 53 | * - that will result in new buildDeps needed 54 | * - churn until we're done 55 | * 56 | * - when making the lockfile, for each build dep that a thing wants, find one that we've chosen, whichever is most recent probably 57 | * 58 | */ 59 | 60 | 61 | let cudfDep = (owner, universe, cudfVersions, (name, source)) => { 62 | let available = Cudf.lookup_packages(universe, name); 63 | let matching = available 64 | |> List.filter(CudfVersions.matchesSource(source, cudfVersions)); 65 | let final = (if (matching == []) { 66 | let hack = switch source { 67 | | Opam(opamVersionRange) => { 68 | /* print_endline("Trying to convert from pseudo-npm"); */ 69 | let nonNpm = tryConvertingOpamFromNpm(opamVersionRange); 70 | /* print_endline(Shared.GenericVersion.view(Shared.Types.viewOpamConcrete, nonNpm)); */ 71 | available |> List.filter(CudfVersions.matchesSource(Opam(nonNpm), cudfVersions)) 72 | } 73 | | _ => [] 74 | }; 75 | switch hack { 76 | | [] => { 77 | /* We know there are packages that want versions of ocaml we don't support, it's ok */ 78 | if (name == "ocaml") { 79 | [] 80 | } else { 81 | print_endline("🛑 🛑 🛑 Requirement unsatisfiable " ++ owner ++ " wants " ++ name ++ " at version " ++ Types.viewReq(source)); 82 | available |> List.iter(package => print_endline(" - " ++ Lockfile.viewRealVersion(CudfVersions.getRealVersion(cudfVersions, package)))); 83 | [] 84 | } 85 | } 86 | | matching => matching 87 | } 88 | } else { 89 | matching 90 | }) 91 | |> List.map(package => (package.Cudf.package, Some((`Eq, package.Cudf.version)))); 92 | /** If no matching packages, make a requirement for a package that doesn't exist. */ 93 | final == [] ? [("**not-a-packge%%%", Some((`Eq, 10000000000)))] : final 94 | }; 95 | 96 | /* TODO need to figure out how to specify what deps we're interested in. 97 | * 98 | * Maybe a fn: Types.depsByKind => List(Types.dep) 99 | * 100 | * orr maybe we don't? Maybe 101 | * 102 | * do we just care about runtime deps? 103 | * Do we care about runtime deps being the same as build deps? 104 | * kindof, a little. But how do we enforce that? 105 | * How do we do that. 106 | * Do we care about runtime deps of our build deps being the same as runtime deps of our other build deps? 107 | * 108 | * whaaat rabbit hole is this even. 109 | * 110 | * What are the initial constraints? 111 | * 112 | * For runtime: 113 | * - so easy, just bring it all in, require uniqueness, ignoring dev deps at every step 114 | * - if there's already a lockfile, then mark those ones as already installed and do "-changed,-notuptodate" 115 | * 116 | * For [target]: 117 | * - do essentially the same thing -- include current installs, try to have minimal changes 118 | * 119 | * For build: 120 | * - all of those runtime deps we got, figure out what build deps they want 121 | * - loop until our "pending build deps" list is done 122 | * - filter out all build dep reqs that are already satisfied by packages we've already downloaded 123 | * - run a unique query that doesn't do any transitives - just deduping build requirements 124 | * - if that fails, fallback to a non-unique query 125 | * - now that we know which build deps we want to install, loop through each one 126 | * - for its runtime deps, do a unique with -changed, including all currently installed packages 127 | * - collect all transitive build deps, add them to the list of build deps to get 128 | * 129 | * 130 | * 131 | * For npm: 132 | * - this is the last step -- npm deps can't loop back to runtime or build deps 133 | * - it's easy, because they're all runtime deps. We're solid, just run with it. 134 | * - first do a pass with uniqueness 135 | * - if it doesn't work, do a pass without uniqueness, and then post-process to remove duplicates where possible 136 | */ 137 | 138 | /* 139 | * type fullPackage = 140 | * - source: 141 | * - version: (yeah this isn't as relevant) 142 | * - runtime: 143 | * - [name]: 144 | * - (name, versionRange, realVersion) 145 | * - build: 146 | * - (name, versionRange, realVersion) 147 | * - npm: 148 | * - [name]: 149 | * - requestedVersion: 150 | * - resolvedVersion: 151 | * - dependencies: 152 | * - [name]: // only listed if this dep isn't satisfied at a higher level 153 | * (recurse) 154 | * 155 | * Currently we have: 156 | * - targets: 157 | * [target=default,ios,etc.]: 158 | * - package: 159 | * {fullPackage} 160 | * - runtimeBag: 161 | * - [name]: 162 | * {fullPackage} 163 | * 164 | * - buildDependencies: 165 | * [name:version] 166 | * - package: 167 | * {fullPackage} 168 | * - runtimeBag: 169 | * - [name]: 170 | * {fullPackage} 171 | * 172 | */ 173 | 174 | let rec addPackage = (~unique, ~previouslyInstalled, ~deep, name, realVersion, version, depsByKind, state, universe, manifest) => { 175 | CudfVersions.update(state.cudfVersions, name, realVersion, version); 176 | Hashtbl.replace(state.cache.manifests, (name, realVersion), (manifest, depsByKind)); 177 | deep ? List.iter(addToUniverse(~unique, ~previouslyInstalled, ~deep, state, universe), depsByKind.runtime) : (); 178 | let package = { 179 | ...Cudf.default_package, 180 | package: name, 181 | version, 182 | conflicts: unique ? [(name, None)] : [], 183 | installed: switch (previouslyInstalled) { | None => false | Some(table) => Hashtbl.mem(table, (name, realVersion)) }, 184 | depends: deep ? List.map(cudfDep(name ++ " (at " ++ Shared.Lockfile.viewRealVersion(realVersion) ++ ")", universe, state.cudfVersions), depsByKind.runtime) : [] 185 | }; 186 | Cudf.add_package(universe, package); 187 | } 188 | 189 | and addToUniverse = (~unique, ~previouslyInstalled, ~deep, state, universe, (name, source)) => { 190 | VersionCache.getAvailableVersions(state.cache.versions, (name, source)) |> List.iter(versionPlus => { 191 | let (realVersion, i) = switch versionPlus { 192 | | `Github(v) => (`Github(v), 1) 193 | | `Opam(v, _, i) => (`Opam(v), i) 194 | | `Npm(v, _, i) => (`Npm(v), i) 195 | }; 196 | if (!Hashtbl.mem(state.cudfVersions.lookupIntVersion, (name, realVersion))) { 197 | let (manifest, depsByKind) = getCachedManifest(state.cache.opamOverrides, state.cache.manifests, (name, versionPlus)); 198 | addPackage(~unique, ~previouslyInstalled, ~deep, name, realVersion, i, depsByKind, state, universe, manifest) 199 | } 200 | }); 201 | }; 202 | 203 | let rootName = "*root*"; 204 | 205 | let solveDeps = (~unique, ~strategy, ~previouslyInstalled=?, ~deep=true, cache, deps) => { 206 | if (deps == []) { 207 | [] 208 | } else { 209 | let universe = Cudf.empty_universe(); 210 | let state = { 211 | cache, 212 | cudfVersions: CudfVersions.init(), 213 | }; 214 | 215 | /** This is where most of the work happens, file io, network requests, etc. */ 216 | List.iter(addToUniverse(~unique, ~previouslyInstalled, ~deep, state, universe), deps); 217 | 218 | /** Here we invoke the solver! Might also take a while, but probably won't */ 219 | let cudfDeps = List.map(cudfDep(rootName, universe, state.cudfVersions), deps); 220 | switch (runSolver(~strategy, rootName, cudfDeps, universe)) { 221 | | None => failwith("Unable to resolve") 222 | | Some(packages) => { 223 | packages 224 | |> List.filter(p => p.Cudf.package != rootName) 225 | |> List.map(p => { 226 | let version = CudfVersions.getRealVersion(state.cudfVersions, p); 227 | 228 | let (manifest, depsByKind) = Hashtbl.find(state.cache.manifests, (p.Cudf.package, version)); 229 | (p.Cudf.package, version, manifest, depsByKind) 230 | }); 231 | } 232 | } 233 | }; 234 | }; 235 | 236 | let module Strategies = { 237 | let initial = "-notuptodate"; 238 | let greatestOverlap = "-changed,-notuptodate"; 239 | }; 240 | 241 | 242 | 243 | /* New style! */ 244 | 245 | let solve = (~cache, ~requested) => { 246 | solveDeps(~unique=true, ~strategy=Strategies.initial, ~deep=true, cache, requested); 247 | }; 248 | 249 | /** TODO untested */ 250 | let crawlDeps = (requested, installed) => { 251 | let depsTable = Hashtbl.create(100); 252 | installed |> List.iter(((name, version, _, deps)) => { 253 | Hashtbl.add(depsTable, name, deps) 254 | }); 255 | let traversed = Hashtbl.create(100); 256 | let rec loop = (name) => { 257 | Hashtbl.replace(traversed, name, true); 258 | Hashtbl.find(depsTable, name).Types.runtime |> List.iter(((child, _)) => { 259 | if (!Hashtbl.mem(traversed, child)) loop(child) 260 | }) 261 | }; 262 | requested |> List.iter(((name, _)) => loop(name)); 263 | installed |> List.filter(((name, _, _, _)) => Hashtbl.mem(traversed, name)) 264 | }; 265 | 266 | /** TODO untested */ 267 | let solveWithAsMuchOverlapAsPossible = (~cache, ~requested, ~current) => { 268 | let previouslyInstalled = Hashtbl.create(100); 269 | current |> List.iter(((name, version, _, _)) => Hashtbl.add(previouslyInstalled, (name, version), 1)); 270 | let installed = solveDeps( 271 | ~unique=true, 272 | ~strategy=Strategies.greatestOverlap, 273 | ~previouslyInstalled, 274 | ~deep=true, 275 | cache, 276 | requested 277 | ); 278 | crawlDeps(requested, installed) 279 | }; 280 | 281 | let makeVersionMap = installed => { 282 | let map = Hashtbl.create(100); 283 | installed |> List.iter(((name, version, _, _)) => { 284 | let current = Hashtbl.mem(map, name) ? Hashtbl.find(map, name) : []; 285 | Hashtbl.replace(map, name, [version, ...current]) 286 | }); 287 | /* TODO sort the entries... so we get the latest when possible */ 288 | map 289 | }; 290 | 291 | /** 292 | * - we allow multiple versions 293 | * - we provide a list of modules that are already installed 294 | * - if we want, we only go one level deep 295 | */ 296 | let solveLoose = (~cache, ~requested, ~current, ~deep) => { 297 | let previouslyInstalled = Hashtbl.create(100); 298 | current |> Hashtbl.iter((name, versions) => versions |> List.iter(version => Hashtbl.add(previouslyInstalled, (name, version), true))); 299 | /* current |> List.iter(({Lockfile.SolvedDep.name, version}) => Hashtbl.add(previouslyInstalled, (name, version), 1)); */ 300 | let installed = solveDeps( 301 | ~unique=true, 302 | ~strategy=Strategies.greatestOverlap, 303 | ~previouslyInstalled, 304 | ~deep, 305 | cache, 306 | requested 307 | ); 308 | if (deep) { 309 | assert(false) /* TODO */ 310 | } else { 311 | let versionMap = makeVersionMap(installed); 312 | print_endline("Build deps now"); 313 | requested |> List.iter(((name, range)) => { 314 | print_endline(name); 315 | }); 316 | print_endline("Got"); 317 | installed |> List.iter(((name, version, _, _)) => { 318 | print_endline(name); 319 | }); 320 | let touched = Hashtbl.create(100); 321 | requested |> List.iter(((name, range)) => { 322 | let versions = Hashtbl.find(versionMap, name); 323 | let matching = versions |> List.filter(real => SolveUtils.satisfies(real, range)); 324 | switch matching { 325 | | [] => failwith("Didn't actully install a matching dep for " ++ name) 326 | | [one, ..._] => Hashtbl.replace(touched, (name, one), true) 327 | } 328 | }); 329 | installed |> List.filter(((name, version, _, _)) => Hashtbl.mem(touched, (name, version))) 330 | } 331 | }; -------------------------------------------------------------------------------- /src/solve/SolveUtils.re: -------------------------------------------------------------------------------- 1 | open Opam; 2 | open Npm; 3 | open Shared; 4 | 5 | let satisfies = (realVersion, req) => { 6 | switch (req, realVersion) { 7 | | (Types.Github(user, repo, ref), `Github(user_, repo_, ref_)) when user == user_ && repo == repo_ && ref == ref_ => true 8 | | (Npm(semver), `Npm(s)) when NpmVersion.matches(semver, s) => true 9 | | (Opam(semver), `Opam(s)) when OpamVersion.matches(semver, s) => true 10 | | _ => false 11 | } 12 | }; 13 | 14 | let sortRealVersions = (a, b) => { 15 | switch (a, b) { 16 | | (`Github(a), `Github(b)) => 0 17 | | (`Npm(a), `Npm(b)) => NpmVersion.compare(a, b) 18 | | (`Opam(a), `Opam(b)) => OpamVersion.compare(a, b) 19 | | _ => 0 20 | } 21 | }; 22 | 23 | let toRealVersion = versionPlus => switch versionPlus { 24 | | `Github(x) => `Github(x) 25 | | `Npm(x, _, _) => `Npm(x) 26 | | `Opam(x, _, _) => `Opam(x) 27 | }; 28 | 29 | /** TODO(jared): This is a HACK and will hopefully be removed once we stop the 30 | * pseudo-npm opam version stuff */ 31 | let rec tryConvertingOpamFromNpm = version => { 32 | open Shared.Types; 33 | version |> Shared.GenericVersion.map(opam => { 34 | switch opam { 35 | /* yay jbuilder */ 36 | | Alpha("", Some(Num(major, Some(Alpha(".", Some(Num(minor, Some(Alpha(".", Some(Num(0, Some(Alpha("-beta", rest))))))))))))) => { 37 | Alpha("", Some(Num(major, Some(Alpha(".", Some(Num(minor, Some(Alpha("+beta", rest))))))))) 38 | } 39 | | Alpha("", Some(Num(major, Some(Alpha(".", Some(Num(minor, Some(Alpha(".", Some(Num(0, post))))))))))) => { 40 | Alpha("", Some(Num(major, Some(Alpha(".", Some(Num(minor, post))))))) 41 | } 42 | | _ => opam 43 | } 44 | }); 45 | }; 46 | 47 | let expectSuccess = (msg, v) => if (v) { () } else { failwith(msg) }; 48 | 49 | let ensureGitRepo = (source, dest) => { 50 | if (!Shared.Files.exists(dest)) { 51 | Shared.Files.mkdirp(Filename.dirname(dest)); 52 | Shared.ExecCommand.execSync(~cmd="git clone " ++ source ++ " " ++ dest, ()) |> snd |> expectSuccess("Unable to clone " ++ source) 53 | } else { 54 | Shared.ExecCommand.execSync(~cmd="cd " ++ dest ++ " && git pull", ()) |> snd |> expectSuccess("Unable tp update " ++ dest) 55 | } 56 | }; 57 | 58 | let lockDownRef = (url, ref) => { 59 | let cmd = "git ls-remote " ++ url ++ " " ++ ref; 60 | let (output, success) = Shared.ExecCommand.execSync(~cmd, ()); 61 | if (success) { 62 | switch (output) { 63 | | [] => ref 64 | | [line, ..._] => { 65 | let ref = String.split_on_char('\t', line) |> List.hd; 66 | ref 67 | } 68 | } 69 | } else { 70 | print_endline("Failed to execute git ls-remote " ++ cmd); 71 | ref 72 | } 73 | }; 74 | 75 | let rec lockDownSource = pendingSource => switch pendingSource { 76 | | Types.PendingSource.NoSource => (Types.Source.NoSource, None) 77 | | WithOpamFile(source, opamFile) => switch (lockDownSource(source)) { 78 | | (s, None) => (s, Some(opamFile)) 79 | | _ => failwith("can't nest withOpamFiles inside each other") 80 | } 81 | | Archive(url, None) => { 82 | /* print_endline("Pretending to get a checksum for " ++ url); */ 83 | (Types.Source.Archive(url, "fake checksum"), None) 84 | } 85 | | Archive(url, Some(checksum)) => (Types.Source.Archive(url, checksum), None) 86 | | GitSource(url, ref) => { 87 | let ref = Shared.Infix.(ref |? "master"); 88 | /** TODO getting HEAD */ 89 | (Types.Source.GitSource(url, lockDownRef(url, ref)), None) 90 | } 91 | | GithubSource(user, name, ref) => { 92 | let ref = Shared.Infix.(ref |? "master"); 93 | (Types.Source.GithubSource(user, name, lockDownRef("git://github.com/" ++ user ++ "/" ++ name ++ ".git", ref)), None) 94 | } 95 | | File(s) => (Types.Source.File(s), None) 96 | }; 97 | 98 | /* let lockDownWithOpam = (pending, opam) => switch opam { 99 | | Some(s) => lockDownSource(Types.PendingSource.WithOpamFile(pending, s)) 100 | | _ => lockDownSource(pending) 101 | }; */ 102 | 103 | let checkRepositories = config => { 104 | ensureGitRepo("https://github.com/esy-ocaml/esy-opam-override", config.Shared.Types.esyOpamOverrides); 105 | ensureGitRepo("https://github.com/ocaml/opam-repository", config.Shared.Types.opamRepository); 106 | }; 107 | 108 | let getCachedManifest = (opamOverrides, cache, (name, versionPlus)) => { 109 | let realVersion = toRealVersion(versionPlus); 110 | switch (Hashtbl.find(cache, (name, realVersion))) { 111 | | exception Not_found => { 112 | let manifest = switch versionPlus { 113 | | `Github(user, repo, ref) => Github.getManifest(name, user, repo, ref) 114 | /* Registry.getGithubManifest(url) */ 115 | | `Npm(version, json, _) => `PackageJson(json) 116 | | `Opam(version, path, _) => `OpamFile(OpamFile.getManifest(opamOverrides, path)) 117 | }; 118 | let depsByKind = Manifest.getDeps(manifest); 119 | let res = (manifest, depsByKind); 120 | Hashtbl.replace(cache, (name, realVersion), res); 121 | res 122 | } 123 | | x => x 124 | }; 125 | }; 126 | 127 | 128 | let runSolver = (~strategy="-notuptodate", rootName, deps, universe) => { 129 | let root = { 130 | ...Cudf.default_package, 131 | package: rootName, 132 | version: 1, 133 | depends: deps 134 | }; 135 | Cudf.add_package(universe, root); 136 | let request = { 137 | ...Cudf.default_request, 138 | install: [(root.Cudf.package, Some((`Eq, root.Cudf.version)))] 139 | }; 140 | 141 | let preamble = Cudf.default_preamble; 142 | let solution = Mccs.resolve_cudf(~verbose=false, ~timeout=5., strategy, (preamble, universe, request)); 143 | switch solution { 144 | | None => None 145 | | Some((_preamble, universe)) => { 146 | let packages = Cudf.get_packages(~filter=(p => p.Cudf.installed), universe); 147 | Some(packages) 148 | } 149 | } 150 | }; 151 | 152 | let getOpamFile = (manifest, name, version) => { 153 | switch manifest { 154 | | `PackageJson(_) => None 155 | | `OpamFile(manifest) => Some(OpamFile.toPackageJson(manifest, name, version)) 156 | } 157 | }; 158 | -------------------------------------------------------------------------------- /src/solve/VersionCache.re: -------------------------------------------------------------------------------- 1 | open Opam; 2 | open Npm; 3 | open Shared; 4 | 5 | open SolveUtils; 6 | 7 | 8 | type t = { 9 | config: Types.config, 10 | availableNpmVersions: Hashtbl.t(string, list((Types.npmConcrete, Yojson.Basic.json))), 11 | availableOpamVersions: Hashtbl.t(string, list((Types.opamConcrete, OpamFile.thinManifest))), 12 | }; 13 | 14 | let getAvailableVersions = (cache, (name, source)) => { 15 | switch source { 16 | | Types.Github(user, repo, ref) => { 17 | [`Github(user, repo, ref)] 18 | } 19 | | Npm(semver) => { 20 | if (!Hashtbl.mem(cache.availableNpmVersions, name)) { 21 | Hashtbl.replace(cache.availableNpmVersions, name, Npm.Registry.getFromNpmRegistry(name)); 22 | }; 23 | let available = Hashtbl.find(cache.availableNpmVersions, name); 24 | available 25 | |> List.sort(((va, _), (vb, _)) => NpmVersion.compare(va, vb)) 26 | |> List.mapi((i, (v, j)) => (v, j, i)) 27 | |> List.filter(((version, json, i)) => NpmVersion.matches(semver, version)) 28 | |> List.map(((version, json, i)) => `Npm(version, json, i)); 29 | } 30 | 31 | | Opam(semver) => { 32 | if (!Hashtbl.mem(cache.availableOpamVersions, name)) { 33 | Hashtbl.replace(cache.availableOpamVersions, name, Opam.Registry.getFromOpamRegistry(cache.config, name)) 34 | }; 35 | let available = Hashtbl.find(cache.availableOpamVersions, name) 36 | |> List.sort(((va, _), (vb, _)) => OpamVersion.compare(va, vb)) 37 | |> List.mapi((i, (v, j)) => (v, j, i)); 38 | let matched = available 39 | |> List.filter(((version, path, i)) => OpamVersion.matches(semver, version)); 40 | let matched = if (matched == []) { 41 | available |> List.filter(((version, path, i)) => OpamVersion.matches(tryConvertingOpamFromNpm(semver), version)) 42 | } else { 43 | matched 44 | }; 45 | matched |> List.map(((version, path, i)) => `Opam(version, path, i)); 46 | } 47 | | _ => [] 48 | } 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /src/solve/jbuild: -------------------------------------------------------------------------------- 1 | (jbuild_version 1) 2 | 3 | (library 4 | ((name solve) 5 | (preprocess (pps (ppx_deriving_yojson testre-ppx ppx_driver))) 6 | (libraries (shared opam npm str curl cudf yaml ppx_deriving_yojson.runtime mccs yojson opam-file-format)))) 7 | 8 | -------------------------------------------------------------------------------- /test/Test.re: -------------------------------------------------------------------------------- 1 | 2 | let module Suites = { 3 | include Fetch; 4 | include Solve; 5 | include Npm.ParseNpm; 6 | }; 7 | 8 | print_endline("Running tests"); 9 | TestRe.report(); 10 | -------------------------------------------------------------------------------- /test/jbuild: -------------------------------------------------------------------------------- 1 | (jbuild_version 1) 2 | 3 | (executable 4 | ((name Test) 5 | (libraries (fetch solve TestRe)) 6 | )) 7 | 8 | --------------------------------------------------------------------------------