astral-pubgrub-0.3.3/.cargo_vcs_info.json0000644000000001360000000000100137710ustar { "git": { "sha1": "f19b7841bfbdc49b32964f84054d35720999438a" }, "path_in_vcs": "" }astral-pubgrub-0.3.3/Cargo.lock0000644000001046700000000000100117540ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", "windows-sys", ] [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "astral-pubgrub" version = "0.3.3" dependencies = [ "astral-version-ranges", "codspeed-criterion-compat", "env_logger", "indexmap", "log", "priority-queue", "proptest", "ron", "rustc-hash 2.1.1", "serde", "thiserror 2.0.17", "varisat", ] [[package]] name = "astral-version-ranges" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31979bc305610246d78ac01d63467a12d8454c6e3b8b22b5811d343a1198dfbb" dependencies = [ "proptest", "serde", "smallvec", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3b847e05a34be5c38f3f2a5052178a3bd32e6b5702f3ea775efde95c483a539" dependencies = [ "anyhow", "cc", "colored", "getrandom 0.2.15", "glob", "libc", "nix", "serde", "serde_json", "statrs", ] [[package]] name = "codspeed-criterion-compat" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a0e2a53beb18dec493ec133f226e0d35e8bb2fdc638ccf7351696eabee416c" dependencies = [ "clap", "codspeed", "codspeed-criterion-compat-walltime", "colored", "regex", ] [[package]] name = "codspeed-criterion-compat-walltime" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f652d6e6d40bba0f5c244744db94d92a26b7f083df18692df88fb0772f1c793" dependencies = [ "anes", "cast", "ciborium", "clap", "codspeed", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", "windows-sys", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.4+wasi-0.2.4", ] [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "half" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "is-terminal" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "ordered-float" version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] [[package]] name = "partial_ref" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f728bc9b1479656e40cba507034904a8c44027c0efdbbaf6a4bdc5f2d3a910c" dependencies = [ "partial_ref_derive", ] [[package]] name = "partial_ref_derive" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e1d2cb5b898b5a5342e994e0d0c367dbfe69cbf717cd307045ec9fb057581" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "priority-queue" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" dependencies = [ "equivalent", "indexmap", "serde", ] [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" dependencies = [ "bit-set", "bit-vec", "bitflags", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", "unarray", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", ] [[package]] name = "rand_xorshift" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ "rand_core", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ron" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ "bitflags", "once_cell", "serde", "serde_derive", "typeid", "unicode-ident", ] [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rusty-fork" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn 2.0.96", ] [[package]] name = "serde_json" version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa 1.0.14", "memchr", "ryu", "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] [[package]] name = "statrs" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" dependencies = [ "approx", "num-traits", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "unicode-xid", ] [[package]] name = "tempfile" version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", "getrandom 0.2.15", "once_cell", "rustix", "windows-sys", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.96", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn 2.0.96", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "typeid" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "varisat" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebe609851d1e9196674ac295f656bd8601200a1077343d22b345013497807caf" dependencies = [ "anyhow", "itoa 0.4.8", "leb128", "log", "ordered-float", "partial_ref", "rustc-hash 1.1.0", "serde", "thiserror 1.0.69", "varisat-checker", "varisat-dimacs", "varisat-formula", "varisat-internal-macros", "varisat-internal-proof", "vec_mut_scan", ] [[package]] name = "varisat-checker" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135c977c5913ed6e98f6b81b8e4d322211303b7d40dae773caef7ad1de6c763b" dependencies = [ "anyhow", "log", "partial_ref", "rustc-hash 1.1.0", "smallvec", "thiserror 1.0.69", "varisat-dimacs", "varisat-formula", "varisat-internal-proof", ] [[package]] name = "varisat-dimacs" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1dee4e21be1f04c0a939f7ae710cced47233a578de08a1b3c7d50848402636" dependencies = [ "anyhow", "itoa 0.4.8", "thiserror 1.0.69", "varisat-formula", ] [[package]] name = "varisat-formula" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395c5543b9bfd9076d6d3af49d6c34a4b91b0b355998c0a5ec6ed7265d364520" [[package]] name = "varisat-internal-macros" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "602ece773543d066aa7848455486c6c0422a3f214da7a2b899100f3c4f12408d" dependencies = [ "proc-macro2", "quote", "regex", "syn 1.0.109", "synstructure", ] [[package]] name = "varisat-internal-proof" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6163bb7bc9018af077b76d64f976803d141c36a27d640f1437dddc4fd527d207" dependencies = [ "anyhow", "varisat-formula", ] [[package]] name = "vec_mut_scan" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ed610a8d5e63d9c0e31300e8fdb55104c5f21e422743a9dc74848fa8317fd2" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.4+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen" version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.96", ] astral-pubgrub-0.3.3/Cargo.toml0000644000000061750000000000100120000ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" name = "astral-pubgrub" version = "0.3.3" authors = [ "Matthieu Pizenberg ", "Alex Tokarev ", "Jacob Finkelman ", ] build = false include = [ "Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples/**", "benches/**", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "PubGrub version solving algorithm" readme = "README.md" keywords = [ "dependency", "pubgrub", "semver", "solver", "version", ] categories = ["algorithms"] license = "MPL-2.0" repository = "https://github.com/astral-sh/pubgrub" [features] serde = [ "dep:serde", "version-ranges/serde", ] [lib] name = "pubgrub" path = "src/lib.rs" [[example]] name = "branching_error_reporting" path = "examples/branching_error_reporting.rs" [[example]] name = "caching_dependency_provider" path = "examples/caching_dependency_provider.rs" [[example]] name = "doc_interface" path = "examples/doc_interface.rs" [[example]] name = "doc_interface_error" path = "examples/doc_interface_error.rs" [[example]] name = "doc_interface_semantic" path = "examples/doc_interface_semantic.rs" [[example]] name = "linear_error_reporting" path = "examples/linear_error_reporting.rs" [[example]] name = "unsat_root_message_no_version" path = "examples/unsat_root_message_no_version.rs" [[test]] name = "examples" path = "tests/examples.rs" [[test]] name = "proptest" path = "tests/proptest.rs" [[test]] name = "sat_dependency_provider" path = "tests/sat_dependency_provider.rs" [[test]] name = "tests" path = "tests/tests.rs" [[bench]] name = "backtracking" path = "benches/backtracking.rs" harness = false [[bench]] name = "large_case" path = "benches/large_case.rs" harness = false required-features = ["serde"] [[bench]] name = "sudoku" path = "benches/sudoku.rs" harness = false [dependencies.indexmap] version = "2.7.0" [dependencies.log] version = "0.4.27" [dependencies.priority-queue] version = "2.3.1" [dependencies.rustc-hash] version = "^2.1.1" [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dependencies.thiserror] version = "2.0" [dependencies.version-ranges] version = "0.1.0" package = "astral-version-ranges" [dev-dependencies.criterion] version = "4.0.1" package = "codspeed-criterion-compat" [dev-dependencies.env_logger] version = "0.11.6" [dev-dependencies.proptest] version = "1.6.0" [dev-dependencies.ron] version = "0.12.0" [dev-dependencies.varisat] version = "0.2.2" [dev-dependencies.version-ranges] version = "0.1.0" features = ["proptest"] package = "astral-version-ranges" astral-pubgrub-0.3.3/Cargo.toml.orig000064400000000000000000000030651046102023000154540ustar 00000000000000# SPDX-License-Identifier: MPL-2.0 [workspace] members = ["version-ranges"] [package] name = "astral-pubgrub" version = "0.3.3" authors = [ "Matthieu Pizenberg ", "Alex Tokarev ", "Jacob Finkelman ", ] edition = "2024" description = "PubGrub version solving algorithm" readme = "README.md" repository = "https://github.com/astral-sh/pubgrub" license = "MPL-2.0" keywords = ["dependency", "pubgrub", "semver", "solver", "version"] categories = ["algorithms"] include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples/**", "benches/**"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "pubgrub" [dependencies] indexmap = "2.7.0" # for debug logs in tests log = "0.4.27" priority-queue = "2.3.1" rustc-hash = "^2.1.1" serde = { version = "1.0", features = ["derive"], optional = true } thiserror = "2.0" version-ranges = { version = "0.1.0", package = "astral-version-ranges", path = "version-ranges" } [dev-dependencies] criterion = { version = "4.0.1", package = "codspeed-criterion-compat" } env_logger = "0.11.6" proptest = "1.6.0" ron = "0.12.0" varisat = "0.2.2" version-ranges = { version = "0.1.0", path = "version-ranges", package = "astral-version-ranges", features = ["proptest"] } [features] serde = ["dep:serde", "version-ranges/serde"] [[bench]] name = "backtracking" harness = false [[bench]] name = "large_case" harness = false required-features = ["serde"] [[bench]] name = "sudoku" harness = false astral-pubgrub-0.3.3/LICENSE000064400000000000000000000405251046102023000135740ustar 00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. astral-pubgrub-0.3.3/README.md000064400000000000000000000062001046102023000140360ustar 00000000000000# PubGrub version solving algorithm ![license](https://img.shields.io/crates/l/pubgrub.svg) [![crates.io](https://img.shields.io/crates/v/pubgrub.svg?logo=rust)][crates] [![docs.rs](https://img.shields.io/badge/docs.rs-pubgrub-yellow)][docs] [![guide](https://img.shields.io/badge/guide-pubgrub-pink?logo=read-the-docs)][guide] Version solving consists in efficiently finding a set of packages and versions that satisfy all the constraints of a given project dependencies. In addition, when that is not possible, PubGrub tries to provide a very human-readable and clear explanation as to why that failed. The [introductory blog post about PubGrub][medium-pubgrub] presents one such example of failure explanation: ```txt Because dropdown >=2.0.0 depends on icons >=2.0.0 and root depends on icons <2.0.0, dropdown >=2.0.0 is forbidden. And because menu >=1.1.0 depends on dropdown >=2.0.0, menu >=1.1.0 is forbidden. And because menu <1.1.0 depends on dropdown >=1.0.0 <2.0.0 which depends on intl <4.0.0, every version of menu requires intl <4.0.0. So, because root depends on both menu >=1.0.0 and intl >=5.0.0, version solving failed. ``` This pubgrub crate provides a Rust implementation of PubGrub. It is generic and works for any type of dependency system as long as packages (P) and versions (V) implement the provided `Package` and `Version` traits. ## Using the pubgrub crate A [guide][guide] with both high-level explanations and in-depth algorithm details is available online. The [API documentation is available on docs.rs][docs]. A version of the [API docs for the unreleased functionality][docs-dev] from `dev` branch is also accessible for convenience. ## Contributing Discussion and development happens here on GitHub and on our [Zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/260232-t-cargo.2FPubGrub). Please join in! Remember to always be considerate of others, who may have different native languages, cultures and experiences. We want everyone to feel welcomed, let us know with a private message on Zulip if you don't feel that way. ## PubGrub PubGrub is a version solving algorithm, written in 2018 by Natalie Weizenbaum for the Dart package manager. It is supposed to be very fast and to explain errors more clearly than the alternatives. An introductory blog post was [published on Medium][medium-pubgrub] by its author. The detailed explanation of the algorithm is [provided on GitHub][github-pubgrub], and complemented by the ["Internals" section of our guide][guide-internals]. The foundation of the algorithm is based on ASP (Answer Set Programming), and a book called "[Answer Set Solving in Practice][potassco-book]" by Martin Gebser, Roland Kaminski, Benjamin Kaufmann and Torsten Schaub. [crates]: https://crates.io/crates/pubgrub [guide]: https://pubgrub-rs-guide.pages.dev [guide-internals]: https://pubgrub-rs-guide.pages.dev/internals/intro.html [docs]: https://docs.rs/pubgrub [docs-dev]: https://pubgrub-rs.github.io/pubgrub/pubgrub/ [medium-pubgrub]: https://medium.com/@nex3/pubgrub-2fb6470504f [github-pubgrub]: https://github.com/dart-lang/pub/blob/master/doc/solver.md [potassco-book]: https://potassco.org/book/ astral-pubgrub-0.3.3/benches/backtracking.rs000064400000000000000000000103421046102023000171610ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! This bench monitors the performance of backtracking and term intersection. //! //! Dependencies are constructed in a way that all versions need to be tested before finding a solution. use criterion::*; use pubgrub::OfflineDependencyProvider; use version_ranges::Ranges; /// This benchmark is a simplified reproduction of one of the patterns found in the `solana-*` crates from Cargo: /// * `solana-archiver-lib v1.1.12` depends on many layers of other solana crates with req `>= 1.1.12`. /// * each version `1.x.y` higher than `1.5.15` of a solana crate depends on other solana crates with req `= 1.x.y`. /// * `solana-crate-features` depends on `cc` with the `num_cpus` feature, which doesn't exist in recent versions of `cc`. fn backtracking_singletons(c: &mut Criterion, package_count: u32, version_count: u32) { let mut dependency_provider = OfflineDependencyProvider::>::new(); dependency_provider.add_dependencies(0u32, 0u32, [(1u32, Ranges::full())]); dependency_provider.add_dependencies(1u32, 0u32, []); for n in 1..package_count { for v in 1..version_count { dependency_provider.add_dependencies(n, v, [(n + 1, Ranges::singleton(v))]); } } c.bench_function("backtracking_singletons", |b| { b.iter(|| { let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); }) }); } /// This benchmark is a simplified reproduction of one of the patterns found in the `solana-*` crates from Cargo: /// * `solana-archiver-lib v1.1.12` depends on many layers of other solana crates with req `>= 1.1.12`. /// * `solana-archiver-lib v1.1.12` also depends on `ed25519-dalek v1.0.0-pre.3`. /// * each version `1.x.y` higher than `1.5.15` of a solana crate depends on other solana crates with req `= 1.x.y`. /// * `solana-crate-features >= 1.2.17` depends on `ed25519-dalek v1.0.0-pre.4` or a higher incompatible version. fn backtracking_disjoint_versions(c: &mut Criterion, package_count: u32, version_count: u32) { let mut dependency_provider = OfflineDependencyProvider::>::new(); let root_deps = [(1u32, Ranges::full()), (u32::MAX, Ranges::singleton(0u32))]; dependency_provider.add_dependencies(0u32, 0u32, root_deps); dependency_provider.add_dependencies(1u32, 0u32, []); for n in 1..package_count { for v in 1..version_count { dependency_provider.add_dependencies(n, v, [(n + 1, Ranges::singleton(v))]); } } for v in 1..version_count { dependency_provider.add_dependencies(package_count, v, [(u32::MAX, Ranges::singleton(v))]); } for v in 0..version_count { dependency_provider.add_dependencies(u32::MAX, v, []); } c.bench_function("backtracking_disjoint_versions", |b| { b.iter(|| { let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); }) }); } /// This benchmark is a simplified reproduction of one of the patterns found in the `solana-*` crates from Cargo: /// * `solana-archiver-lib v1.1.12` depends on many layers of other solana crates with req `>= 1.1.12`. /// * each version `1.x.y` lower than `1.5.14` of a solana crate depends on other solana crates with req `>= 1.x.y`. /// * `solana-crate-features` depends on `cc` with the `num_cpus` feature, which doesn't exist in recent versions of `cc`. fn backtracking_ranges(c: &mut Criterion, package_count: u32, version_count: u32) { let mut dependency_provider = OfflineDependencyProvider::>::new(); dependency_provider.add_dependencies(0u32, 0u32, [(1u32, Ranges::full())]); dependency_provider.add_dependencies(1u32, 0u32, []); for n in 1..package_count { for v in 1..version_count { let r = Ranges::higher_than(version_count - v); dependency_provider.add_dependencies(n, v, [(n + 1, r)]); } } c.bench_function("backtracking_ranges", |b| { b.iter(|| { let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); }) }); } fn bench_group(c: &mut Criterion) { backtracking_singletons(c, 100, 500); backtracking_disjoint_versions(c, 300, 200); backtracking_ranges(c, 5, 200); } criterion_group!(benches, bench_group); criterion_main!(benches); astral-pubgrub-0.3.3/benches/large_case.rs000064400000000000000000000030621046102023000166240ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use std::time::Duration; use criterion::*; use serde::de::Deserialize; use pubgrub::{OfflineDependencyProvider, Package, Range, SemanticVersion, VersionSet, resolve}; fn bench<'a, P: Package + Deserialize<'a>, VS: VersionSet + Deserialize<'a>>( b: &mut Bencher, case: &'a str, ) where ::V: Deserialize<'a>, { let dependency_provider: OfflineDependencyProvider = ron::de::from_str(case).unwrap(); b.iter(|| { for p in dependency_provider.packages() { for n in dependency_provider.versions(p).unwrap() { let _ = resolve(&dependency_provider, p.clone(), n.clone()); } } }); } fn bench_nested(c: &mut Criterion) { let mut group = c.benchmark_group("large_cases"); group.measurement_time(Duration::from_secs(20)); for case in std::fs::read_dir("test-examples").unwrap() { let case = case.unwrap().path(); let name = case.file_name().unwrap().to_string_lossy(); let data = std::fs::read_to_string(&case).unwrap(); if name.ends_with("u16_NumberVersion.ron") || name.ends_with("u16_u32.ron") { group.bench_function(name, |b| { bench::>(b, &data); }); } else if name.ends_with("str_SemanticVersion.ron") { group.bench_function(name, |b| { bench::<&str, Range>(b, &data); }); } } group.finish(); } criterion_group!(benches, bench_nested); criterion_main!(benches); astral-pubgrub-0.3.3/benches/sudoku.rs000064400000000000000000000121061046102023000160500ustar 00000000000000//! A sudoku solver. //! //! Uses `Arc` for being closer to real versions. // SPDX-License-Identifier: MPL-2.0 use pubgrub::{OfflineDependencyProvider, Range, resolve}; use std::fmt; use std::sync::Arc; use version_ranges::Ranges; use criterion::*; /// The size of a box in the board. const BOARD_BASE: usize = 3; /// The size of the board. const BOARD_SIZE: usize = BOARD_BASE * BOARD_BASE; type DP = OfflineDependencyProvider>>; #[derive(Clone, Debug, Eq, Hash, PartialEq)] enum SudokuPackage { /// Add all known fields. Root, /// Version is the value of the cell. Cell { row: usize, col: usize }, } impl fmt::Display for SudokuPackage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { SudokuPackage::Root => f.write_str("root"), SudokuPackage::Cell { row, col } => { write!(f, "({col}, {row})") } } } } fn from_board(b: &str) -> Vec<(SudokuPackage, Range>)> { let mut out = vec![]; for (row, line) in b .trim() .lines() .map(str::trim) .filter(|l| !l.starts_with('-')) .enumerate() { for (col, val) in line .split_ascii_whitespace() .filter(|c| !c.starts_with('|')) .enumerate() { if let Some(val) = val.chars().next().unwrap().to_digit(10) { out.push(( SudokuPackage::Cell { row: row + 1, col: col + 1, }, Range::singleton(val as usize), )); } } } out } /// Encode all the exclusions from assigning a cell to a value fn encode_constraints( dependency_provider: &mut OfflineDependencyProvider>>, ) { for row in 1..=BOARD_SIZE { for col in 1..=BOARD_SIZE { for val in 1..=BOARD_SIZE { let mut deps = vec![]; // A number may only occur once in a row for row_ in 1..=BOARD_SIZE { if row_ == row { continue; } deps.push(( SudokuPackage::Cell { row: row_, col }, Range::singleton(Arc::new(val)).complement(), )) } // A number may only occur once in a col for col_ in 1..=BOARD_SIZE { if col_ == col { continue; } deps.push(( SudokuPackage::Cell { row, col: col_ }, Range::singleton(Arc::new(val)).complement(), )) } // A number may only occur once in a box let box_base_row = row - ((row - 1) % BOARD_BASE); let box_base_col = col - ((col - 1) % BOARD_BASE); for row_ in box_base_row..box_base_row + BOARD_BASE { for col_ in box_base_col..box_base_col + BOARD_BASE { if col_ == col && row_ == row { continue; } deps.push(( SudokuPackage::Cell { row: row_, col: col_, }, Range::singleton(Arc::new(val)).complement(), )) } } let name = SudokuPackage::Cell { row, col }; dependency_provider.add_dependencies(name, val, deps) } } } } fn solve(c: &mut Criterion, board: Vec<(SudokuPackage, Ranges>)>, case: &str) { let mut dependency_provider = DP::new(); encode_constraints(&mut dependency_provider); dependency_provider.add_dependencies(SudokuPackage::Root, Arc::new(1usize), board); c.bench_function(case, |b| { b.iter(|| { let _ = resolve(&dependency_provider, SudokuPackage::Root, Arc::new(1usize)); }) }); } fn bench_solve(c: &mut Criterion) { let easy = from_board( r#" 5 3 _ | _ 7 _ | _ _ _ 6 _ _ | 1 9 5 | _ _ _ _ 9 8 | _ _ _ | _ 6 _ -------+-------+------- 8 5 9 | _ 6 1 | 4 2 3 4 2 6 | 8 5 3 | 7 9 1 7 1 3 | 9 2 4 | 8 5 6 -------+-------+------- _ 6 _ | _ _ _ | 2 8 _ _ _ _ | 4 1 9 | _ _ 5 _ _ _ | _ 8 6 | 1 7 9"#, ); let hard = from_board( r#" 5 3 _ | _ 7 _ | _ _ _ 6 _ _ | 1 9 5 | _ _ _ _ 9 8 | _ _ _ | _ 6 _ -------+-------+------- 8 _ _ | _ 6 _ | _ _ 3 4 _ _ | 8 _ 3 | _ _ 1 7 _ _ | _ 2 _ | _ _ 6 -------+-------+------- _ 6 _ | _ _ _ | 2 8 _ _ _ _ | 4 1 9 | _ _ 5 _ _ _ | _ 8 _ | _ 7 9"#, ); solve(c, easy, "sudoku-easy"); solve(c, hard, "sudoku-hard"); } criterion_group!(benches, bench_solve); criterion_main!(benches); astral-pubgrub-0.3.3/examples/branching_error_reporting.rs000064400000000000000000000045051046102023000222060ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use pubgrub::{ DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, resolve, }; type SemVS = Ranges; // https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] // root 1.0.0 depends on foo ^1.0.0 dependency_provider.add_dependencies( "root", (1, 0, 0), [("foo", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0)))], ); #[rustfmt::skip] // foo 1.0.0 depends on a ^1.0.0 and b ^1.0.0 dependency_provider.add_dependencies( "foo", (1, 0, 0), [ ("a", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), ("b", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), ], ); #[rustfmt::skip] // foo 1.1.0 depends on x ^1.0.0 and y ^1.0.0 dependency_provider.add_dependencies( "foo", (1, 1, 0), [ ("x", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), ("y", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), ], ); #[rustfmt::skip] // a 1.0.0 depends on b ^2.0.0 dependency_provider.add_dependencies( "a", (1, 0, 0), [("b", Ranges::from_range_bounds((2, 0, 0)..(3, 0, 0)))], ); // b 1.0.0 and 2.0.0 have no dependencies. dependency_provider.add_dependencies("b", (1, 0, 0), []); dependency_provider.add_dependencies("b", (2, 0, 0), []); #[rustfmt::skip] // x 1.0.0 depends on y ^2.0.0. dependency_provider.add_dependencies( "x", (1, 0, 0), [("y", Ranges::from_range_bounds((2, 0, 0)..(3, 0, 0)))], ); // y 1.0.0 and 2.0.0 have no dependencies. dependency_provider.add_dependencies("y", (1, 0, 0), []); dependency_provider.add_dependencies("y", (2, 0, 0), []); // Run the algorithm. match resolve(&dependency_provider, "root", (1, 0, 0)) { Ok(sol) => println!("{sol:?}"), Err(PubGrubError::NoSolution(mut derivation_tree)) => { derivation_tree.collapse_no_versions(); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); std::process::exit(1); } Err(err) => panic!("{err:?}"), }; } astral-pubgrub-0.3.3/examples/caching_dependency_provider.rs000064400000000000000000000062371046102023000224610ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use std::cell::RefCell; use pubgrub::{ Dependencies, DependencyProvider, OfflineDependencyProvider, PackageResolutionStatistics, Ranges, resolve, }; type NumVS = Ranges; // An example implementing caching dependency provider that will // store queried dependencies in memory and check them before querying more from remote. struct CachingDependencyProvider { remote_dependencies: DP, cached_dependencies: RefCell>, } impl CachingDependencyProvider { pub fn new(remote_dependencies_provider: DP) -> Self { CachingDependencyProvider { remote_dependencies: remote_dependencies_provider, cached_dependencies: RefCell::new(OfflineDependencyProvider::new()), } } } impl> DependencyProvider for CachingDependencyProvider { // Caches dependencies if they were already queried fn get_dependencies( &self, package: &DP::P, version: &DP::V, ) -> Result, DP::Err> { let mut cache = self.cached_dependencies.borrow_mut(); match cache.get_dependencies(package, version) { Ok(Dependencies::Unavailable(_)) => { let dependencies = self.remote_dependencies.get_dependencies(package, version); match dependencies { Ok(Dependencies::Available(dependencies)) => { cache.add_dependencies( package.clone(), version.clone(), dependencies.clone(), ); Ok(Dependencies::Available(dependencies)) } Ok(Dependencies::Unavailable(reason)) => Ok(Dependencies::Unavailable(reason)), error @ Err(_) => error, } } Ok(dependencies) => Ok(dependencies), Err(_) => unreachable!(), } } fn choose_version(&self, package: &DP::P, ranges: &DP::VS) -> Result, DP::Err> { self.remote_dependencies.choose_version(package, ranges) } type Priority = DP::Priority; fn prioritize( &self, package: &Self::P, range: &Self::VS, package_statistics: &PackageResolutionStatistics, ) -> Self::Priority { self.remote_dependencies .prioritize(package, range, package_statistics) } type Err = DP::Err; type P = DP::P; type V = DP::V; type VS = DP::VS; type M = DP::M; } fn main() { // Simulating remote provider locally. let mut remote_dependencies_provider = OfflineDependencyProvider::<&str, NumVS>::new(); // Add dependencies as needed. Here only root package is added. remote_dependencies_provider.add_dependencies("root", 1u32, Vec::new()); let caching_dependencies_provider = CachingDependencyProvider::new(remote_dependencies_provider); let solution = resolve(&caching_dependencies_provider, "root", 1u32); println!("Solution: {solution:?}"); } astral-pubgrub-0.3.3/examples/doc_interface.rs000064400000000000000000000015431046102023000175350ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use pubgrub::{OfflineDependencyProvider, Ranges, resolve}; type NumVS = Ranges; // `root` depends on `menu` and `icons` // `menu` depends on `dropdown` // `dropdown` depends on `icons` // `icons` has no dependency #[rustfmt::skip] fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); dependency_provider.add_dependencies( "root", 1u32, [("menu", Ranges::full()), ("icons", Ranges::full())], ); dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Ranges::full())]); dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Ranges::full())]); dependency_provider.add_dependencies("icons", 1u32, []); // Run the algorithm. let solution = resolve(&dependency_provider, "root", 1u32); println!("Solution: {solution:?}"); } astral-pubgrub-0.3.3/examples/doc_interface_error.rs000064400000000000000000000061011046102023000207410ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use pubgrub::{ DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, resolve, }; type SemVS = Ranges; // `root` depends on `menu`, `icons 1.0.0` and `intl 5.0.0` // `menu 1.0.0` depends on `dropdown < 2.0.0` // `menu >= 1.1.0` depends on `dropdown >= 2.0.0` // `dropdown 1.8.0` depends on `intl 3.0.0` // `dropdown >= 2.0.0` depends on `icons 2.0.0` // `icons` has no dependency // `intl` has no dependency #[rustfmt::skip] fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); // Direct dependencies: menu and icons. dependency_provider.add_dependencies("root", (1, 0, 0), [ ("menu", Ranges::full()), ("icons", Ranges::singleton((1, 0, 0))), ("intl", Ranges::singleton((5, 0, 0))), ]); // Dependencies of the menu lib. dependency_provider.add_dependencies("menu", (1, 0, 0), [ ("dropdown", Ranges::from_range_bounds(..(2, 0, 0))), ]); dependency_provider.add_dependencies("menu", (1, 1, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 2, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 3, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 4, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 5, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); // Dependencies of the dropdown lib. dependency_provider.add_dependencies("dropdown", (1, 8, 0), [ ("intl", Ranges::singleton((3, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 0, 0), [ ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 1, 0), [ ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 2, 0), [ ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 3, 0), [ ("icons", Ranges::singleton((2, 0, 0))), ]); // Icons have no dependencies. dependency_provider.add_dependencies("icons", (1, 0, 0), []); dependency_provider.add_dependencies("icons", (2, 0, 0), []); // Intl have no dependencies. dependency_provider.add_dependencies("intl", (3, 0, 0), []); dependency_provider.add_dependencies("intl", (4, 0, 0), []); dependency_provider.add_dependencies("intl", (5, 0, 0), []); // Run the algorithm. match resolve(&dependency_provider, "root", (1, 0, 0)) { Ok(sol) => println!("{sol:?}"), Err(PubGrubError::NoSolution(mut derivation_tree)) => { derivation_tree.collapse_no_versions(); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); } Err(err) => panic!("{err:?}"), }; } astral-pubgrub-0.3.3/examples/doc_interface_semantic.rs000064400000000000000000000053031046102023000214160ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use pubgrub::{ DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, resolve, }; type SemVS = Ranges; // `root` depends on `menu` and `icons 1.0.0` // `menu 1.0.0` depends on `dropdown < 2.0.0` // `menu >= 1.1.0` depends on `dropdown >= 2.0.0` // `dropdown 1.8.0` has no dependency // `dropdown >= 2.0.0` depends on `icons 2.0.0` // `icons` has no dependency #[rustfmt::skip] fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); // Direct dependencies: menu and icons. dependency_provider.add_dependencies("root", (1, 0, 0), [ ("menu", Ranges::full()), ("icons", Ranges::singleton((1, 0, 0))), ]); // Dependencies of the menu lib. dependency_provider.add_dependencies("menu", (1, 0, 0), [ ("dropdown", Ranges::from_range_bounds(..(2, 0, 0))), ]); dependency_provider.add_dependencies("menu", (1, 1, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 2, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 3, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 4, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 5, 0), [ ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); // Dependencies of the dropdown lib. dependency_provider.add_dependencies("dropdown", (1, 8, 0), []); dependency_provider.add_dependencies("dropdown", (2, 0, 0), [ ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 1, 0), [ ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 2, 0), [ ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 3, 0), [ ("icons", Ranges::singleton((2, 0, 0))), ]); // Icons has no dependency. dependency_provider.add_dependencies("icons", (1, 0, 0), []); dependency_provider.add_dependencies("icons", (2, 0, 0), []); // Run the algorithm. match resolve(&dependency_provider, "root", (1, 0, 0)) { Ok(sol) => println!("{sol:?}"), Err(PubGrubError::NoSolution(mut derivation_tree)) => { derivation_tree.collapse_no_versions(); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); } Err(err) => panic!("{err:?}"), }; } astral-pubgrub-0.3.3/examples/linear_error_reporting.rs000064400000000000000000000032611046102023000215230ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use pubgrub::{ DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, resolve, }; type SemVS = Ranges; // https://github.com/dart-lang/pub/blob/master/doc/solver.md#linear-error-reporting fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] // root 1.0.0 depends on foo ^1.0.0 and baz ^1.0.0 dependency_provider.add_dependencies( "root", (1, 0, 0), [ ("foo", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), ("baz", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), ], ); #[rustfmt::skip] // foo 1.0.0 depends on bar ^2.0.0 dependency_provider.add_dependencies( "foo", (1, 0, 0), [("bar", Ranges::from_range_bounds((2, 0, 0)..(3, 0, 0)))], ); #[rustfmt::skip] // bar 2.0.0 depends on baz ^3.0.0 dependency_provider.add_dependencies( "bar", (2, 0, 0), [("baz", Ranges::from_range_bounds((3, 0, 0)..(4, 0, 0)))], ); // baz 1.0.0 and 3.0.0 have no dependencies dependency_provider.add_dependencies("baz", (1, 0, 0), []); dependency_provider.add_dependencies("baz", (3, 0, 0), []); // Run the algorithm. match resolve(&dependency_provider, "root", (1, 0, 0)) { Ok(sol) => println!("{sol:?}"), Err(PubGrubError::NoSolution(mut derivation_tree)) => { derivation_tree.collapse_no_versions(); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); std::process::exit(1); } Err(err) => panic!("{err:?}"), }; } astral-pubgrub-0.3.3/examples/unsat_root_message_no_version.rs000064400000000000000000000217431046102023000231160ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use std::fmt::{self, Display}; use pubgrub::{ DefaultStringReporter, Derived, External, Map, OfflineDependencyProvider, PubGrubError, Ranges, ReportFormatter, Reporter, SemanticVersion, Term, resolve, }; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Package { Root, Package(String), } impl Display for Package { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Package::Root => write!(f, "root"), Package::Package(name) => write!(f, "{name}"), } } } #[derive(Debug, Default)] struct CustomReportFormatter; impl ReportFormatter, String> for CustomReportFormatter { type Output = String; fn format_terms(&self, terms: &Map>>) -> String { let terms_vec: Vec<_> = terms.iter().collect(); match terms_vec.as_slice() { [] => "version solving failed".into(), [(package @ Package::Root, Term::Positive(_))] => { format!("{package} is forbidden") } [(package @ Package::Root, Term::Negative(_))] => { format!("{package} is mandatory") } [(package @ Package::Package(_), Term::Positive(ranges))] => { format!("{package} {ranges} is forbidden") } [(package @ Package::Package(_), Term::Negative(ranges))] => { format!("{package} {ranges} is mandatory") } [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => { External::<_, _, String>::FromDependencyOf(p1, r1.clone(), p2, r2.clone()) .to_string() } [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => { External::<_, _, String>::FromDependencyOf(p2, r2.clone(), p1, r1.clone()) .to_string() } slice => { let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{p} {t}")).collect(); str_terms.join(", ") + " are incompatible" } } } fn format_external( &self, external: &External, String>, ) -> String { match external { External::NotRoot(package, version) => { format!("we are solving dependencies of {package} {version}") } External::NoVersions(package, set) => { if set == &Ranges::full() { format!("there is no available version for {package}") } else { format!("there is no version of {package} in {set}") } } External::Custom(package, set, reason) => { if set == &Ranges::full() { format!("dependencies of {package} are unavailable because {reason}") } else { format!( "dependencies of {package} at version {set} are unavailable because {reason}" ) } } External::FromDependencyOf(package, package_set, dependency, dependency_set) => { if package_set == &Ranges::full() && dependency_set == &Ranges::full() { format!("{package} depends on {dependency}") } else if package_set == &Ranges::full() { format!("{package} depends on {dependency} {dependency_set}") } else if dependency_set == &Ranges::full() { if matches!(package, Package::Root) { // Exclude the dummy version for root packages format!("{package} depends on {dependency}") } else { format!("{package} {package_set} depends on {dependency}") } } else if matches!(package, Package::Root) { // Exclude the dummy version for root packages format!("{package} depends on {dependency} {dependency_set}") } else { format!("{package} {package_set} depends on {dependency} {dependency_set}") } } } } /// Simplest case, we just combine two external incompatibilities. fn explain_both_external( &self, external1: &External, String>, external2: &External, String>, current_terms: &Map>>, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} and {}, {}.", self.format_external(external1), self.format_external(external2), self.format_terms(current_terms) ) } /// Both causes have already been explained so we use their refs. fn explain_both_ref( &self, ref_id1: usize, derived1: &Derived, String>, ref_id2: usize, derived2: &Derived, String>, current_terms: &Map>>, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {} ({}), {}.", self.format_terms(&derived1.terms), ref_id1, self.format_terms(&derived2.terms), ref_id2, self.format_terms(current_terms) ) } /// One cause is derived (already explained so one-line), /// the other is a one-line external cause, /// and finally we conclude with the current incompatibility. fn explain_ref_and_external( &self, ref_id: usize, derived: &Derived, String>, external: &External, String>, current_terms: &Map>>, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {}, {}.", self.format_terms(&derived.terms), ref_id, self.format_external(external), self.format_terms(current_terms) ) } /// Add an external cause to the chain of explanations. fn and_explain_external( &self, external: &External, String>, current_terms: &Map>>, ) -> String { format!( "And because {}, {}.", self.format_external(external), self.format_terms(current_terms) ) } /// Add an already explained incompat to the chain of explanations. fn and_explain_ref( &self, ref_id: usize, derived: &Derived, String>, current_terms: &Map>>, ) -> String { format!( "And because {} ({}), {}.", self.format_terms(&derived.terms), ref_id, self.format_terms(current_terms) ) } /// Add an already explained incompat to the chain of explanations. fn and_explain_prior_and_external( &self, prior_external: &External, String>, external: &External, String>, current_terms: &Map>>, ) -> String { format!( "And because {} and {}, {}.", self.format_external(prior_external), self.format_external(external), self.format_terms(current_terms) ) } } fn main() { let mut dependency_provider = OfflineDependencyProvider::>::new(); // Define the root package with a dependency on a package we do not provide dependency_provider.add_dependencies( Package::Root, (0, 0, 0), vec![( Package::Package("foo".to_string()), Ranges::singleton((1, 0, 0)), )], ); // Run the algorithm match resolve(&dependency_provider, Package::Root, (0, 0, 0)) { Ok(sol) => println!("{sol:?}"), Err(PubGrubError::NoSolution(derivation_tree)) => { eprintln!("No solution.\n"); eprintln!("### Default report:"); eprintln!("```"); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); eprintln!("```\n"); eprintln!("### Report with custom formatter:"); eprintln!("```"); eprintln!( "{}", DefaultStringReporter::report_with_formatter( &derivation_tree, &CustomReportFormatter ) ); eprintln!("```"); std::process::exit(1); } Err(err) => panic!("{err:?}"), }; } astral-pubgrub-0.3.3/src/error.rs000064400000000000000000000056431046102023000150570ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! Handling pubgrub errors. use thiserror::Error; use crate::{DependencyProvider, DerivationTree}; /// There is no solution for this set of dependencies. pub type NoSolutionError = DerivationTree< ::P, ::VS, ::M, >; /// Errors that may occur while solving dependencies. #[derive(Error)] pub enum PubGrubError { /// There is no solution for this set of dependencies. #[error("There is no solution")] NoSolution(NoSolutionError), /// Error arising when the implementer of [DependencyProvider] returned an error in the method /// [`get_dependencies`](DependencyProvider::get_dependencies). #[error("Retrieving dependencies of {package} {version} failed")] ErrorRetrievingDependencies { /// Package whose dependencies we want. package: DP::P, /// Version of the package for which we want the dependencies. version: DP::V, /// Error raised by the implementer of [DependencyProvider]. source: DP::Err, }, /// Error arising when the implementer of [DependencyProvider] returned an error in the method /// [`choose_version`](DependencyProvider::choose_version). #[error("Choosing a version for {package} failed")] ErrorChoosingVersion { /// Package to choose a version for. package: DP::P, /// Error raised by the implementer of [DependencyProvider]. source: DP::Err, }, /// Error arising when the implementer of [DependencyProvider] /// returned an error in the method [`should_cancel`](DependencyProvider::should_cancel). #[error("The solver was cancelled")] ErrorInShouldCancel(#[source] DP::Err), } impl From> for PubGrubError { fn from(err: NoSolutionError) -> Self { Self::NoSolution(err) } } impl std::fmt::Debug for PubGrubError where DP: DependencyProvider, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::NoSolution(err) => f.debug_tuple("NoSolution").field(&err).finish(), Self::ErrorRetrievingDependencies { package, version, source, } => f .debug_struct("ErrorRetrievingDependencies") .field("package", package) .field("version", version) .field("source", source) .finish(), Self::ErrorChoosingVersion { package, source } => f .debug_struct("ErrorChoosingVersion") .field("package", package) .field("source", source) .finish(), Self::ErrorInShouldCancel(arg0) => { f.debug_tuple("ErrorInShouldCancel").field(arg0).finish() } } } } astral-pubgrub-0.3.3/src/internal/arena.rs000064400000000000000000000106751046102023000166310ustar 00000000000000use std::fmt; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::ops::{Index, Range}; type FnvIndexSet = indexmap::IndexSet; /// The index of a value allocated in an arena that holds `T`s. /// /// The Clone, Copy and other traits are defined manually because /// deriving them adds some additional constraints on the `T` generic type /// that we actually don't need since it is phantom. /// /// pub struct Id { raw: u32, _ty: PhantomData T>, } impl Clone for Id { fn clone(&self) -> Self { *self } } impl Copy for Id {} impl PartialEq for Id { fn eq(&self, other: &Id) -> bool { self.raw == other.raw } } impl Eq for Id {} impl Hash for Id { fn hash(&self, state: &mut H) { self.raw.hash(state) } } impl fmt::Debug for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut type_name = std::any::type_name::(); if let Some(id) = type_name.rfind(':') { type_name = &type_name[id + 1..] } write!(f, "Id::<{}>({})", type_name, self.raw) } } impl Id { pub(crate) fn into_raw(self) -> usize { self.raw as usize } fn from(n: u32) -> Self { Self { raw: n, _ty: PhantomData, } } pub(crate) fn range_to_iter(range: Range) -> impl Iterator { let start = range.start.raw; let end = range.end.raw; (start..end).map(Self::from) } } /// Yet another index-based arena. /// /// An arena is a kind of simple grow-only allocator, backed by a `Vec` /// where all items have the same lifetime, making it easier /// to have references between those items. /// They are all dropped at once when the arena is dropped. #[derive(Clone, PartialEq, Eq)] pub struct Arena { data: Vec, } impl fmt::Debug for Arena { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("Arena") .field("len", &self.data.len()) .field("data", &self.data) .finish() } } impl Default for Arena { fn default() -> Self { Self::new() } } impl Arena { pub(crate) fn new() -> Self { Self { data: Vec::new() } } pub(crate) fn alloc(&mut self, value: T) -> Id { let raw = self.data.len(); self.data.push(value); Id::from(raw as u32) } pub(crate) fn alloc_iter>(&mut self, values: I) -> Range> { let start = Id::from(self.data.len() as u32); values.for_each(|v| { self.alloc(v); }); let end = Id::from(self.data.len() as u32); Range { start, end } } } impl Index> for Arena { type Output = T; fn index(&self, id: Id) -> &T { &self.data[id.raw as usize] } } impl Index>> for Arena { type Output = [T]; fn index(&self, id: Range>) -> &[T] { &self.data[(id.start.raw as usize)..(id.end.raw as usize)] } } /// Yet another index-based arena. This one de-duplicates entries by hashing. /// /// An arena is a kind of simple grow-only allocator, backed by a `Vec` /// where all items have the same lifetime, making it easier /// to have references between those items. /// In this case the `Vec` is inside a `IndexSet` allowing fast lookup by value not just index. /// They are all dropped at once when the arena is dropped. #[derive(Clone, PartialEq, Eq)] pub struct HashArena { data: FnvIndexSet, } impl fmt::Debug for HashArena { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("Arena") .field("len", &self.data.len()) .field("data", &self.data) .finish() } } impl HashArena { pub fn new() -> Self { Self::default() } pub fn alloc(&mut self, value: T) -> Id { let (raw, _) = self.data.insert_full(value); Id::from(raw as u32) } } impl Default for HashArena { fn default() -> Self { Self { data: FnvIndexSet::default(), } } } impl Index> for HashArena { type Output = T; fn index(&self, id: Id) -> &T { &self.data[id.raw as usize] } } astral-pubgrub-0.3.3/src/internal/core.rs000064400000000000000000000514721046102023000164730ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! Core model and functions //! to write a functional PubGrub algorithm. use std::collections::HashSet as Set; use std::sync::Arc; use crate::internal::{ Arena, DecisionLevel, HashArena, Id, IncompDpId, IncompId, Incompatibility, PartialSolution, Relation, SatisfierSearch, SmallVec, }; use crate::{DependencyProvider, DerivationTree, Map, NoSolutionError, VersionSet}; /// Current state of the PubGrub algorithm. #[derive(Clone)] pub struct State { /// The root package and version. pub root_package: Id, root_version: DP::V, /// All incompatibilities indexed by package. #[allow(clippy::type_complexity)] pub incompatibilities: Map, Vec>>, /// As an optimization, store the ids of incompatibilities that are already contradicted. /// /// For each one keep track of the decision level when it was found to be contradicted. /// These will stay contradicted until we have backtracked beyond its associated decision level. contradicted_incompatibilities: Map, DecisionLevel>, /// All incompatibilities expressing dependencies, /// with common dependents merged. #[allow(clippy::type_complexity)] merged_dependencies: Map<(Id, Id), SmallVec>>, /// Partial solution. pub partial_solution: PartialSolution, /// The store is the reference storage for all incompatibilities. pub incompatibility_store: Arena>, /// The store is the reference storage for all packages. pub package_store: HashArena, /// This is a stack of work to be done in `unit_propagation`. /// It can definitely be a local variable to that method, but /// this way we can reuse the same allocation for better performance. unit_propagation_buffer: SmallVec>, } impl State { /// Initialization of PubGrub state. pub fn init(root_package: DP::P, root_version: DP::V) -> Self { let mut incompatibility_store = Arena::new(); let mut package_store = HashArena::new(); let root_package = package_store.alloc(root_package); let not_root_id = incompatibility_store.alloc(Incompatibility::not_root( root_package, root_version.clone(), )); let mut incompatibilities = Map::default(); incompatibilities.insert(root_package, vec![not_root_id]); Self { root_package, root_version, incompatibilities, contradicted_incompatibilities: Map::default(), partial_solution: PartialSolution::empty(), incompatibility_store, package_store, unit_propagation_buffer: SmallVec::Empty, merged_dependencies: Map::default(), } } /// Add the dependencies for the current version of the current package as incompatibilities. pub fn add_package_version_dependencies( &mut self, package: Id, version: DP::V, dependencies: impl IntoIterator, ) -> Option> { let dep_incompats = self.add_incompatibility_from_dependencies(package, version.clone(), dependencies); self.partial_solution.add_package_version_incompatibilities( package, version.clone(), dep_incompats, &self.incompatibility_store, ) } /// Add an incompatibility to the state. pub fn add_incompatibility(&mut self, incompat: Incompatibility) { let id = self.incompatibility_store.alloc(incompat); self.merge_incompatibility(id); } /// Add a single custom incompatibility that requires the base package and the proxy package /// share the same version range. /// /// This intended for cases where proxy packages (also known as virtual packages) are used. /// Without this information, pubgrub does not know that these packages have to be at the same /// version. In cases where the base package is already to an incompatible version, this avoids /// going through all versions of the proxy package. In cases where there are two incompatible /// proxy packages, it avoids trying versions for both of them. Both improve performance (we /// don't need to check all versions when there is a conflict) and error messages (report a /// conflict of version ranges instead of enumerating the conflicting versions). /// /// Using this method requires that each version of the proxy package depends on the exact /// version of the base package. pub fn add_proxy_package( &mut self, proxy_package: Id, base_package: Id, versions: DP::VS, ) { let incompat = Incompatibility::from_dependency( proxy_package, versions.clone(), (base_package, versions), ); let id = self .incompatibility_store .alloc_iter([incompat].into_iter()); for id in IncompDpId::::range_to_iter(id) { self.merge_incompatibility(id); } } /// Add an incompatibility to the state. #[cold] pub(crate) fn add_incompatibility_from_dependencies( &mut self, package: Id, version: DP::V, deps: impl IntoIterator, ) -> std::ops::Range> { // Create incompatibilities and allocate them in the store. let new_incompats_id_range = self.incompatibility_store .alloc_iter(deps.into_iter().map(|(dep_p, dep_vs)| { let dep_pid = self.package_store.alloc(dep_p); Incompatibility::from_dependency( package, ::singleton(version.clone()), (dep_pid, dep_vs), ) })); // Merge the newly created incompatibilities with the older ones. for id in IncompDpId::::range_to_iter(new_incompats_id_range.clone()) { self.merge_incompatibility(id); } new_incompats_id_range } /// Unit propagation is the core mechanism of the solving algorithm. /// CF /// /// For each package with a satisfied incompatibility, returns the package and the root cause /// incompatibility. #[cold] #[allow(clippy::type_complexity)] // Type definitions don't support impl trait. pub fn unit_propagation( &mut self, package: Id, ) -> Result, IncompDpId)>, NoSolutionError> { let mut satisfier_causes = SmallVec::default(); self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { // Iterate over incompatibilities in reverse order // to evaluate first the newest incompatibilities. let mut conflict_id = None; // We only care about incompatibilities if it contains the current package. for &incompat_id in self.incompatibilities[¤t_package].iter().rev() { if self .contradicted_incompatibilities .contains_key(&incompat_id) { continue; } let current_incompat = &self.incompatibility_store[incompat_id]; match self.partial_solution.relation(current_incompat) { // If the partial solution satisfies the incompatibility // we must perform conflict resolution. Relation::Satisfied => { log::info!( "Start conflict resolution because incompat satisfied:\n {}", current_incompat.display(&self.package_store) ); conflict_id = Some(incompat_id); break; } Relation::AlmostSatisfied(package_almost) => { // Add `package_almost` to the `unit_propagation_buffer` set. // Putting items in `unit_propagation_buffer` more than once waste cycles, // but so does allocating a hash map and hashing each item. // In practice `unit_propagation_buffer` is small enough that we can just do a linear scan. if !self.unit_propagation_buffer.contains(&package_almost) { self.unit_propagation_buffer.push(package_almost); } // Add (not term) to the partial solution with incompat as cause. self.partial_solution.add_derivation( package_almost, incompat_id, &self.incompatibility_store, ); // With the partial solution updated, the incompatibility is now contradicted. self.contradicted_incompatibilities .insert(incompat_id, self.partial_solution.current_decision_level()); } Relation::Contradicted(_) => { self.contradicted_incompatibilities .insert(incompat_id, self.partial_solution.current_decision_level()); } _ => {} } } if let Some(incompat_id) = conflict_id { let (package_almost, root_cause) = self .conflict_resolution(incompat_id, &mut satisfier_causes) .map_err(|terminal_incompat_id| { self.build_derivation_tree(terminal_incompat_id) })?; self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package_almost); // Add to the partial solution with incompat as cause. self.partial_solution.add_derivation( package_almost, root_cause, &self.incompatibility_store, ); // After conflict resolution and the partial solution update, // the root cause incompatibility is now contradicted. self.contradicted_incompatibilities .insert(root_cause, self.partial_solution.current_decision_level()); } } // If there are no more changed packages, unit propagation is done. Ok(satisfier_causes) } /// Return the root cause or the terminal incompatibility. CF /// /// /// When we found a conflict, we want to learn as much as possible from it, to avoid making (or /// keeping) decisions that will be rejected. Say we found that the dependency requirements on X and the /// dependency requirements on Y are incompatible. We may find that the decisions on earlier packages B and C /// require us to make incompatible requirements on X and Y, so we backtrack until either B or C /// can be revisited. To make it practical, we really only need one of the terms to be a /// decision. We may as well leave the other terms general. Something like "the dependency on /// the package X is incompatible with the decision on C" tends to work out pretty well. Then if /// A turns out to also have a dependency on X the resulting root cause is still useful. /// (`unit_propagation` will ensure we don't try that version of C.) /// Of course, this is more heuristics than science. If the output is too general, then /// `unit_propagation` will handle the confusion by calling us again with the next most specific /// conflict it comes across. If the output is too specific, then the outer `solver` loop will /// eventually end up calling us again until all possibilities are enumerated. /// /// To end up with a more useful incompatibility, this function combines incompatibilities into /// derivations. Fulfilling this derivation implies the later conflict. By banning it, we /// prevent the intermediate steps from occurring again, at least in the exact same way. /// However, the statistics collected for `prioritize` may want to analyze those intermediate /// steps. For example we might start with "there is no version 1 of Z", and /// `conflict_resolution` may be able to determine that "that was inevitable when we picked /// version 1 of X" which was inevitable when we picked W and so on, until version 1 of B, which /// was depended on by version 1 of A. Therefore the root cause may simplify all the way down to /// "we cannot pick version 1 of A". This will prevent us going down this path again. However /// when we start looking at version 2 of A, and discover that it depends on version 2 of B, we /// will want to prioritize the chain of intermediate steps to check if it has a problem with /// the same shape. The `satisfier_causes` argument keeps track of these intermediate steps so /// that the caller can use them for prioritization. #[allow(clippy::type_complexity)] #[cold] fn conflict_resolution( &mut self, incompatibility: IncompDpId, satisfier_causes: &mut SmallVec<(Id, IncompDpId)>, ) -> Result<(Id, IncompDpId), IncompDpId> { let mut current_incompat_id = incompatibility; let mut current_incompat_changed = false; loop { if self.incompatibility_store[current_incompat_id] .is_terminal(self.root_package, &self.root_version) { return Err(current_incompat_id); } else { let (package, satisfier_search_result) = self.partial_solution.satisfier_search( &self.incompatibility_store[current_incompat_id], &self.incompatibility_store, ); match satisfier_search_result { SatisfierSearch::DifferentDecisionLevels { previous_satisfier_level, } => { self.backtrack( current_incompat_id, current_incompat_changed, previous_satisfier_level, ); log::info!("backtrack to {previous_satisfier_level:?}"); satisfier_causes.push((package, current_incompat_id)); return Ok((package, current_incompat_id)); } SatisfierSearch::SameDecisionLevels { satisfier_cause } => { let prior_cause = Incompatibility::prior_cause( current_incompat_id, satisfier_cause, package, &self.incompatibility_store, ); log::info!("prior cause: {}", prior_cause.display(&self.package_store)); current_incompat_id = self.incompatibility_store.alloc(prior_cause); satisfier_causes.push((package, current_incompat_id)); current_incompat_changed = true; } } } } } /// After a conflict occurred, backtrack the partial solution to a given decision level, and add /// the incompatibility if it was new. fn backtrack( &mut self, incompat: IncompDpId, incompat_changed: bool, decision_level: DecisionLevel, ) { self.partial_solution.backtrack(decision_level); // Remove contradicted incompatibilities that depend on decisions we just backtracked away. self.contradicted_incompatibilities .retain(|_, dl| *dl <= decision_level); if incompat_changed { self.merge_incompatibility(incompat); } } /// Manually backtrack before the given package was selected. /// /// This can be used to switch the order of packages if the previous prioritization was bad. /// /// Returns the number of the decisions that were backtracked, or `None` if the package was not /// decided on yet. pub fn backtrack_package(&mut self, package: Id) -> Option { let base_decision_level = self.partial_solution.current_decision_level(); let new_decision_level = self.partial_solution.backtrack_package(package).ok()?; // Remove contradicted incompatibilities that depend on decisions we just backtracked away. self.contradicted_incompatibilities .retain(|_, dl| *dl <= new_decision_level); Some(base_decision_level.0 - new_decision_level.0) } /// Add this incompatibility into the set of all incompatibilities. /// /// PubGrub collapses identical dependencies from adjacent package versions /// into individual incompatibilities. /// This substantially reduces the total number of incompatibilities /// and makes it much easier for PubGrub to reason about multiple versions of packages at once. /// /// For example, rather than representing /// foo 1.0.0 depends on bar ^1.0.0 and /// foo 1.1.0 depends on bar ^1.0.0 /// as two separate incompatibilities, /// they are collapsed together into the single incompatibility {foo ^1.0.0, not bar ^1.0.0} /// (provided that no other version of foo exists between 1.0.0 and 2.0.0). /// We could collapse them into { foo (1.0.0 ∪ 1.1.0), not bar ^1.0.0 } /// without having to check the existence of other versions though. fn merge_incompatibility(&mut self, mut id: IncompDpId) { if let Some((p1, p2)) = self.incompatibility_store[id].as_dependency() { // If we are a dependency, there's a good chance we can be merged with a previous dependency let deps_lookup = self.merged_dependencies.entry((p1, p2)).or_default(); if let Some((past, merged)) = deps_lookup.as_mut_slice().iter_mut().find_map(|past| { self.incompatibility_store[id] .merge_dependents(&self.incompatibility_store[*past]) .map(|m| (past, m)) }) { let new = self.incompatibility_store.alloc(merged); for (pkg, _) in self.incompatibility_store[new].iter() { self.incompatibilities .entry(pkg) .or_default() .retain(|id| id != past); } *past = new; id = new; } else { deps_lookup.push(id); } } for (pkg, term) in self.incompatibility_store[id].iter() { if cfg!(debug_assertions) { assert_ne!(term, &crate::term::Term::any()); } self.incompatibilities.entry(pkg).or_default().push(id); } } // Error reporting ######################################################### fn build_derivation_tree( &self, incompat: IncompDpId, ) -> DerivationTree { let mut all_ids: Set> = Set::default(); let mut shared_ids = Set::default(); let mut stack = vec![incompat]; while let Some(i) = stack.pop() { if let Some((id1, id2)) = self.incompatibility_store[i].causes() { if all_ids.contains(&i) { shared_ids.insert(i); } else { stack.push(id1); stack.push(id2); } } all_ids.insert(i); } // To avoid recursion we need to generate trees in topological order. // That is to say we need to ensure that the causes are processed before the incompatibility they effect. // It happens to be that sorting by their ID maintains this property. let mut sorted_ids = all_ids.into_iter().collect::>(); sorted_ids.sort_unstable_by_key(|id| id.into_raw()); let mut precomputed = Map::default(); for id in sorted_ids { let tree = Incompatibility::build_derivation_tree( id, &shared_ids, &self.incompatibility_store, &self.package_store, &precomputed, ); precomputed.insert(id, Arc::new(tree)); } // Now the user can refer to the entire tree from its root. Arc::into_inner(precomputed.remove(&incompat).unwrap()).unwrap() } } astral-pubgrub-0.3.3/src/internal/incompatibility.rs000064400000000000000000000477241046102023000207500ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! An incompatibility is a set of terms for different packages //! that should never be satisfied all together. use std::fmt::{Debug, Display}; use std::sync::Arc; use crate::internal::{Arena, HashArena, Id, SmallMap}; use crate::{ DependencyProvider, DerivationTree, Derived, External, Map, Package, Set, Term, VersionSet, term, }; /// An incompatibility is a set of terms for different packages /// that should never be satisfied all together. /// An incompatibility usually originates from a package dependency. /// For example, if package A at version 1 depends on package B /// at version 2, you can never have both terms `A = 1` /// and `not B = 2` satisfied at the same time in a partial solution. /// This would mean that we found a solution with package A at version 1 /// but not with package B at version 2. /// Yet A at version 1 depends on B at version 2 so this is not possible. /// Therefore, the set `{ A = 1, not B = 2 }` is an incompatibility, /// defined from dependencies of A at version 1. /// /// Incompatibilities can also be derived from two other incompatibilities /// during conflict resolution. More about all this in /// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility). #[derive(Debug, Clone)] pub struct Incompatibility { package_terms: SmallMap, Term>, /// The reason for the incompatibility. pub kind: Kind, } /// Type alias of unique identifiers for incompatibilities. pub type IncompId = Id>; pub(crate) type IncompDpId = IncompId< ::P, ::VS, ::M, >; /// The reason for the incompatibility. #[derive(Debug, Clone)] pub enum Kind { /// Initial incompatibility aiming at picking the root package for the first decision. /// /// This incompatibility drives the resolution, it requires that we pick the (virtual) root /// packages. NotRoot(Id

, VS::V), /// There are no versions in the given range for this package. /// /// This incompatibility is used when we tried all versions in a range and no version /// worked, so we have to backtrack NoVersions(Id

, VS), /// Incompatibility coming from the dependencies of a given package. /// /// If a@1 depends on b>=1,<2, we create an incompatibility with terms `{a 1, b <1,>=2}` with /// kind `FromDependencyOf(a, 1, b, >=1,<2)`. /// /// We can merge multiple dependents with the same version. For example, if a@1 depends on b and /// a@2 depends on b, we can say instead a@1||2 depends on b. FromDependencyOf(Id

, VS, Id

, VS), /// Derived from two causes. Stores cause ids. /// /// For example, if a -> b and b -> c, we can derive a -> c. DerivedFrom(IncompId, IncompId), /// The package is unavailable for reasons outside pubgrub. /// /// Examples: /// * The version would require building the package, but builds are disabled. /// * The package is not available in the cache, but internet access has been disabled. Custom(Id

, VS, M), } /// A Relation describes how a set of terms can be compared to an incompatibility. /// Typically, the set of terms comes from the partial solution. #[derive(Eq, PartialEq, Debug)] pub(crate) enum Relation { /// We say that a set of terms S satisfies an incompatibility I /// if S satisfies every term in I. Satisfied, /// We say that S contradicts I /// if S contradicts at least one term in I. Contradicted(Id

), /// If S satisfies all but one of I's terms and is inconclusive for the remaining term, /// we say S "almost satisfies" I and we call the remaining term the "unsatisfied term". AlmostSatisfied(Id

), /// Otherwise, we say that their relation is inconclusive. Inconclusive, } impl Incompatibility { /// Create the initial "not Root" incompatibility. pub(crate) fn not_root(package: Id

, version: VS::V) -> Self { Self { package_terms: SmallMap::One([( package, Term::Negative(VS::singleton(version.clone())), )]), kind: Kind::NotRoot(package, version), } } /// Create an incompatibility to remember that a given set does not contain any version. pub fn no_versions(package: Id

, term: Term) -> Self { let set = match &term { Term::Positive(r) => r.clone(), Term::Negative(_) => panic!("No version should have a positive term"), }; Self { package_terms: SmallMap::One([(package, term)]), kind: Kind::NoVersions(package, set), } } /// Create an incompatibility for a reason outside pubgrub. #[allow(dead_code)] // Used by uv pub fn custom_term(package: Id

, term: Term, metadata: M) -> Self { let set = match &term { Term::Positive(r) => r.clone(), Term::Negative(_) => panic!("No version should have a positive term"), }; Self { package_terms: SmallMap::One([(package, term)]), kind: Kind::Custom(package, set, metadata), } } /// Create an incompatibility for a reason outside pubgrub. pub fn custom_version(package: Id

, version: VS::V, metadata: M) -> Self { let set = VS::singleton(version); let term = Term::Positive(set.clone()); Self { package_terms: SmallMap::One([(package, term)]), kind: Kind::Custom(package, set, metadata), } } /// Build an incompatibility from a given dependency. pub fn from_dependency(package: Id

, versions: VS, dep: (Id

, VS)) -> Self { let (p2, set2) = dep; Self { package_terms: if set2 == VS::empty() { SmallMap::One([(package, Term::Positive(versions.clone()))]) } else { SmallMap::Two([ (package, Term::Positive(versions.clone())), (p2, Term::Negative(set2.clone())), ]) }, kind: Kind::FromDependencyOf(package, versions, p2, set2), } } pub(crate) fn as_dependency(&self) -> Option<(Id

, Id

)> { match &self.kind { Kind::FromDependencyOf(p1, _, p2, _) => Some((*p1, *p2)), _ => None, } } /// Merge dependant versions with the same dependency. /// /// When multiple versions of a package depend on the same range of another package, /// we can merge the two into a single incompatibility. /// For example, if a@1 depends on b and a@2 depends on b, we can say instead /// a@1||2 depends on b. /// /// It is a special case of prior cause computation where the unified package /// is the common dependant in the two incompatibilities expressing dependencies. pub(crate) fn merge_dependents(&self, other: &Self) -> Option { // It is almost certainly a bug to call this method without checking that self is a dependency debug_assert!(self.as_dependency().is_some()); // Check that both incompatibilities are of the shape p1 depends on p2, // with the same p1 and p2. let self_pkgs = self.as_dependency()?; if self_pkgs != other.as_dependency()? { return None; } let (p1, p2) = self_pkgs; // We ignore self-dependencies. They are always either trivially true or trivially false, // as the package version implies whether the constraint will always be fulfilled or always // violated. // At time of writing, the public crate API only allowed a map of dependencies, // meaning it can't hit this branch, which requires two self-dependencies. if p1 == p2 { return None; } let dep_term = self.get(p2); // The dependency range for p2 must be the same in both case // to be able to merge multiple p1 ranges. if dep_term != other.get(p2) { return None; } Some(Self::from_dependency( p1, self.get(p1) .unwrap() .unwrap_positive() .union(other.get(p1).unwrap().unwrap_positive()), // It is safe to `simplify` here ( p2, dep_term.map_or(VS::empty(), |v| v.unwrap_negative().clone()), ), )) } /// Prior cause of two incompatibilities using the rule of resolution. pub(crate) fn prior_cause( incompat: Id, satisfier_cause: Id, package: Id

, incompatibility_store: &Arena, ) -> Self { let kind = Kind::DerivedFrom(incompat, satisfier_cause); // Optimization to avoid cloning and dropping t1 let (t1, mut package_terms) = incompatibility_store[incompat] .package_terms .split_one(&package) .unwrap(); let satisfier_cause_terms = &incompatibility_store[satisfier_cause].package_terms; package_terms.merge( satisfier_cause_terms.iter().filter(|(p, _)| p != &&package), |t1, t2| Some(t1.intersection(t2)), ); let term = t1.union(satisfier_cause_terms.get(&package).unwrap()); if term != Term::any() { package_terms.insert(package, term); } Self { package_terms, kind, } } /// Check if an incompatibility should mark the end of the algorithm /// because it satisfies the root package. pub(crate) fn is_terminal(&self, root_package: Id

, root_version: &VS::V) -> bool { if self.package_terms.len() == 0 { true } else if self.package_terms.len() > 1 { false } else { let (package, term) = self.package_terms.iter().next().unwrap(); (package == &root_package) && term.contains(root_version) } } /// Get the term related to a given package (if it exists). pub(crate) fn get(&self, package: Id

) -> Option<&Term> { self.package_terms.get(&package) } /// Iterate over packages. pub fn iter(&self) -> impl Iterator, &Term)> { self.package_terms .iter() .map(|(package, term)| (*package, term)) } // Reporting ############################################################### /// Retrieve parent causes if of type DerivedFrom. pub(crate) fn causes(&self) -> Option<(Id, Id)> { match self.kind { Kind::DerivedFrom(id1, id2) => Some((id1, id2)), _ => None, } } /// Build a derivation tree for error reporting. pub(crate) fn build_derivation_tree( self_id: Id, shared_ids: &Set>, store: &Arena, package_store: &HashArena

, precomputed: &Map, Arc>>, ) -> DerivationTree { match store[self_id].kind.clone() { Kind::DerivedFrom(id1, id2) => { let derived: Derived = Derived { terms: store[self_id] .package_terms .iter() .map(|(&a, b)| (package_store[a].clone(), b.clone())) .collect(), shared_id: shared_ids.get(&self_id).map(|id| id.into_raw()), cause1: precomputed .get(&id1) .expect("Non-topological calls building tree") .clone(), cause2: precomputed .get(&id2) .expect("Non-topological calls building tree") .clone(), }; DerivationTree::Derived(derived) } Kind::NotRoot(package, version) => { DerivationTree::External(External::NotRoot(package_store[package].clone(), version)) } Kind::NoVersions(package, set) => DerivationTree::External(External::NoVersions( package_store[package].clone(), set.clone(), )), Kind::FromDependencyOf(package, set, dep_package, dep_set) => { DerivationTree::External(External::FromDependencyOf( package_store[package].clone(), set.clone(), package_store[dep_package].clone(), dep_set.clone(), )) } Kind::Custom(package, set, metadata) => DerivationTree::External(External::Custom( package_store[package].clone(), set.clone(), metadata.clone(), )), } } } impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> Incompatibility { /// CF definition of Relation enum. pub(crate) fn relation(&self, terms: impl Fn(Id

) -> Option<&'a Term>) -> Relation

{ let mut relation = Relation::Satisfied; for (&package, incompat_term) in self.package_terms.iter() { match terms(package).map(|term| incompat_term.relation_with(term)) { Some(term::Relation::Satisfied) => {} Some(term::Relation::Contradicted) => { return Relation::Contradicted(package); } None | Some(term::Relation::Inconclusive) => { // If a package is not present, the intersection is the same as [Term::any]. // According to the rules of satisfactions, the relation would be inconclusive. // It could also be satisfied if the incompatibility term was also [Term::any], // but we systematically remove those from incompatibilities // so we're safe on that front. if relation == Relation::Satisfied { relation = Relation::AlmostSatisfied(package); } else { return Relation::Inconclusive; } } } } relation } } impl Incompatibility { /// Display the incompatibility. pub fn display<'a>(&'a self, package_store: &'a HashArena

) -> impl Display + 'a { match self.iter().collect::>().as_slice() { [] => "version solving failed".into(), // TODO: special case when that unique package is root. [(package, Term::Positive(range))] => { format!("{} {} is forbidden", package_store[*package], range) } [(package, Term::Negative(range))] => { format!("{} {} is mandatory", package_store[*package], range) } [ (p_pos, Term::Positive(r_pos)), (p_neg, Term::Negative(r_neg)), ] | [ (p_neg, Term::Negative(r_neg)), (p_pos, Term::Positive(r_pos)), ] => External::<_, _, M>::FromDependencyOf( &package_store[*p_pos], r_pos.clone(), &package_store[*p_neg], r_neg.clone(), ) .to_string(), slice => { let str_terms: Vec<_> = slice .iter() .map(|(p, t)| format!("{} {}", package_store[*p], t)) .collect(); str_terms.join(", ") + " are incompatible" } } } } // TESTS ####################################################################### #[cfg(test)] pub(crate) mod tests { use proptest::prelude::*; use std::cmp::Reverse; use std::collections::BTreeMap; use super::*; use crate::internal::State; use crate::term::tests::strategy as term_strat; use crate::{OfflineDependencyProvider, Ranges}; proptest! { /// For any three different packages p1, p2 and p3, /// for any three terms t1, t2 and t3, /// if we have the two following incompatibilities: /// { p1: t1, p2: not t2 } /// { p2: t2, p3: t3 } /// the rule of resolution says that we can deduce the following incompatibility: /// { p1: t1, p3: t3 } #[test] fn rule_of_resolution(t1 in term_strat(), t2 in term_strat(), t3 in term_strat()) { let mut store = Arena::new(); let mut package_store = HashArena::new(); let p1 = package_store.alloc("p1"); let p2 = package_store.alloc("p2"); let p3 = package_store.alloc("p3"); let i1 = store.alloc(Incompatibility { package_terms: SmallMap::Two([(p1, t1.clone()), (p2, t2.negate())]), kind: Kind::<_, _, String>::FromDependencyOf(p1, Ranges::full(), p2, Ranges::full()) }); let i2 = store.alloc(Incompatibility { package_terms: SmallMap::Two([(p2, t2), (p3, t3.clone())]), kind: Kind::<_, _, String>::FromDependencyOf(p2, Ranges::full(), p3, Ranges::full()) }); let mut i3 = Map::default(); i3.insert(p1, t1); i3.insert(p3, t3); let i_resolution = Incompatibility::prior_cause(i1, i2, p2, &store); assert_eq!(i_resolution.package_terms.iter().map(|(&k, v)|(k, v.clone())).collect::>(), i3); } } /// Check that multiple self-dependencies are supported. /// /// The current public API deduplicates dependencies through a map, so we test them here /// manually. /// /// https://github.com/astral-sh/uv/issues/13344 #[test] fn package_depend_on_self() { let cases: &[Vec<(String, Ranges)>] = &[ vec![("foo".to_string(), Ranges::full())], vec![ ("foo".to_string(), Ranges::full()), ("foo".to_string(), Ranges::full()), ], vec![ ("foo".to_string(), Ranges::full()), ("foo".to_string(), Ranges::singleton(1usize)), ], vec![ ("foo".to_string(), Ranges::singleton(1usize)), ("foo".to_string(), Ranges::from_range_bounds(1usize..2)), ("foo".to_string(), Ranges::from_range_bounds(1usize..3)), ], ]; for case in cases { let mut state: State>> = State::init("root".to_string(), 0); state.unit_propagation(state.root_package).unwrap(); // Add the root package state.add_package_version_dependencies( state.root_package, 0, [("foo".to_string(), Ranges::singleton(1usize))], ); state.unit_propagation(state.root_package).unwrap(); // Add a package that depends on itself twice let (next, _) = state .partial_solution .pick_highest_priority_pkg(|_p, _r| (0, Reverse(0))) .unwrap(); state.add_package_version_dependencies(next, 1, case.clone()); state.unit_propagation(next).unwrap(); assert!( state .partial_solution .pick_highest_priority_pkg(|_p, _r| (0, Reverse(0))) .is_none() ); let solution: BTreeMap = state .partial_solution .extract_solution() .map(|(p, v)| (state.package_store[p].clone(), v)) .collect(); let expected = BTreeMap::from([("root".to_string(), 0), ("foo".to_string(), 1)]); assert_eq!(solution, expected, "{case:?}"); } } } astral-pubgrub-0.3.3/src/internal/mod.rs000064400000000000000000000010321046102023000163050ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! Non exposed modules. mod arena; mod core; mod incompatibility; mod partial_solution; mod small_map; mod small_vec; pub(crate) use arena::{Arena, HashArena}; pub(crate) use incompatibility::{IncompDpId, Relation}; pub(crate) use partial_solution::{DecisionLevel, PartialSolution, SatisfierSearch}; pub(crate) use small_map::SmallMap; pub(crate) use small_vec::SmallVec; // uv-specific additions pub use arena::Id; pub use core::State; pub use incompatibility::{IncompId, Incompatibility, Kind}; astral-pubgrub-0.3.3/src/internal/partial_solution.rs000064400000000000000000000724701046102023000211340ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! A Memory acts like a structured partial solution //! where terms are regrouped by package in a [Map](crate::type_aliases::Map). use std::cmp::Reverse; use std::fmt::{Debug, Display}; use std::hash::BuildHasherDefault; use log::debug; use priority_queue::PriorityQueue; use rustc_hash::FxHasher; use crate::internal::{ Arena, HashArena, Id, IncompDpId, IncompId, Incompatibility, Relation, SmallMap, SmallVec, }; use crate::{DependencyProvider, Package, Term, VersionSet}; type FnvIndexMap = indexmap::IndexMap>; type FnvIndexSet = indexmap::IndexSet>; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub(crate) struct DecisionLevel(pub(crate) u32); impl DecisionLevel { pub(crate) fn increment(self) -> Self { Self(self.0 + 1) } } /// The partial solution contains all package assignments, /// organized by package and historically ordered. #[derive(Clone, Debug)] pub struct PartialSolution { next_global_index: u32, /// The number of decisions that have been made, equal to the number of packages with decisions. current_decision_level: DecisionLevel, /// Store for all known package decisions and package derivations. /// /// "assignment" refers to both packages with decisions and package with only derivations and /// no decision yet. We combine this in a single index map with a decisions section in front and /// a derivations sections in the back, where making a decision moves a package from the /// derivations sections to the decisions section. /// /// `[..current_decision_level]`: Packages that have had a decision made, sorted by the /// `decision_level`. The section is can be seen as the partial solution, it contains a /// mapping from package name to decided version. The sorting makes it very efficient to /// extract the solution, and to backtrack to a particular decision level. The /// `AssignmentsIntersection` is always a `Decision`. /// /// `[current_decision_level..]`: Packages that are dependencies of some other package, /// but have not yet been decided. The `AssignmentsIntersection` is always a `Derivations`, the /// derivations store the obligations from the decided packages. #[allow(clippy::type_complexity)] package_assignments: FnvIndexMap, PackageAssignments>, /// The undecided packages order by their `Priority`. /// /// The max heap allows quickly `pop`ing the highest priority package. /// /// The `Reverse` is the discovery order of packages used as tiebreaker. Its order is that /// of a breadth-first search. #[allow(clippy::type_complexity)] prioritized_potential_packages: PriorityQueue, (DP::Priority, Reverse), BuildHasherDefault>, /// Packages whose derivations changed since the last time `prioritize` was called and need /// their priorities to be updated. outdated_priorities: FnvIndexSet>, /// Whether we have never backtracked, to enable fast path optimizations. has_ever_backtracked: bool, } /// A package assignment is either a decision or a list of (accumulated) derivations without a /// decision. #[derive(Clone, Debug)] struct PackageAssignments { /// Whether the assigment is a decision or a derivation. assignments_intersection: AssignmentsIntersection, /// All constraints on the package version from previous decisions, accumulated by decision /// level. dated_derivations: SmallVec>, /// Smallest [`DecisionLevel`] in `dated_derivations`. smallest_decision_level: DecisionLevel, /// Highest [`DecisionLevel`] in `dated_derivations`. highest_decision_level: DecisionLevel, } impl Display for PackageAssignments { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let derivations: Vec<_> = self .dated_derivations .iter() .map(|dd| dd.to_string()) .collect(); write!( f, "decision range: {:?}..{:?}\nderivations:\n {}\n,assignments_intersection: {}", self.smallest_decision_level, self.highest_decision_level, derivations.join("\n "), self.assignments_intersection ) } } #[derive(Clone, Debug)] struct DatedDerivation { global_index: u32, /// Only decisions up this level has been used to compute the accumulated term. decision_level: DecisionLevel, cause: IncompId, /// The intersection of all terms up to `decision_level`. /// /// It may not contain all terms of this `decision_level`, there may be more than one /// `DatedDerivation` per decision level. accumulated_intersection: Term, } impl Display for DatedDerivation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}, cause: {:?}", self.decision_level, self.cause) } } #[derive(Clone, Debug)] enum AssignmentsIntersection { /// A decision on package for version has been made at the given level. Decision { decision_level: u32, version: VS::V, /// The version, but as positive singleton term. term: Term, }, Derivations(Term), } impl Display for AssignmentsIntersection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Decision { decision_level, version, term: _, } => { write!(f, "Decision: level {decision_level}, v = {version}") } Self::Derivations(term) => write!(f, "Derivations term: {term}"), } } } #[derive(Clone, Debug)] pub(crate) enum SatisfierSearch { DifferentDecisionLevels { previous_satisfier_level: DecisionLevel, }, SameDecisionLevels { satisfier_cause: IncompId, }, } type SatisfiedMap = SmallMap, (Option>, u32, DecisionLevel)>; impl PartialSolution { /// Initialize an empty PartialSolution. pub(crate) fn empty() -> Self { Self { next_global_index: 0, current_decision_level: DecisionLevel(0), package_assignments: FnvIndexMap::default(), prioritized_potential_packages: PriorityQueue::default(), outdated_priorities: FnvIndexSet::default(), has_ever_backtracked: false, } } pub(crate) fn display<'a>(&'a self, package_store: &'a HashArena) -> impl Display + 'a { struct PSDisplay<'a, DP: DependencyProvider>(&'a PartialSolution, &'a HashArena); impl Display for PSDisplay<'_, DP> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut assignments: Vec<_> = self .0 .package_assignments .iter() .map(|(p, pa)| format!("{:?} = '{}': {}", p, self.1[*p], pa)) .collect(); assignments.sort(); write!( f, "next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignments:\n{}", self.0.next_global_index, self.0.current_decision_level, assignments.join("\t\n") ) } } PSDisplay(self, package_store) } /// Add a decision. pub fn add_decision(&mut self, package: Id, version: DP::V) { // Check that add_decision is never used in the wrong context. if cfg!(debug_assertions) { match self.package_assignments.get_mut(&package) { None => panic!("Derivations must already exist"), Some(pa) => match &pa.assignments_intersection { // Cannot be called when a decision has already been taken. AssignmentsIntersection::Decision { .. } => { panic!("Already existing decision") } // Cannot be called if the versions is not contained in the terms' intersection. AssignmentsIntersection::Derivations(term) => { debug_assert!( term.contains(&version), "{package:?}: {version} was expected to be contained in {term}", ) } }, } } let new_idx = self.current_decision_level.0 as usize; self.current_decision_level = self.current_decision_level.increment(); let (old_idx, _, pa) = self .package_assignments .get_full_mut(&package) .expect("Derivations must already exist"); pa.highest_decision_level = self.current_decision_level; pa.assignments_intersection = AssignmentsIntersection::Decision { decision_level: self.next_global_index, version: version.clone(), term: Term::exact(version), }; // Maintain that the beginning of the `package_assignments` Have all decisions in sorted order. if new_idx != old_idx { self.package_assignments.swap_indices(new_idx, old_idx); } self.next_global_index += 1; } /// The list of package that have not been selected after the last prioritization. /// /// This list gets updated by [`Self::pick_highest_priority_pkg`] and cleared by backtracking. #[allow(clippy::type_complexity)] pub fn undecided_packages( &self, ) -> impl Iterator< Item = ( &Id, &(::Priority, Reverse), ), > { self.prioritized_potential_packages.iter() } /// Add a derivation. pub(crate) fn add_derivation( &mut self, package: Id, cause: IncompDpId, store: &Arena>, ) { use indexmap::map::Entry; let mut dated_derivation = DatedDerivation { global_index: self.next_global_index, decision_level: self.current_decision_level, cause, accumulated_intersection: store[cause].get(package).unwrap().negate(), }; self.next_global_index += 1; match self.package_assignments.entry(package) { Entry::Occupied(mut occupied) => { let pa = occupied.get_mut(); pa.highest_decision_level = self.current_decision_level; match &mut pa.assignments_intersection { // Check that add_derivation is never called in the wrong context. AssignmentsIntersection::Decision { .. } => { panic!("add_derivation should not be called after a decision") } AssignmentsIntersection::Derivations(t) => { *t = t.intersection(&dated_derivation.accumulated_intersection); dated_derivation.accumulated_intersection = t.clone(); if t.is_positive() { self.outdated_priorities.insert(package); } } } pa.dated_derivations.push(dated_derivation); } Entry::Vacant(v) => { let term = dated_derivation.accumulated_intersection.clone(); if term.is_positive() { self.outdated_priorities.insert(package); } v.insert(PackageAssignments { smallest_decision_level: self.current_decision_level, highest_decision_level: self.current_decision_level, dated_derivations: SmallVec::One([dated_derivation]), assignments_intersection: AssignmentsIntersection::Derivations(term), }); } } } #[cold] pub fn prioritized_packages(&self) -> impl Iterator, &DP::VS)> { // TODO(konsti): Should we use `self.outdated_priorities` instead? let current_decision_level = self.current_decision_level; self.package_assignments .get_range(self.current_decision_level.0 as usize..) .unwrap() .iter() .filter(move |(_, pa)| pa.highest_decision_level == current_decision_level) .filter_map(|(&p, pa)| { Some((p, pa.assignments_intersection.potential_package_filter()?)) }) } #[cold] pub fn pick_highest_priority_pkg( &mut self, mut prioritizer: impl FnMut(Id, &DP::VS) -> DP::Priority, ) -> Option<(Id, &DP::VS)> { let prioritized_potential_packages = &mut self.prioritized_potential_packages; while let Some(p) = self.outdated_priorities.pop() { let Some(pa) = self.package_assignments.get(&p) else { continue; }; let Some(r) = pa.assignments_intersection.potential_package_filter() else { continue; }; let priority = prioritizer(p, r); prioritized_potential_packages.push(p, (priority, Reverse(p.into_raw() as u32))); } while let Some(p) = self.prioritized_potential_packages.pop().map(|(p, _)| p) { let Some(pa) = self.package_assignments.get(&p) else { continue; }; if let Some(r) = pa.assignments_intersection.potential_package_filter() { return Some((p, r)); } } None } /// If a partial solution has, for every positive derivation, /// a corresponding decision that satisfies that assignment, /// it's a total solution and version solving has succeeded. pub fn extract_solution(&self) -> impl Iterator, DP::V)> + '_ { self.package_assignments .iter() .take(self.current_decision_level.0 as usize) .map(|(&p, pa)| match &pa.assignments_intersection { AssignmentsIntersection::Decision { decision_level: _, version: v, term: _, } => (p, v.clone()), AssignmentsIntersection::Derivations(_) => { // The invariant on the order in `self.package_assignments` was broken. let mut context = String::new(); for (id, assignment) in self .package_assignments .iter() .take(self.current_decision_level.0 as usize) { context.push_str(&format!( " * {:?} {:?}\n", id, assignment.assignments_intersection )); } panic!( "Derivations in the Decision part. Decision level {}\n{}", self.current_decision_level.0, context ) } }) } /// Backtrack the partial solution to a given decision level. pub(crate) fn backtrack(&mut self, decision_level: DecisionLevel) { self.current_decision_level = decision_level; self.package_assignments.retain(|p, pa| { if pa.smallest_decision_level > decision_level { // Remove all entries that have a smallest decision level higher than the backtrack target. false } else if pa.highest_decision_level <= decision_level { // Do not change entries older than the backtrack decision level target. if pa .assignments_intersection .potential_package_filter() .is_some() && self.prioritized_potential_packages.get(p).is_none() { self.outdated_priorities.insert(*p); } true } else { // smallest_decision_level <= decision_level < highest_decision_level // // Since decision_level < highest_decision_level, // We can be certain that there will be no decision in this package assignments // after backtracking, because such decision would have been the last // assignment and it would have the "highest_decision_level". // Truncate the history. while pa.dated_derivations.last().map(|dd| dd.decision_level) > Some(decision_level) { pa.dated_derivations.pop(); } debug_assert!(!pa.dated_derivations.is_empty()); let last = pa.dated_derivations.last().unwrap(); // Update highest_decision_level. pa.highest_decision_level = last.decision_level; // Reset the assignments intersection. pa.assignments_intersection = AssignmentsIntersection::Derivations(last.accumulated_intersection.clone()); self.prioritized_potential_packages.remove(p); if pa.assignments_intersection.term().is_positive() { self.outdated_priorities.insert(*p); } true } }); self.has_ever_backtracked = true; } /// Backtrack the partial solution before a particular package was selected. /// /// This can be used to switch the order of packages if the previous prioritization was bad. /// /// Returns the new decision level on success and an error if the package was not decided on /// yet. pub(crate) fn backtrack_package(&mut self, package: Id) -> Result { let Some(decision_level) = self.package_assignments.get_index_of(&package) else { return Err(()); }; let decision_level = DecisionLevel(decision_level as u32); if decision_level > self.current_decision_level { return Err(()); } debug!( "Package backtracking ot decision level {}", decision_level.0 ); self.backtrack(decision_level); Ok(decision_level) } /// Add a package version as decision if none of its dependencies conflicts with the partial /// solution. /// /// If the resolution never backtracked before, a fast path adds the package version directly /// without checking dependencies. /// /// Returns the incompatibility that caused the current version to be rejected, if a conflict /// in the dependencies was found. pub(crate) fn add_package_version_incompatibilities( &mut self, package: Id, version: DP::V, new_incompatibilities: std::ops::Range>, store: &Arena>, ) -> Option> { if !self.has_ever_backtracked { // Fast path: Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem. // So let's live with a little bit of risk and add the decision without checking the dependencies. // The worst that can happen is we will have to do a full backtrack which only removes this one decision. log::info!("add_decision: {package:?} @ {version} without checking dependencies"); self.add_decision(package, version); return None; } // Check if any of the dependencies preclude deciding on this crate version. let package_term = Term::exact(version.clone()); let relation = |incompat: IncompId| { store[incompat].relation(|p| { // The current package isn't part of the package assignments yet. if p == package { Some(&package_term) } else { self.term_intersection_for_package(p) } }) }; if let Some(satisfied) = Id::range_to_iter(new_incompatibilities) .find(|incompat| relation(*incompat) == Relation::Satisfied) { log::info!( "rejecting decision {package:?} @ {version} because its dependencies conflict" ); Some(satisfied) } else { log::info!("adding decision: {package:?} @ {version}"); self.add_decision(package, version); None } } /// Check if the terms in the partial solution satisfy the incompatibility. pub(crate) fn relation( &self, incompat: &Incompatibility, ) -> Relation { incompat.relation(|package| self.term_intersection_for_package(package)) } /// Retrieve intersection of terms related to package. pub fn term_intersection_for_package(&self, package: Id) -> Option<&Term> { self.package_assignments .get(&package) .map(|pa| pa.assignments_intersection.term()) } /// Figure out if the satisfier and previous satisfier are of different decision levels. #[allow(clippy::type_complexity)] pub(crate) fn satisfier_search( &self, incompat: &Incompatibility, store: &Arena>, ) -> (Id, SatisfierSearch) { let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments); let (&satisfier_package, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map .iter() .max_by_key(|(_p, (_, global_index, _))| global_index) .unwrap(); let previous_satisfier_level = Self::find_previous_satisfier( incompat, satisfier_package, satisfied_map, &self.package_assignments, store, ); let search_result = if previous_satisfier_level >= satisfier_decision_level { SatisfierSearch::SameDecisionLevels { satisfier_cause: satisfier_cause.unwrap(), } } else { SatisfierSearch::DifferentDecisionLevels { previous_satisfier_level, } }; (satisfier_package, search_result) } /// A satisfier is the earliest assignment in partial solution such that the incompatibility /// is satisfied by the partial solution up to and including that assignment. /// /// Returns a map indicating for each package term, when that was first satisfied in history. /// If we effectively found a satisfier, the returned map must be the same size that incompat. /// /// Question: This is possible since we added a "global_index" to every dated_derivation. /// It would be nice if we could get rid of it, but I don't know if then it will be possible /// to return a coherent previous_satisfier_level. #[allow(clippy::type_complexity)] fn find_satisfier( incompat: &Incompatibility, package_assignments: &FnvIndexMap, PackageAssignments>, ) -> SatisfiedMap { let mut satisfied = SmallMap::Empty; for (package, incompat_term) in incompat.iter() { let pa = package_assignments.get(&package).expect("Must exist"); satisfied.insert(package, pa.satisfier(package, &incompat_term.negate())); } satisfied } /// Earliest assignment in the partial solution before satisfier /// such that incompatibility is satisfied by the partial solution up to /// and including that assignment plus satisfier. #[allow(clippy::type_complexity)] fn find_previous_satisfier( incompat: &Incompatibility, satisfier_package: Id, mut satisfied_map: SatisfiedMap, package_assignments: &FnvIndexMap, PackageAssignments>, store: &Arena>, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. let satisfier_pa = package_assignments.get(&satisfier_package).unwrap(); let (satisfier_cause, _gidx, _dl) = satisfied_map.get(&satisfier_package).unwrap(); let accum_term = if let &Some(cause) = satisfier_cause { store[cause].get(satisfier_package).unwrap().negate() } else { match &satisfier_pa.assignments_intersection { AssignmentsIntersection::Derivations(_) => panic!("must be a decision"), AssignmentsIntersection::Decision { decision_level: _, version: _, term, } => term.clone(), } }; let incompat_term = incompat .get(satisfier_package) .expect("satisfier package not in incompat"); satisfied_map.insert( satisfier_package, satisfier_pa.satisfier( satisfier_package, &accum_term.intersection(&incompat_term.negate()), ), ); // Finally, let's identify the decision level of that previous satisfier. let (_, &(_, _, decision_level)) = satisfied_map .iter() .max_by_key(|(_p, (_, global_index, _))| global_index) .unwrap(); decision_level.max(DecisionLevel(1)) } pub(crate) fn current_decision_level(&self) -> DecisionLevel { self.current_decision_level } /// Retrieve the constraints on a package that will not change. pub fn unchanging_term_for_package(&self, package: Id) -> Option<&Term> { let pa = self.package_assignments.get(&package)?; let idx_newer = pa .dated_derivations .as_slice() .partition_point(|dd| dd.decision_level <= DecisionLevel(1)); let idx = idx_newer.checked_sub(1)?; Some(&pa.dated_derivations[idx].accumulated_intersection) } } impl PackageAssignments { fn satisfier( &self, package: Id

, start_term: &Term, ) -> (Option>, u32, DecisionLevel) { let empty = Term::empty(); // Indicate if we found a satisfier in the list of derivations, otherwise it will be the decision. let idx = self .dated_derivations .as_slice() .partition_point(|dd| !dd.accumulated_intersection.is_disjoint(start_term)); if let Some(dd) = self.dated_derivations.get(idx) { debug_assert_eq!(dd.accumulated_intersection.intersection(start_term), empty); return (Some(dd.cause), dd.global_index, dd.decision_level); } // If it wasn't found in the derivations, // it must be the decision which is last (if called in the right context). match &self.assignments_intersection { AssignmentsIntersection::Decision { decision_level: global_index, version: _, term: _, } => (None, *global_index, self.highest_decision_level), AssignmentsIntersection::Derivations(accumulated_intersection) => { unreachable!( concat!( "while processing package {:?}: ", "accum_term = {} has overlap with incompat_term = {}, ", "which means the last assignment should have been a decision, ", "but instead it was a derivation. This shouldn't be possible! ", "(Maybe your Version ordering is broken?)" ), package, accumulated_intersection, start_term ) } } } } impl AssignmentsIntersection { /// Returns the term intersection of all assignments (decision included). fn term(&self) -> &Term { match self { Self::Decision { decision_level: _, version: _, term, } => term, Self::Derivations(term) => term, } } /// A package is a potential pick if there isn't an already /// selected version (no "decision") /// and if it contains at least one positive derivation term /// in the partial solution. fn potential_package_filter(&self) -> Option<&VS> { match self { Self::Decision { .. } => None, Self::Derivations(term_intersection) => { if term_intersection.is_positive() { Some(term_intersection.unwrap_positive()) } else { None } } } } } astral-pubgrub-0.3.3/src/internal/small_map.rs000064400000000000000000000150751046102023000175070ustar 00000000000000use std::hash::Hash; use crate::Map; #[derive(Debug, Clone, Default)] pub(crate) enum SmallMap { #[default] Empty, One([(K, V); 1]), Two([(K, V); 2]), Flexible(Map), } impl SmallMap { pub(crate) fn get(&self, key: &K) -> Option<&V> { match self { Self::Empty => None, Self::One([(k, v)]) if k == key => Some(v), Self::One(_) => None, Self::Two([(k1, v1), _]) if key == k1 => Some(v1), Self::Two([_, (k2, v2)]) if key == k2 => Some(v2), Self::Two(_) => None, Self::Flexible(data) => data.get(key), } } pub(crate) fn get_mut(&mut self, key: &K) -> Option<&mut V> { match self { Self::Empty => None, Self::One([(k, v)]) if k == key => Some(v), Self::One(_) => None, Self::Two([(k1, v1), _]) if key == k1 => Some(v1), Self::Two([_, (k2, v2)]) if key == k2 => Some(v2), Self::Two(_) => None, Self::Flexible(data) => data.get_mut(key), } } pub(crate) fn remove(&mut self, key: &K) -> Option { let out; *self = match std::mem::take(self) { Self::Empty => { out = None; Self::Empty } Self::One([(k, v)]) => { if key == &k { out = Some(v); Self::Empty } else { out = None; Self::One([(k, v)]) } } Self::Two([(k1, v1), (k2, v2)]) => { if key == &k1 { out = Some(v1); Self::One([(k2, v2)]) } else if key == &k2 { out = Some(v2); Self::One([(k1, v1)]) } else { out = None; Self::Two([(k1, v1), (k2, v2)]) } } Self::Flexible(mut data) => { out = data.remove(key); Self::Flexible(data) } }; out } pub(crate) fn insert(&mut self, key: K, value: V) { *self = match std::mem::take(self) { Self::Empty => Self::One([(key, value)]), Self::One([(k, v)]) => { if key == k { Self::One([(k, value)]) } else { Self::Two([(k, v), (key, value)]) } } Self::Two([(k1, v1), (k2, v2)]) => { if key == k1 { Self::Two([(k1, value), (k2, v2)]) } else if key == k2 { Self::Two([(k1, v1), (k2, value)]) } else { let mut data: Map = Map::with_capacity_and_hasher(3, Default::default()); data.insert(key, value); data.insert(k1, v1); data.insert(k2, v2); Self::Flexible(data) } } Self::Flexible(mut data) => { data.insert(key, value); Self::Flexible(data) } }; } /// Returns a reference to the value for one key and a copy of the map without the key. /// /// This is an optimization over the following, where we only need a reference to `t1`. It /// avoids cloning and then drop the ranges in each `prior_cause` call. /// ```ignore /// let mut package_terms = package_terms.clone(); // let t1 = package_terms.remove(package).unwrap(); /// ``` pub(crate) fn split_one(&self, key: &K) -> Option<(&V, Self)> where K: Clone, V: Clone, { match self { Self::Empty => None, Self::One([(k, v)]) => { if k == key { Some((v, Self::Empty)) } else { None } } Self::Two([(k1, v1), (k2, v2)]) => { if k1 == key { Some((v1, Self::One([(k2.clone(), v2.clone())]))) } else if k2 == key { Some((v2, Self::One([(k1.clone(), v1.clone())]))) } else { None } } Self::Flexible(map) => { if let Some(value) = map.get(key) { let mut map = map.clone(); map.remove(key).unwrap(); Some((value, Self::Flexible(map))) } else { None } } } } } impl SmallMap { /// Merge two hash maps. /// /// When a key is common to both, /// apply the provided function to both values. /// If the result is None, remove that key from the merged map, /// otherwise add the content of the `Some(_)`. pub(crate) fn merge<'a>( &'a mut self, map_2: impl Iterator, f: impl Fn(&V, &V) -> Option, ) { for (key, val_2) in map_2 { match self.get_mut(key) { None => { self.insert(key.clone(), val_2.clone()); } Some(val_1) => match f(val_1, val_2) { None => { self.remove(key); } Some(merged_value) => *val_1 = merged_value, }, } } } } impl SmallMap { pub(crate) fn len(&self) -> usize { match self { Self::Empty => 0, Self::One(_) => 1, Self::Two(_) => 2, Self::Flexible(data) => data.len(), } } } enum IterSmallMap<'a, K, V> { Inline(std::slice::Iter<'a, (K, V)>), Map(std::collections::hash_map::Iter<'a, K, V>), } impl<'a, K: 'a, V: 'a> Iterator for IterSmallMap<'a, K, V> { type Item = (&'a K, &'a V); fn next(&mut self) -> Option { match self { IterSmallMap::Inline(inner) => inner.next().map(|(k, v)| (k, v)), IterSmallMap::Map(inner) => inner.next(), } } } impl SmallMap { pub(crate) fn iter(&self) -> impl Iterator { match self { Self::Empty => IterSmallMap::Inline([].iter()), Self::One(data) => IterSmallMap::Inline(data.iter()), Self::Two(data) => IterSmallMap::Inline(data.iter()), Self::Flexible(data) => IterSmallMap::Map(data.iter()), } } } astral-pubgrub-0.3.3/src/internal/small_vec.rs000064400000000000000000000133461046102023000175060ustar 00000000000000use std::fmt; use std::hash::{Hash, Hasher}; use std::ops::Deref; #[derive(Clone, Default)] pub enum SmallVec { #[default] Empty, One([T; 1]), Two([T; 2]), Flexible(Vec), } impl SmallVec { pub fn empty() -> Self { Self::Empty } pub fn one(t: T) -> Self { Self::One([t]) } pub fn as_slice(&self) -> &[T] { match self { Self::Empty => &[], Self::One(v) => v, Self::Two(v) => v, Self::Flexible(v) => v, } } pub fn as_mut_slice(&mut self) -> &mut [T] { match self { Self::Empty => &mut [], Self::One(v) => v, Self::Two(v) => v, Self::Flexible(v) => v, } } pub fn push(&mut self, new: T) { *self = match std::mem::take(self) { Self::Empty => Self::One([new]), Self::One([v1]) => Self::Two([v1, new]), Self::Two([v1, v2]) => Self::Flexible(vec![v1, v2, new]), Self::Flexible(mut v) => { v.push(new); Self::Flexible(v) } } } pub fn pop(&mut self) -> Option { match std::mem::take(self) { Self::Empty => None, Self::One([v1]) => { *self = Self::Empty; Some(v1) } Self::Two([v1, v2]) => { *self = Self::One([v1]); Some(v2) } Self::Flexible(mut v) => { let out = v.pop(); *self = Self::Flexible(v); out } } } pub fn clear(&mut self) { if let Self::Flexible(mut v) = std::mem::take(self) { v.clear(); *self = Self::Flexible(v); } // else: self already eq Empty from the take } pub fn iter(&self) -> std::slice::Iter<'_, T> { self.as_slice().iter() } } impl Deref for SmallVec { type Target = [T]; fn deref(&self) -> &Self::Target { self.as_slice() } } impl<'a, T> IntoIterator for &'a SmallVec { type Item = &'a T; type IntoIter = std::slice::Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl Eq for SmallVec {} impl PartialEq for SmallVec { fn eq(&self, other: &Self) -> bool { self.as_slice() == other.as_slice() } } impl fmt::Debug for SmallVec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_slice().fmt(f) } } impl Hash for SmallVec { fn hash(&self, state: &mut H) { self.len().hash(state); Hash::hash_slice(self.as_slice(), state); } } #[cfg(feature = "serde")] impl serde::Serialize for SmallVec { fn serialize(&self, s: S) -> Result { serde::Serialize::serialize(self.as_slice(), s) } } #[cfg(feature = "serde")] impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for SmallVec { fn deserialize>(d: D) -> Result { struct SmallVecVisitor { marker: std::marker::PhantomData, } impl<'de, T> serde::de::Visitor<'de> for SmallVecVisitor where T: serde::Deserialize<'de>, { type Value = SmallVec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a sequence") } fn visit_seq(self, mut seq: A) -> Result where A: serde::de::SeqAccess<'de>, { let mut values = SmallVec::empty(); while let Some(value) = seq.next_element()? { values.push(value); } Ok(values) } } let visitor = SmallVecVisitor { marker: Default::default(), }; d.deserialize_seq(visitor) } } impl IntoIterator for SmallVec { type Item = T; type IntoIter = SmallVecIntoIter; fn into_iter(self) -> Self::IntoIter { match self { SmallVec::Empty => SmallVecIntoIter::Empty, SmallVec::One(a) => SmallVecIntoIter::One(a.into_iter()), SmallVec::Two(a) => SmallVecIntoIter::Two(a.into_iter()), SmallVec::Flexible(v) => SmallVecIntoIter::Flexible(v.into_iter()), } } } pub enum SmallVecIntoIter { Empty, One(<[T; 1] as IntoIterator>::IntoIter), Two(<[T; 2] as IntoIterator>::IntoIter), Flexible( as IntoIterator>::IntoIter), } impl Iterator for SmallVecIntoIter { type Item = T; fn next(&mut self) -> Option { match self { SmallVecIntoIter::Empty => None, SmallVecIntoIter::One(it) => it.next(), SmallVecIntoIter::Two(it) => it.next(), SmallVecIntoIter::Flexible(it) => it.next(), } } } // TESTS ####################################################################### #[cfg(test)] pub mod tests { use proptest::prelude::*; use super::*; proptest! { #[test] fn push_and_pop(commands: Vec>) { let mut v = vec![]; let mut sv = SmallVec::Empty; for command in commands { match command { Some(i) => { v.push(i); sv.push(i); } None => { assert_eq!(v.pop(), sv.pop()); } } assert_eq!(v.as_slice(), sv.as_slice()); } } } } astral-pubgrub-0.3.3/src/lib.rs000064400000000000000000000234611046102023000144720ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! PubGrub version solving algorithm. //! //! Version solving consists in efficiently finding a set of packages and versions //! that satisfy all the constraints of a given project dependencies. //! In addition, when that is not possible, //! we should try to provide a very human-readable and clear //! explanation as to why that failed. //! //! # Basic example //! //! Let's imagine that we are building a user interface //! with a menu containing dropdowns with some icons, //! icons that we are also directly using in other parts of the interface. //! For this scenario our direct dependencies are `menu` and `icons`, //! but the complete set of dependencies looks like follows: //! //! - `root` depends on `menu` and `icons` //! - `menu` depends on `dropdown` //! - `dropdown` depends on `icons` //! - `icons` has no dependency //! //! We can model that scenario with this library as follows //! ``` //! # use pubgrub::{OfflineDependencyProvider, resolve, Ranges}; //! //! type NumVS = Ranges; //! //! let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! //! dependency_provider.add_dependencies( //! "root", //! 1u32, //! [("menu", Ranges::full()), ("icons", Ranges::full())], //! ); //! dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Ranges::full())]); //! dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Ranges::full())]); //! dependency_provider.add_dependencies("icons", 1u32, []); //! //! // Run the algorithm. //! let solution = resolve(&dependency_provider, "root", 1u32).unwrap(); //! ``` //! //! # Package and Version flexibility //! //! The [OfflineDependencyProvider] used in that example is generic over the way package names, //! version requirements, and version numbers are represented. //! //! The first bound is the type of package names. It can be anything that implements our [Package] trait. //! The [Package] trait is automatic if the type already implements //! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). //! So things like [String] will work out of the box. //! //! The second bound is the type of package requirements. It can be anything that implements our [VersionSet] trait. //! This trait is used to figure out how version requirements are combined. //! If the normal [Ord]/[PartialEq] operations are all that is needed for requirements, our [Ranges] type will work. //! //! The chosen `VersionSet` in turn specifies what can be used for version numbers. //! This type needs to at least implement [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display). //! For convenience, this library provides [SemanticVersion] that implements the basics of semantic versioning rules. //! //! # DependencyProvider trait //! //! In our previous example we used the //! [OfflineDependencyProvider], //! which is a basic implementation of the [DependencyProvider] trait. //! //! But we might want to implement the [DependencyProvider] //! trait for our own type. //! Let's say that we will use [String] for packages, //! and [SemanticVersion] for versions. //! This may be done quite easily by implementing the three following functions. //! ``` //! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion, Ranges, //! # DependencyConstraints, Map, PackageResolutionStatistics}; //! # use std::error::Error; //! # use std::borrow::Borrow; //! # use std::convert::Infallible; //! # //! # struct MyDependencyProvider; //! # //! type SemVS = Ranges; //! //! impl DependencyProvider for MyDependencyProvider { //! fn choose_version(&self, package: &String, range: &SemVS) -> Result, Infallible> { //! unimplemented!() //! } //! //! type Priority = usize; //! fn prioritize(&self, package: &String, range: &SemVS, conflicts_counts: &PackageResolutionStatistics) -> Self::Priority { //! unimplemented!() //! } //! //! fn get_dependencies( //! &self, //! package: &String, //! version: &SemanticVersion, //! ) -> Result, Infallible> { //! Ok(Dependencies::Available(DependencyConstraints::default())) //! } //! //! type Err = Infallible; //! type P = String; //! type V = SemanticVersion; //! type VS = SemVS; //! type M = String; //! } //! ``` //! //! The first method //! [choose_version](DependencyProvider::choose_version) //! chooses a version compatible with the provided range for a package. //! The second method //! [prioritize](DependencyProvider::prioritize) //! in which order different packages should be chosen. //! Usually prioritizing packages //! with the fewest number of compatible versions speeds up resolution. //! But in general you are free to employ whatever strategy suits you best //! to pick a package and a version. //! //! The third method [get_dependencies](DependencyProvider::get_dependencies) //! aims at retrieving the dependencies of a given package at a given version. //! //! In a real scenario, these two methods may involve reading the file system //! or doing network request, so you may want to hold a cache in your //! [DependencyProvider] implementation. //! How exactly this could be achieved is shown in `CachingDependencyProvider` //! (see `examples/caching_dependency_provider.rs`). //! You could also use the [OfflineDependencyProvider] //! type defined by the crate as guidance, //! but you are free to use whatever approach makes sense in your situation. //! //! # Solution and error reporting //! //! When everything goes well, the algorithm finds and returns the complete //! set of direct and indirect dependencies satisfying all the constraints. //! The packages and versions selected are returned as //! [SelectedDependencies](SelectedDependencies). //! But sometimes there is no solution because dependencies are incompatible. //! In such cases, [resolve(...)](resolve) returns a //! [PubGrubError::NoSolution(derivation_tree)](PubGrubError::NoSolution), //! where the provided derivation tree is a custom binary tree //! containing the full chain of reasons why there is no solution. //! //! All the items in the tree are called incompatibilities //! and may be of two types, either "external" or "derived". //! Leaves of the tree are external incompatibilities, //! and nodes are derived. //! External incompatibilities have reasons that are independent //! of the way this algorithm is implemented such as //! - dependencies: "package_a" at version 1 depends on "package_b" at version 4 //! - missing dependencies: dependencies of "package_a" are unavailable //! - absence of version: there is no version of "package_a" in the range [3.1.0 4.0.0[ //! //! Derived incompatibilities are obtained during the algorithm execution by deduction, //! such as if "a" depends on "b" and "b" depends on "c", "a" depends on "c". //! //! This crate defines a [Reporter] trait, with an associated //! [Output](Reporter::Output) type and a single method. //! ``` //! # use pubgrub::{Package, VersionSet, DerivationTree}; //! # use std::fmt::{Debug, Display}; //! # //! pub trait Reporter { //! type Output; //! //! fn report(derivation_tree: &DerivationTree) -> Self::Output; //! } //! ``` //! Implementing a [Reporter] may involve a lot of heuristics //! to make the output human-readable and natural. //! For convenience, we provide a default implementation //! [DefaultStringReporter] that outputs the report as a [String]. //! You may use it as follows: //! ``` //! # use pubgrub::{resolve, OfflineDependencyProvider, DefaultStringReporter, Reporter, PubGrubError, Ranges}; //! # //! # type NumVS = Ranges; //! # //! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let root_package = "root"; //! # let root_version = 1u32; //! # //! match resolve(&dependency_provider, root_package, root_version) { //! Ok(solution) => println!("{:?}", solution), //! Err(PubGrubError::NoSolution(mut derivation_tree)) => { //! derivation_tree.collapse_no_versions(); //! eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); //! } //! Err(err) => panic!("{:?}", err), //! }; //! ``` //! Notice that we also used //! [collapse_no_versions()](DerivationTree::collapse_no_versions) above. //! This method simplifies the derivation tree to get rid of the //! [NoVersions](External::NoVersions) //! external incompatibilities in the derivation tree. //! So instead of seeing things like this in the report: //! ```txt //! Because there is no version of foo in 1.0.1 <= v < 2.0.0 //! and foo 1.0.0 depends on bar 2.0.0 <= v < 3.0.0, //! foo 1.0.0 <= v < 2.0.0 depends on bar 2.0.0 <= v < 3.0.0. //! ``` //! you may have directly: //! ```txt //! foo 1.0.0 <= v < 2.0.0 depends on bar 2.0.0 <= v < 3.0.0. //! ``` //! Beware though that if you are using some kind of offline mode //! with a cache, you may want to know that some versions //! do not exist in your cache. #![warn(missing_docs)] mod error; mod package; mod provider; mod report; mod solver; mod term; mod type_aliases; mod version; mod version_set; pub use error::{NoSolutionError, PubGrubError}; pub use package::Package; pub use provider::OfflineDependencyProvider; pub use report::{ DefaultStringReportFormatter, DefaultStringReporter, DerivationTree, Derived, External, ReportFormatter, Reporter, }; pub use solver::{Dependencies, DependencyProvider, PackageResolutionStatistics, resolve}; pub use term::Term; pub use type_aliases::{DependencyConstraints, Map, SelectedDependencies, Set}; pub use version::{SemanticVersion, VersionParseError}; pub use version_ranges::Ranges; #[deprecated(note = "Use `Ranges` instead")] pub use version_ranges::Ranges as Range; pub use version_set::VersionSet; // uv-specific additions pub use internal::{Id, IncompId, Incompatibility, Kind, State}; mod internal; astral-pubgrub-0.3.3/src/package.rs000064400000000000000000000011731046102023000153130ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! Trait for identifying packages. //! Automatically implemented for traits implementing //! [Clone] + [Eq] + [Hash] + [Debug] + [Display]. use std::fmt::{Debug, Display}; use std::hash::Hash; /// Trait for identifying packages. /// Automatically implemented for types already implementing /// [Clone] + [Eq] + [Hash] + [Debug] + [Display]. pub trait Package: Clone + Eq + Hash + Debug + Display {} /// Automatically implement the Package trait for any type /// that already implement [Clone] + [Eq] + [Hash] + [Debug] + [Display]. impl Package for T {} astral-pubgrub-0.3.3/src/provider.rs000064400000000000000000000112411046102023000155470ustar 00000000000000use std::cmp::Reverse; use std::collections::BTreeMap; use std::convert::Infallible; use crate::{ Dependencies, DependencyConstraints, DependencyProvider, Map, Package, PackageResolutionStatistics, VersionSet, }; /// A basic implementation of [DependencyProvider]. #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( feature = "serde", serde(bound( serialize = "VS::V: serde::Serialize, VS: serde::Serialize, P: serde::Serialize", deserialize = "VS::V: serde::Deserialize<'de>, VS: serde::Deserialize<'de>, P: serde::Deserialize<'de>" )) )] #[cfg_attr(feature = "serde", serde(transparent))] pub struct OfflineDependencyProvider { dependencies: Map>>, } impl OfflineDependencyProvider { /// Creates an empty OfflineDependencyProvider with no dependencies. pub fn new() -> Self { Self { dependencies: Map::default(), } } /// Registers the dependencies of a package and version pair. /// Dependencies must be added with a single call to /// [add_dependencies](OfflineDependencyProvider::add_dependencies). /// All subsequent calls to /// [add_dependencies](OfflineDependencyProvider::add_dependencies) for a given /// package version pair will replace the dependencies by the new ones. /// /// The API does not allow to add dependencies one at a time to uphold an assumption that /// [OfflineDependencyProvider.get_dependencies(p, v)](OfflineDependencyProvider::get_dependencies) /// provides all dependencies of a given package (p) and version (v) pair. pub fn add_dependencies>( &mut self, package: P, version: impl Into, dependencies: I, ) { let package_deps = dependencies.into_iter().collect(); let v = version.into(); *self .dependencies .entry(package) .or_default() .entry(v) .or_default() = package_deps; } /// Lists packages that have been saved. pub fn packages(&self) -> impl Iterator { self.dependencies.keys() } /// Lists versions of saved packages in sorted order. /// Returns [None] if no information is available regarding that package. pub fn versions(&self, package: &P) -> Option> { self.dependencies.get(package).map(|k| k.keys()) } /// Lists dependencies of a given package and version. /// Returns [None] if no information is available regarding that package and version pair. fn dependencies(&self, package: &P, version: &VS::V) -> Option> { self.dependencies.get(package)?.get(version).cloned() } } /// An implementation of [DependencyProvider] that /// contains all dependency information available in memory. /// Currently packages are picked with the fewest versions contained in the constraints first. /// But, that may change in new versions if better heuristics are found. /// Versions are picked with the newest versions first. impl DependencyProvider for OfflineDependencyProvider { type P = P; type V = VS::V; type VS = VS; type M = String; type Err = Infallible; #[inline] fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { Ok(self .dependencies .get(package) .and_then(|versions| versions.keys().rev().find(|v| range.contains(v)).cloned())) } type Priority = (u32, Reverse); #[inline] fn prioritize( &self, package: &Self::P, range: &Self::VS, package_statistics: &PackageResolutionStatistics, ) -> Self::Priority { let version_count = self .dependencies .get(package) .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) .unwrap_or(0); if version_count == 0 { return (u32::MAX, Reverse(0)); } (package_statistics.conflict_count(), Reverse(version_count)) } #[inline] fn get_dependencies( &self, package: &P, version: &VS::V, ) -> Result, Infallible> { Ok(match self.dependencies(package, version) { None => { Dependencies::Unavailable("its dependencies could not be determined".to_string()) } Some(dependencies) => Dependencies::Available(dependencies), }) } } astral-pubgrub-0.3.3/src/report.rs000064400000000000000000000610061046102023000152340ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! Build a report as clear as possible as to why //! dependency solving failed. use std::fmt::{self, Debug, Display}; use std::ops::Deref; use std::sync::Arc; use crate::{Map, Package, Set, Term, VersionSet}; /// Reporter trait. pub trait Reporter { /// Output type of the report. type Output; /// Generate a report from the derivation tree /// describing the resolution failure using the default formatter. fn report(derivation_tree: &DerivationTree) -> Self::Output; /// Generate a report from the derivation tree /// describing the resolution failure using a custom formatter. fn report_with_formatter( derivation_tree: &DerivationTree, formatter: &impl ReportFormatter, ) -> Self::Output; } /// Derivation tree resulting in the impossibility to solve the dependencies of our root package. #[derive(Debug, Clone)] pub enum DerivationTree { /// External incompatibility. External(External), /// Incompatibility derived from two others. Derived(Derived), } /// Incompatibility that is not derived from other incompatibilities. #[derive(Debug, Clone)] pub enum External { /// Initial incompatibility aiming at picking the root package for the first decision. NotRoot(P, VS::V), /// There are no versions in the given set for this package. NoVersions(P, VS), /// Incompatibility coming from the dependencies of a given package. FromDependencyOf(P, VS, P, VS), /// The package is unusable for reasons outside pubgrub. Custom(P, VS, M), } /// Incompatibility derived from two others. #[derive(Debug, Clone)] pub struct Derived { /// Terms of the incompatibility. pub terms: Map>, /// Indicate if the incompatibility is present multiple times in the derivation tree. /// /// If that is the case, the number is a unique id. This can be used to only explain this /// incompatibility once, then refer to the explanation for the other times. pub shared_id: Option, /// First cause. pub cause1: Arc>, /// Second cause. pub cause2: Arc>, } impl DerivationTree { /// Get all packages referred to in the derivation tree. pub fn packages(&self) -> Set<&P> { let mut packages = Set::default(); match self { Self::External(external) => match external { External::FromDependencyOf(p, _, p2, _) => { packages.insert(p); packages.insert(p2); } External::NoVersions(p, _) | External::NotRoot(p, _) | External::Custom(p, _, _) => { packages.insert(p); } }, Self::Derived(derived) => { // Less efficient than recursing with a `&mut Set<&P>`, but it's sufficient for // small to medium-sized inputs such as a single `DerivationTree`. packages.extend(derived.terms.keys()); packages.extend(derived.cause1.packages().iter()); packages.extend(derived.cause2.packages().iter()); } } packages } /// Merge the [NoVersions](External::NoVersions) external incompatibilities /// with the other one they are matched with /// in a derived incompatibility. /// This cleans up quite nicely the generated report. /// You might want to do this if you know that the /// [DependencyProvider](crate::solver::DependencyProvider) /// was not run in some kind of offline mode that may not /// have access to all versions existing. pub fn collapse_no_versions(&mut self) { match self { DerivationTree::External(_) => {} DerivationTree::Derived(derived) => { match ( Arc::make_mut(&mut derived.cause1), Arc::make_mut(&mut derived.cause2), ) { (DerivationTree::External(External::NoVersions(p, r)), ref mut cause2) => { cause2.collapse_no_versions(); *self = cause2 .clone() .merge_no_versions(p.to_owned(), r.to_owned()) .unwrap_or_else(|| self.to_owned()); } (ref mut cause1, DerivationTree::External(External::NoVersions(p, r))) => { cause1.collapse_no_versions(); *self = cause1 .clone() .merge_no_versions(p.to_owned(), r.to_owned()) .unwrap_or_else(|| self.to_owned()); } _ => { Arc::make_mut(&mut derived.cause1).collapse_no_versions(); Arc::make_mut(&mut derived.cause2).collapse_no_versions(); } } } } } fn merge_no_versions(self, package: P, set: VS) -> Option { match self { // TODO: take care of the Derived case. // Once done, we can remove the Option. DerivationTree::Derived(_) => Some(self), DerivationTree::External(External::NotRoot(_, _)) => { panic!("How did we end up with a NoVersions merged with a NotRoot?") } // // Cannot be merged because the reason may not match DerivationTree::External(External::NoVersions(_, _)) => None, DerivationTree::External(External::Custom(_, r, reason)) => Some( DerivationTree::External(External::Custom(package, set.union(&r), reason)), ), DerivationTree::External(External::FromDependencyOf(p1, r1, p2, r2)) => { if p1 == package { Some(DerivationTree::External(External::FromDependencyOf( p1, r1.union(&set), p2, r2, ))) } else { Some(DerivationTree::External(External::FromDependencyOf( p1, r1, p2, r2.union(&set), ))) } } } } } impl Display for External { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NotRoot(package, version) => { write!(f, "we are solving dependencies of {package} {version}") } Self::NoVersions(package, set) => { if set == &VS::full() { write!(f, "there is no available version for {package}") } else { write!(f, "there is no version of {package} in {set}") } } Self::Custom(package, set, metadata) => { if set == &VS::full() { write!(f, "dependencies of {package} are unavailable {metadata}") } else { write!( f, "dependencies of {package} at version {set} are unavailable {metadata}" ) } } Self::FromDependencyOf(p, set_p, dep, set_dep) => { if set_p == &VS::full() && set_dep == &VS::full() { write!(f, "{p} depends on {dep}") } else if set_p == &VS::full() { write!(f, "{p} depends on {dep} {set_dep}") } else if set_dep == &VS::full() { write!(f, "{p} {set_p} depends on {dep}") } else { write!(f, "{p} {set_p} depends on {dep} {set_dep}") } } } } } /// Trait for formatting outputs in the reporter. pub trait ReportFormatter { /// Output type of the report. type Output; /// Format an [External] incompatibility. fn format_external(&self, external: &External) -> Self::Output; /// Format terms of an incompatibility. fn format_terms(&self, terms: &Map>) -> Self::Output; /// Simplest case, we just combine two external incompatibilities. fn explain_both_external( &self, external1: &External, external2: &External, current_terms: &Map>, ) -> Self::Output; /// Both causes have already been explained so we use their refs. fn explain_both_ref( &self, ref_id1: usize, derived1: &Derived, ref_id2: usize, derived2: &Derived, current_terms: &Map>, ) -> Self::Output; /// One cause is derived (already explained so one-line), /// the other is a one-line external cause, /// and finally we conclude with the current incompatibility. fn explain_ref_and_external( &self, ref_id: usize, derived: &Derived, external: &External, current_terms: &Map>, ) -> Self::Output; /// Add an external cause to the chain of explanations. fn and_explain_external( &self, external: &External, current_terms: &Map>, ) -> Self::Output; /// Add an already explained incompat to the chain of explanations. fn and_explain_ref( &self, ref_id: usize, derived: &Derived, current_terms: &Map>, ) -> Self::Output; /// Add an already explained incompat to the chain of explanations. fn and_explain_prior_and_external( &self, prior_external: &External, external: &External, current_terms: &Map>, ) -> Self::Output; } /// Default formatter for the default reporter. #[derive(Default, Debug)] pub struct DefaultStringReportFormatter; impl ReportFormatter for DefaultStringReportFormatter { type Output = String; fn format_external(&self, external: &External) -> String { external.to_string() } fn format_terms(&self, terms: &Map>) -> Self::Output { let terms_vec: Vec<_> = terms.iter().collect(); match terms_vec.as_slice() { [] => "version solving failed".into(), // TODO: special case when that unique package is root. [(package, Term::Positive(range))] => format!("{package} {range} is forbidden"), [(package, Term::Negative(range))] => format!("{package} {range} is mandatory"), [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => self.format_external( &External::<_, _, M>::FromDependencyOf(p1, r1.clone(), p2, r2.clone()), ), [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => self.format_external( &External::<_, _, M>::FromDependencyOf(p2, r2.clone(), p1, r1.clone()), ), slice => { let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{p} {t}")).collect(); str_terms.join(", ") + " are incompatible" } } } /// Simplest case, we just combine two external incompatibilities. fn explain_both_external( &self, external1: &External, external2: &External, current_terms: &Map>, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} and {}, {}.", self.format_external(external1), self.format_external(external2), ReportFormatter::::format_terms(self, current_terms) ) } /// Both causes have already been explained so we use their refs. fn explain_both_ref( &self, ref_id1: usize, derived1: &Derived, ref_id2: usize, derived2: &Derived, current_terms: &Map>, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {} ({}), {}.", ReportFormatter::::format_terms(self, &derived1.terms), ref_id1, ReportFormatter::::format_terms(self, &derived2.terms), ref_id2, ReportFormatter::::format_terms(self, current_terms) ) } /// One cause is derived (already explained so one-line), /// the other is a one-line external cause, /// and finally we conclude with the current incompatibility. fn explain_ref_and_external( &self, ref_id: usize, derived: &Derived, external: &External, current_terms: &Map>, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {}, {}.", ReportFormatter::::format_terms(self, &derived.terms), ref_id, self.format_external(external), ReportFormatter::::format_terms(self, current_terms) ) } /// Add an external cause to the chain of explanations. fn and_explain_external( &self, external: &External, current_terms: &Map>, ) -> String { format!( "And because {}, {}.", self.format_external(external), ReportFormatter::::format_terms(self, current_terms) ) } /// Add an already explained incompat to the chain of explanations. fn and_explain_ref( &self, ref_id: usize, derived: &Derived, current_terms: &Map>, ) -> String { format!( "And because {} ({}), {}.", ReportFormatter::::format_terms(self, &derived.terms), ref_id, ReportFormatter::::format_terms(self, current_terms) ) } /// Add an already explained incompat to the chain of explanations. fn and_explain_prior_and_external( &self, prior_external: &External, external: &External, current_terms: &Map>, ) -> String { format!( "And because {} and {}, {}.", self.format_external(prior_external), self.format_external(external), ReportFormatter::::format_terms(self, current_terms) ) } } /// Default reporter able to generate an explanation as a [String]. pub struct DefaultStringReporter { /// Number of explanations already with a line reference. ref_count: usize, /// Shared nodes that have already been marked with a line reference. /// The incompatibility ids are the keys, and the line references are the values. shared_with_ref: Map, /// Accumulated lines of the report already generated. lines: Vec, } impl DefaultStringReporter { /// Initialize the reporter. fn new() -> Self { Self { ref_count: 0, shared_with_ref: Map::default(), lines: Vec::new(), } } fn build_recursive< P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display, F: ReportFormatter, >( &mut self, derived: &Derived, formatter: &F, ) { self.build_recursive_helper(derived, formatter); if let Some(id) = derived.shared_id { #[allow(clippy::map_entry)] // `add_line_ref` not compatible with proposed fix. if !self.shared_with_ref.contains_key(&id) { self.add_line_ref(); self.shared_with_ref.insert(id, self.ref_count); } }; } fn build_recursive_helper< P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display, F: ReportFormatter, >( &mut self, current: &Derived, formatter: &F, ) { match (current.cause1.deref(), current.cause2.deref()) { (DerivationTree::External(external1), DerivationTree::External(external2)) => { // Simplest case, we just combine two external incompatibilities. self.lines.push(formatter.explain_both_external( external1, external2, ¤t.terms, )); } (DerivationTree::Derived(derived), DerivationTree::External(external)) => { // One cause is derived, so we explain this first // then we add the one-line external part // and finally conclude with the current incompatibility. self.report_one_each(derived, external, ¤t.terms, formatter); } (DerivationTree::External(external), DerivationTree::Derived(derived)) => { self.report_one_each(derived, external, ¤t.terms, formatter); } (DerivationTree::Derived(derived1), DerivationTree::Derived(derived2)) => { // This is the most complex case since both causes are also derived. match ( self.line_ref_of(derived1.shared_id), self.line_ref_of(derived2.shared_id), ) { // If both causes already have been referenced (shared_id), // the explanation simply uses those references. (Some(ref1), Some(ref2)) => self.lines.push(formatter.explain_both_ref( ref1, derived1, ref2, derived2, ¤t.terms, )), // Otherwise, if one only has a line number reference, // we recursively call the one without reference and then // add the one with reference to conclude. (Some(ref1), None) => { self.build_recursive(derived2, formatter); self.lines .push(formatter.and_explain_ref(ref1, derived1, ¤t.terms)); } (None, Some(ref2)) => { self.build_recursive(derived1, formatter); self.lines .push(formatter.and_explain_ref(ref2, derived2, ¤t.terms)); } // Finally, if no line reference exists yet, // we call recursively the first one and then, // - if this was a shared node, it will get a line ref // and we can simply recall this with the current node. // - otherwise, we add a line reference to it, // recursively call on the second node, // and finally conclude. (None, None) => { self.build_recursive(derived1, formatter); if derived1.shared_id.is_some() { self.lines.push("".into()); self.build_recursive(current, formatter); } else { self.add_line_ref(); let ref1 = self.ref_count; self.lines.push("".into()); self.build_recursive(derived2, formatter); self.lines.push(formatter.and_explain_ref( ref1, derived1, ¤t.terms, )); } } } } } } /// Report a derived and an external incompatibility. /// /// The result will depend on the fact that the derived incompatibility /// has already been explained or not. fn report_one_each< P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display, F: ReportFormatter, >( &mut self, derived: &Derived, external: &External, current_terms: &Map>, formatter: &F, ) { match self.line_ref_of(derived.shared_id) { Some(ref_id) => self.lines.push(formatter.explain_ref_and_external( ref_id, derived, external, current_terms, )), None => self.report_recurse_one_each(derived, external, current_terms, formatter), } } /// Report one derived (without a line ref yet) and one external. fn report_recurse_one_each< P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display, F: ReportFormatter, >( &mut self, derived: &Derived, external: &External, current_terms: &Map>, formatter: &F, ) { match (derived.cause1.deref(), derived.cause2.deref()) { // If the derived cause has itself one external prior cause, // we can chain the external explanations. (DerivationTree::Derived(prior_derived), DerivationTree::External(prior_external)) => { self.build_recursive(prior_derived, formatter); self.lines.push(formatter.and_explain_prior_and_external( prior_external, external, current_terms, )); } // If the derived cause has itself one external prior cause, // we can chain the external explanations. (DerivationTree::External(prior_external), DerivationTree::Derived(prior_derived)) => { self.build_recursive(prior_derived, formatter); self.lines.push(formatter.and_explain_prior_and_external( prior_external, external, current_terms, )); } _ => { self.build_recursive(derived, formatter); self.lines .push(formatter.and_explain_external(external, current_terms)); } } } // Helper functions ######################################################## fn add_line_ref(&mut self) { let new_count = self.ref_count + 1; self.ref_count = new_count; if let Some(line) = self.lines.last_mut() { *line = format!("{line} ({new_count})"); } } fn line_ref_of(&self, shared_id: Option) -> Option { shared_id.and_then(|id| self.shared_with_ref.get(&id).cloned()) } } impl Reporter for DefaultStringReporter { type Output = String; fn report(derivation_tree: &DerivationTree) -> Self::Output { let formatter = DefaultStringReportFormatter; match derivation_tree { DerivationTree::External(external) => formatter.format_external(external), DerivationTree::Derived(derived) => { let mut reporter = Self::new(); reporter.build_recursive(derived, &formatter); reporter.lines.join("\n") } } } fn report_with_formatter( derivation_tree: &DerivationTree, formatter: &impl ReportFormatter, ) -> Self::Output { match derivation_tree { DerivationTree::External(external) => formatter.format_external(external), DerivationTree::Derived(derived) => { let mut reporter = Self::new(); reporter.build_recursive(derived, formatter); reporter.lines.join("\n") } } } } astral-pubgrub-0.3.3/src/solver.rs000064400000000000000000000346711046102023000152430ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use std::collections::BTreeSet as Set; use std::error::Error; use std::fmt::{Debug, Display}; use log::{debug, info}; use crate::internal::{Id, Incompatibility, State}; use crate::{ DependencyConstraints, Map, Package, PubGrubError, SelectedDependencies, Term, VersionSet, }; /// Statistics on how often a package conflicted with other packages. #[derive(Debug, Default, Clone)] pub struct PackageResolutionStatistics { // We track these fields separately but currently don't expose them separately to keep the // stable API slim. Please be encouraged to try different combinations of them and report if // you find better metrics that should be exposed. // // Say we have packages A and B, A having higher priority than B. We first decide A and then B, // and then find B to conflict with A. We call be B "affected" and A "culprit" since the // decisions for B is being rejected due to the decision we made for A earlier. // // If B is rejected due to its dependencies conflicting with A, we increase // `dependencies_affected` for B and for `dependencies_culprit` A. If B is rejected in unit // through an incompatibility with B, we increase `unit_propagation_affected` for B and for // `unit_propagation_culprit` A. unit_propagation_affected: u32, unit_propagation_culprit: u32, dependencies_affected: u32, dependencies_culprit: u32, } impl PackageResolutionStatistics { /// The number of conflicts this package was involved in. /// /// Processing packages with a high conflict count earlier usually speeds up resolution. /// /// Whenever a package is part of the root cause incompatibility of a conflict, we increase its /// count by one. Since the structure of the incompatibilities may change, this count too may /// change in the future. pub fn conflict_count(&self) -> u32 { self.unit_propagation_affected + self.unit_propagation_culprit + self.dependencies_affected + self.dependencies_culprit } } /// Finds a set of packages satisfying dependency bounds for a given package + version pair. /// /// It consists in efficiently finding a set of packages and versions /// that satisfy all the constraints of a given project dependencies. /// In addition, when that is not possible, /// PubGrub tries to provide a very human-readable and clear /// explanation as to why that failed. /// Below is an example of explanation present in /// the introductory blog post about PubGrub /// (Although this crate is not yet capable of building formatting quite this nice.) /// /// ```txt /// Because dropdown >=2.0.0 depends on icons >=2.0.0 and /// root depends on icons <2.0.0, dropdown >=2.0.0 is forbidden. /// /// And because menu >=1.1.0 depends on dropdown >=2.0.0, /// menu >=1.1.0 is forbidden. /// /// And because menu <1.1.0 depends on dropdown >=1.0.0 <2.0.0 /// which depends on intl <4.0.0, every version of menu /// requires intl <4.0.0. /// /// So, because root depends on both menu >=1.0.0 and intl >=5.0.0, /// version solving failed. /// ``` /// /// Is generic over an implementation of [DependencyProvider] which represents where the dependency constraints come from. /// The associated types on the DependencyProvider allow flexibility for the representation of /// package names, version requirements, version numbers, and other things. /// See its documentation for more details. /// For simple cases [OfflineDependencyProvider](crate::OfflineDependencyProvider) may be sufficient. /// /// ## API /// /// ``` /// # use std::convert::Infallible; /// # use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Ranges}; /// # /// # type NumVS = Ranges; /// # /// # fn try_main() -> Result<(), PubGrubError>> { /// # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); /// # let package = "root"; /// # let version = 1u32; /// let solution = resolve(&dependency_provider, package, version)?; /// # Ok(()) /// # } /// # fn main() { /// # assert!(matches!(try_main(), Err(PubGrubError::NoSolution(_)))); /// # } /// ``` /// /// The call to [resolve] for a given package at a given version /// will compute the set of packages and versions needed /// to satisfy the dependencies of that package and version pair. /// If there is no solution, the reason will be provided as clear as possible. #[cold] pub fn resolve( dependency_provider: &DP, package: DP::P, version: impl Into, ) -> Result, PubGrubError> { let mut state: State = State::init(package.clone(), version.into()); let mut conflict_tracker: Map, PackageResolutionStatistics> = Map::default(); let mut added_dependencies: Map, Set> = Map::default(); let mut next = state.root_package; loop { dependency_provider .should_cancel() .map_err(|err| PubGrubError::ErrorInShouldCancel(err))?; info!( "unit_propagation: {:?} = '{}'", &next, state.package_store[next] ); let satisfier_causes = state.unit_propagation(next)?; for (affected, incompat) in satisfier_causes { conflict_tracker .entry(affected) .or_default() .unit_propagation_affected += 1; for (conflict_package, _) in state.incompatibility_store[incompat].iter() { if conflict_package == affected { continue; } conflict_tracker .entry(conflict_package) .or_default() .unit_propagation_culprit += 1; } } debug!( "Partial solution after unit propagation: {}", state.partial_solution.display(&state.package_store) ); let Some((highest_priority_pkg, term_intersection)) = state.partial_solution.pick_highest_priority_pkg(|p, r| { dependency_provider.prioritize( &state.package_store[p], r, conflict_tracker.entry(p).or_default(), ) }) else { return Ok(state .partial_solution .extract_solution() .map(|(p, v)| (state.package_store[p].clone(), v)) .collect()); }; next = highest_priority_pkg; let decision = dependency_provider .choose_version(&state.package_store[next], term_intersection) .map_err(|err| PubGrubError::ErrorChoosingVersion { package: state.package_store[next].clone(), source: err, })?; info!( "DP chose: {:?} = '{}' @ {:?}", &next, state.package_store[next], decision ); // Pick the next compatible version. let v = match decision { None => { let inc = Incompatibility::no_versions(next, Term::Positive(term_intersection.clone())); state.add_incompatibility(inc); continue; } Some(x) => x, }; if !term_intersection.contains(&v) { panic!( "`choose_version` picked an incompatible version for package {}, {} is not in {}", state.package_store[next], v, term_intersection ); } let is_new_dependency = added_dependencies .entry(next) .or_default() .insert(v.clone()); if is_new_dependency { // Retrieve that package dependencies. let p = next; let dependencies = dependency_provider .get_dependencies(&state.package_store[p], &v) .map_err(|err| PubGrubError::ErrorRetrievingDependencies { package: state.package_store[p].clone(), version: v.clone(), source: err, })?; let dependencies = match dependencies { Dependencies::Unavailable(reason) => { state.add_incompatibility(Incompatibility::custom_version( p, v.clone(), reason, )); continue; } Dependencies::Available(x) => x, }; // Add that package and version if the dependencies are not problematic. if let Some(conflict) = state.add_package_version_dependencies(p, v.clone(), dependencies) { conflict_tracker.entry(p).or_default().dependencies_affected += 1; for (incompat_package, _) in state.incompatibility_store[conflict].iter() { if incompat_package == p { continue; } conflict_tracker .entry(incompat_package) .or_default() .dependencies_culprit += 1; } } } else { // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied // terms and can add the decision directly. info!( "add_decision (not first time): {:?} = '{}' @ {}", &next, state.package_store[next], v ); state.partial_solution.add_decision(next, v); } } } /// An enum used by [DependencyProvider] that holds information about package dependencies. /// For each [Package] there is a set of versions allowed as a dependency. #[derive(Clone)] pub enum Dependencies { /// Package dependencies are unavailable with the reason why they are missing. Unavailable(M), /// Container for all available package versions. Available(DependencyConstraints), } /// Trait that allows the algorithm to retrieve available packages and their dependencies. /// An implementor needs to be supplied to the [resolve] function. pub trait DependencyProvider { /// How this provider stores the name of the packages. type P: Package; /// How this provider stores the versions of the packages. /// /// A common choice is [`SemanticVersion`][crate::version::SemanticVersion]. type V: Debug + Display + Clone + Ord; /// How this provider stores the version requirements for the packages. /// The requirements must be able to process the same kind of version as this dependency provider. /// /// A common choice is [`Ranges`][version_ranges::Ranges]. type VS: VersionSet; /// The type returned from `prioritize`. The resolver does not care what type this is /// as long as it can pick a largest one and clone it. /// /// [`Reverse`](std::cmp::Reverse) can be useful if you want to pick the package with /// the fewest versions that match the outstanding constraint. type Priority: Ord + Clone; /// Type for custom incompatibilities. /// /// There are reasons in user code outside pubgrub that can cause packages or versions /// to be unavailable. Examples: /// * The version would require building the package, but builds are disabled. /// * The package is not available in the cache, but internet access has been disabled. /// * The package uses a legacy format not supported anymore. /// /// The intended use is to track them in an enum and assign them to this type. You can also /// assign [`String`] as placeholder. type M: Eq + Clone + Debug + Display; /// The kind of error returned from these methods. /// /// Returning this signals that resolution should fail with this error. type Err: Error + 'static; /// Determine the order in which versions are chosen for packages. /// /// Decisions are always made for the highest priority package first. The order of decisions /// determines which solution is chosen and can drastically change the performances of the /// solver. If there is a conflict between two package versions, decisions will be backtracked /// until the lower priority package version is discarded preserving the higher priority /// package. Usually, you want to decide more certain packages (e.g. those with a single version /// constraint) and packages with more conflicts first. /// /// The `package_conflicts_counts` argument provides access to some other heuristics that /// are production users have found useful. Although the exact meaning/efficacy of those /// arguments may change. /// /// The function is called once for each new package and then cached until we detect a /// (potential) change to `range`, otherwise it is cached, assuming that the priority only /// depends on the arguments to this function. /// /// If two packages have the same priority, PubGrub will bias toward a breadth first search. fn prioritize( &self, package: &Self::P, range: &Self::VS, // TODO(konsti): Are we always refreshing the priorities when `PackageResolutionStatistics` // changed for a package? package_conflicts_counts: &PackageResolutionStatistics, ) -> Self::Priority; /// Once the resolver has found the highest `Priority` package from all potential valid /// packages, it needs to know what version of that package to use. The most common pattern /// is to select the largest version that the range contains. fn choose_version( &self, package: &Self::P, range: &Self::VS, ) -> Result, Self::Err>; /// Retrieves the package dependencies. /// Return [Dependencies::Unavailable] if its dependencies are unavailable. #[allow(clippy::type_complexity)] fn get_dependencies( &self, package: &Self::P, version: &Self::V, ) -> Result, Self::Err>; /// This is called fairly regularly during the resolution, /// if it returns an Err then resolution will be terminated. /// This is helpful if you want to add some form of early termination like a timeout, /// or you want to add some form of user feedback if things are taking a while. /// If not provided the resolver will run as long as needed. fn should_cancel(&self) -> Result<(), Self::Err> { Ok(()) } } astral-pubgrub-0.3.3/src/term.rs000064400000000000000000000253151046102023000146730ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! A term is the fundamental unit of operation of the PubGrub algorithm. //! It is a positive or negative expression regarding a set of versions. use std::fmt::{self, Display}; use crate::VersionSet; /// A positive or negative expression regarding a set of versions. /// /// `Positive(r)` and `Negative(r.complement())` are not equivalent: /// * the term `Positive(r)` is satisfied if the package is selected AND the selected version is in `r`. /// * the term `Negative(r.complement())` is satisfied if the package is not selected OR the selected version is in `r`. /// /// A `Positive` term in the partial solution requires a version to be selected, but a `Negative` term /// allows for a solution that does not have that package selected. /// Specifically, `Positive(VS::empty())` means that there was a conflict (we need to select a version for the package /// but can't pick any), while `Negative(VS::full())` would mean it is fine as long as we don't select the package. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Term { /// For example, `1.0.0 <= v < 2.0.0` is a positive expression /// that is evaluated true if a version is selected /// and comprised between version 1.0.0 and version 2.0.0. Positive(VS), /// The term `not (v < 3.0.0)` is a negative expression /// that is evaluated true if a version >= 3.0.0 is selected /// or if no version is selected at all. Negative(VS), } /// Base methods. impl Term { /// A term that is always true. pub(crate) fn any() -> Self { Self::Negative(VS::empty()) } /// A term that is never true. pub(crate) fn empty() -> Self { Self::Positive(VS::empty()) } /// A positive term containing exactly that version. pub(crate) fn exact(version: VS::V) -> Self { Self::Positive(VS::singleton(version)) } /// Simply check if a term is positive. pub(crate) fn is_positive(&self) -> bool { match self { Self::Positive(_) => true, Self::Negative(_) => false, } } /// Negate a term. /// Evaluation of a negated term always returns /// the opposite of the evaluation of the original one. pub(crate) fn negate(&self) -> Self { match self { Self::Positive(set) => Self::Negative(set.clone()), Self::Negative(set) => Self::Positive(set.clone()), } } /// Evaluate a term regarding a given choice of version. pub(crate) fn contains(&self, v: &VS::V) -> bool { match self { Self::Positive(set) => set.contains(v), Self::Negative(set) => !set.contains(v), } } /// Unwrap the set contained in a positive term. /// /// Panics if used on a negative set. pub fn unwrap_positive(&self) -> &VS { match self { Self::Positive(set) => set, Self::Negative(set) => panic!("Negative term cannot unwrap positive set: {set:?}"), } } /// Unwrap the set contained in a negative term. /// /// Panics if used on a positive set. pub(crate) fn unwrap_negative(&self) -> &VS { match self { Self::Negative(set) => set, Self::Positive(set) => panic!("Positive term cannot unwrap negative set: {set:?}"), } } } /// Set operations with terms. impl Term { /// Compute the intersection of two terms. /// /// The intersection is negative (unselected package is allowed) /// if all terms are negative. pub(crate) fn intersection(&self, other: &Self) -> Self { match (self, other) { (Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.intersection(r2)), (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { Self::Positive(n.complement().intersection(p)) } (Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.union(r2)), } } /// Check whether two terms are mutually exclusive. /// /// An optimization for the native implementation of checking whether the intersection of two sets is empty. pub(crate) fn is_disjoint(&self, other: &Self) -> bool { match (self, other) { (Self::Positive(r1), Self::Positive(r2)) => r1.is_disjoint(r2), // Unselected package is allowed in both terms, so they are never disjoint. (Self::Negative(_), Self::Negative(_)) => false, // If the positive term is a subset of the negative term, it lies fully in the region that the negative // term excludes. (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { p.subset_of(n) } } } /// Compute the union of two terms. /// If at least one term is negative, the union is also negative (unselected package is allowed). pub(crate) fn union(&self, other: &Self) -> Self { match (self, other) { (Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.union(r2)), (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { Self::Negative(p.complement().intersection(n)) } (Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.intersection(r2)), } } /// Indicate if this term is a subset of another term. /// Just like for sets, we say that t1 is a subset of t2 /// if and only if t1 ∩ t2 = t1. pub(crate) fn subset_of(&self, other: &Self) -> bool { match (self, other) { (Self::Positive(r1), Self::Positive(r2)) => r1.subset_of(r2), (Self::Positive(r1), Self::Negative(r2)) => r1.is_disjoint(r2), // Only a negative term allows the unselected package, // so it can never be a subset of a positive term. (Self::Negative(_), Self::Positive(_)) => false, (Self::Negative(r1), Self::Negative(r2)) => r2.subset_of(r1), } } } /// Describe a relation between a set of terms S and another term t. /// /// As a shorthand, we say that a term v /// satisfies or contradicts a term t if {v} satisfies or contradicts it. pub(crate) enum Relation { /// We say that a set of terms S "satisfies" a term t /// if t must be true whenever every term in S is true. Satisfied, /// Conversely, S "contradicts" t if t must be false /// whenever every term in S is true. Contradicted, /// If neither of these is true we say that S is "inconclusive" for t. Inconclusive, } /// Relation between terms. impl Term { /// Check if a set of terms satisfies this term. /// /// We say that a set of terms S "satisfies" a term t /// if t must be true whenever every term in S is true. /// /// It turns out that this can also be expressed with set operations: /// S satisfies t if and only if ⋂ S ⊆ t #[cfg(test)] fn satisfied_by(&self, terms_intersection: &Self) -> bool { terms_intersection.subset_of(self) } /// Check if a set of terms contradicts this term. /// /// We say that a set of terms S "contradicts" a term t /// if t must be false whenever every term in S is true. /// /// It turns out that this can also be expressed with set operations: /// S contradicts t if and only if ⋂ S is disjoint with t /// S contradicts t if and only if (⋂ S) ⋂ t = ∅ #[cfg(test)] fn contradicted_by(&self, terms_intersection: &Self) -> bool { terms_intersection.intersection(self) == Self::empty() } /// Check if a set of terms satisfies or contradicts a given term. /// Otherwise the relation is inconclusive. pub(crate) fn relation_with(&self, other_terms_intersection: &Self) -> Relation { if other_terms_intersection.subset_of(self) { Relation::Satisfied } else if self.is_disjoint(other_terms_intersection) { Relation::Contradicted } else { Relation::Inconclusive } } } impl AsRef for Term { fn as_ref(&self) -> &Self { self } } // REPORT ###################################################################### impl Display for Term { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Positive(set) => write!(f, "{set}"), Self::Negative(set) => write!(f, "Not ( {set} )"), } } } // TESTS ####################################################################### #[cfg(test)] pub mod tests { use super::*; use proptest::prelude::*; use version_ranges::Ranges; pub fn strategy() -> impl Strategy>> { prop_oneof![ version_ranges::proptest_strategy().prop_map(Term::Negative), version_ranges::proptest_strategy().prop_map(Term::Positive), ] } proptest! { // Testing relation -------------------------------- #[test] fn relation_with(term1 in strategy(), term2 in strategy()) { match term1.relation_with(&term2) { Relation::Satisfied => assert!(term1.satisfied_by(&term2)), Relation::Contradicted => assert!(term1.contradicted_by(&term2)), Relation::Inconclusive => { assert!(!term1.satisfied_by(&term2)); assert!(!term1.contradicted_by(&term2)); } } } /// Ensure that we don't wrongly convert between positive and negative ranges #[test] fn positive_negative(term1 in strategy(), term2 in strategy()) { let intersection_positive = term1.is_positive() || term2.is_positive(); let union_positive = term1.is_positive() && term2.is_positive(); assert_eq!(term1.intersection(&term2).is_positive(), intersection_positive); assert_eq!(term1.union(&term2).is_positive(), union_positive); } #[test] fn is_disjoint_through_intersection(r1 in strategy(), r2 in strategy()) { let disjoint_def = r1.intersection(&r2) == Term::empty(); assert_eq!(r1.is_disjoint(&r2), disjoint_def); } #[test] fn subset_of_through_intersection(r1 in strategy(), r2 in strategy()) { let disjoint_def = r1.intersection(&r2) == r1; assert_eq!(r1.subset_of(&r2), disjoint_def); } #[test] fn union_through_intersection(r1 in strategy(), r2 in strategy()) { let union_def = r1 .negate() .intersection(&r2.negate()) .negate(); assert_eq!(r1.union(&r2), union_def); } } } astral-pubgrub-0.3.3/src/type_aliases.rs000064400000000000000000000017221046102023000164020ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! Publicly exported type aliases. use crate::DependencyProvider; /// Map implementation used by the library. pub type Map = rustc_hash::FxHashMap; /// Set implementation used by the library. pub type Set = rustc_hash::FxHashSet; /// Concrete dependencies picked by the library during [resolve](crate::solver::resolve) /// from [DependencyConstraints]. pub type SelectedDependencies = Map<::P, ::V>; /// Holds information about all possible versions a given package can accept. /// There is a difference in semantics between an empty map /// inside [DependencyConstraints] and [Dependencies::Unavailable](crate::solver::Dependencies::Unavailable): /// the former means the package has no dependency and it is a known fact, /// while the latter means they could not be fetched by the [DependencyProvider]. pub type DependencyConstraints = Map; astral-pubgrub-0.3.3/src/version.rs000064400000000000000000000141721046102023000154100ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! Traits and implementations to create and compare versions. use std::fmt::{self, Debug, Display}; use std::str::FromStr; use thiserror::Error; /// Type for semantic versions: major.minor.patch. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct SemanticVersion { major: u32, minor: u32, patch: u32, } #[cfg(feature = "serde")] impl serde::Serialize for SemanticVersion { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&format!("{self}")) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for SemanticVersion { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; FromStr::from_str(&s).map_err(serde::de::Error::custom) } } // Constructors impl SemanticVersion { /// Create a version with "major", "minor" and "patch" values. /// `version = major.minor.patch` pub fn new(major: u32, minor: u32, patch: u32) -> Self { Self { major, minor, patch, } } /// Version 0.0.0. pub fn zero() -> Self { Self::new(0, 0, 0) } /// Version 1.0.0. pub fn one() -> Self { Self::new(1, 0, 0) } /// Version 2.0.0. pub fn two() -> Self { Self::new(2, 0, 0) } } // Convert a tuple (major, minor, patch) into a version. impl From<(u32, u32, u32)> for SemanticVersion { fn from(tuple: (u32, u32, u32)) -> Self { let (major, minor, patch) = tuple; Self::new(major, minor, patch) } } // Convert a &(major, minor, patch) into a version. impl From<&(u32, u32, u32)> for SemanticVersion { fn from(tuple: &(u32, u32, u32)) -> Self { let (major, minor, patch) = *tuple; Self::new(major, minor, patch) } } // Convert an &version into a version. impl From<&SemanticVersion> for SemanticVersion { fn from(v: &SemanticVersion) -> Self { *v } } // Convert a version into a tuple (major, minor, patch). impl From for (u32, u32, u32) { fn from(v: SemanticVersion) -> Self { (v.major, v.minor, v.patch) } } // Bump versions. impl SemanticVersion { /// Bump the patch number of a version. pub fn bump_patch(self) -> Self { Self::new(self.major, self.minor, self.patch + 1) } /// Bump the minor number of a version. pub fn bump_minor(self) -> Self { Self::new(self.major, self.minor + 1, 0) } /// Bump the major number of a version. pub fn bump_major(self) -> Self { Self::new(self.major + 1, 0, 0) } } /// Error creating [SemanticVersion] from [String]. #[derive(Error, Debug, PartialEq, Eq)] pub enum VersionParseError { /// [SemanticVersion] must contain major, minor, patch versions. #[error("version {full_version} must contain 3 numbers separated by dot")] NotThreeParts { /// [SemanticVersion] that was being parsed. full_version: String, }, /// Wrapper around [ParseIntError](core::num::ParseIntError). #[error("cannot parse '{version_part}' in '{full_version}' as u32: {parse_error}")] ParseIntError { /// [SemanticVersion] that was being parsed. full_version: String, /// A version part where parsing failed. version_part: String, /// A specific error resulted from parsing a part of the version as [u32]. parse_error: String, }, } impl FromStr for SemanticVersion { type Err = VersionParseError; fn from_str(s: &str) -> Result { let parse_u32 = |part: &str| { part.parse::().map_err(|e| Self::Err::ParseIntError { full_version: s.to_string(), version_part: part.to_string(), parse_error: e.to_string(), }) }; let mut parts = s.split('.'); match (parts.next(), parts.next(), parts.next(), parts.next()) { (Some(major), Some(minor), Some(patch), None) => { let major = parse_u32(major)?; let minor = parse_u32(minor)?; let patch = parse_u32(patch)?; Ok(Self { major, minor, patch, }) } _ => Err(Self::Err::NotThreeParts { full_version: s.to_string(), }), } } } #[test] fn from_str_for_semantic_version() { let parse = |str: &str| str.parse::(); assert!( parse( &SemanticVersion { major: 0, minor: 1, patch: 0 } .to_string() ) .is_ok() ); assert!(parse("1.2.3").is_ok()); assert_eq!( parse("1.abc.3"), Err(VersionParseError::ParseIntError { full_version: "1.abc.3".to_owned(), version_part: "abc".to_owned(), parse_error: "invalid digit found in string".to_owned(), }) ); assert_eq!( parse("1.2.-3"), Err(VersionParseError::ParseIntError { full_version: "1.2.-3".to_owned(), version_part: "-3".to_owned(), parse_error: "invalid digit found in string".to_owned(), }) ); assert_eq!( parse("1.2.9876543210"), Err(VersionParseError::ParseIntError { full_version: "1.2.9876543210".to_owned(), version_part: "9876543210".to_owned(), parse_error: "number too large to fit in target type".to_owned(), }) ); assert_eq!( parse("1.2"), Err(VersionParseError::NotThreeParts { full_version: "1.2".to_owned(), }) ); assert_eq!( parse("1.2.3."), Err(VersionParseError::NotThreeParts { full_version: "1.2.3.".to_owned(), }) ); } impl Display for SemanticVersion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) } } astral-pubgrub-0.3.3/src/version_set.rs000064400000000000000000000071341046102023000162630ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use std::fmt::{Debug, Display}; use crate::Ranges; /// A set of versions. /// /// See [`Ranges`] for an implementation. /// /// The methods with default implementations can be overwritten for better performance, but their /// output must be equal to the default implementation. /// /// # Equality /// /// It is important that the `Eq` trait is implemented so that if two sets contain the same /// versions, they are equal under `Eq`. In particular, you can only use `#[derive(Eq, PartialEq)]` /// if `Eq` is strictly equivalent to the structural equality, i.e. if version sets are always /// stored in a canonical representations. Such problems may arise if your implementations of /// `complement()` and `intersection()` do not return canonical representations. /// /// For example, `>=1,<4 || >=2,<5` and `>=1,<4 || >=3,<5` are equal, because they can both be /// normalized to `>=1,<5`. /// /// Note that pubgrub does not know which versions actually exist for a package, the contract /// is about upholding the mathematical properties of set operations, assuming all versions are /// possible. This is required for the solver to determine the relationship of version sets to each /// other. pub trait VersionSet: Debug + Display + Clone + Eq { /// Version type associated with the sets manipulated. type V: Debug + Display + Clone + Ord; // Constructors /// An empty set containing no version. fn empty() -> Self; /// A set containing only the given version. fn singleton(v: Self::V) -> Self; // Operations /// The set of all version that are not in this set. fn complement(&self) -> Self; /// The set of all versions that are in both sets. fn intersection(&self, other: &Self) -> Self; /// Whether the version is part of this set. fn contains(&self, v: &Self::V) -> bool; // Automatically implemented functions /// The set containing all versions. /// /// The default implementation is the complement of the empty set. fn full() -> Self { Self::empty().complement() } /// The set of all versions that are either (or both) of the sets. /// /// The default implementation is complement of the intersection of the complements of both sets /// (De Morgan's law). fn union(&self, other: &Self) -> Self { self.complement() .intersection(&other.complement()) .complement() } /// Whether the ranges have no overlapping segments. fn is_disjoint(&self, other: &Self) -> bool { self.intersection(other) == Self::empty() } /// Whether all ranges of `self` are contained in `other`. fn subset_of(&self, other: &Self) -> bool { self == &self.intersection(other) } } /// [`Ranges`] contains optimized implementations of all operations. impl VersionSet for Ranges { type V = T; fn empty() -> Self { Ranges::empty() } fn singleton(v: Self::V) -> Self { Ranges::singleton(v) } fn complement(&self) -> Self { Ranges::complement(self) } fn intersection(&self, other: &Self) -> Self { Ranges::intersection(self, other) } fn contains(&self, v: &Self::V) -> bool { Ranges::contains(self, v) } fn full() -> Self { Ranges::full() } fn union(&self, other: &Self) -> Self { Ranges::union(self, other) } fn is_disjoint(&self, other: &Self) -> bool { Ranges::is_disjoint(self, other) } fn subset_of(&self, other: &Self) -> bool { Ranges::subset_of(self, other) } } astral-pubgrub-0.3.3/tests/examples.rs000064400000000000000000000215731046102023000161170ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use pubgrub::{ DefaultStringReporter, Map, OfflineDependencyProvider, PubGrubError, Ranges, Reporter as _, SemanticVersion, Set, resolve, }; type NumVS = Ranges; type SemVS = Ranges; use std::io::Write; use log::LevelFilter; fn init_log() { let _ = env_logger::builder() .filter_level(LevelFilter::Trace) .format(|buf, record| writeln!(buf, "{}", record.args())) .is_test(true) .try_init(); } #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#no-conflicts fn no_conflict() { init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( "root", (1, 0, 0), [("foo", Ranges::between((1, 0, 0), (2, 0, 0)))], ); #[rustfmt::skip] dependency_provider.add_dependencies( "foo", (1, 0, 0), [("bar", Ranges::between((1, 0, 0), (2, 0, 0)))], ); dependency_provider.add_dependencies("bar", (1, 0, 0), []); dependency_provider.add_dependencies("bar", (2, 0, 0), []); // Run the algorithm. let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); // Solution. let mut expected_solution = Map::default(); expected_solution.insert("root", (1, 0, 0).into()); expected_solution.insert("foo", (1, 0, 0).into()); expected_solution.insert("bar", (1, 0, 0).into()); // Comparing the true solution with the one computed by the algorithm. assert_eq!(expected_solution, computed_solution); } #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#avoiding-conflict-during-decision-making fn avoiding_conflict_during_decision_making() { init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( "root", (1, 0, 0), [ ("foo", Ranges::between((1, 0, 0), (2, 0, 0))), ("bar", Ranges::between((1, 0, 0), (2, 0, 0))), ], ); #[rustfmt::skip] dependency_provider.add_dependencies( "foo", (1, 1, 0), [("bar", Ranges::between((2, 0, 0), (3, 0, 0)))], ); dependency_provider.add_dependencies("foo", (1, 0, 0), []); dependency_provider.add_dependencies("bar", (1, 0, 0), []); dependency_provider.add_dependencies("bar", (1, 1, 0), []); dependency_provider.add_dependencies("bar", (2, 0, 0), []); // Run the algorithm. let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); // Solution. let mut expected_solution = Map::default(); expected_solution.insert("root", (1, 0, 0).into()); expected_solution.insert("foo", (1, 0, 0).into()); expected_solution.insert("bar", (1, 1, 0).into()); // Comparing the true solution with the one computed by the algorithm. assert_eq!(expected_solution, computed_solution); } #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#performing-conflict-resolution fn conflict_resolution() { init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( "root", (1, 0, 0), [("foo", Ranges::higher_than((1, 0, 0)))], ); #[rustfmt::skip] dependency_provider.add_dependencies( "foo", (2, 0, 0), [("bar", Ranges::between((1, 0, 0), (2, 0, 0)))], ); dependency_provider.add_dependencies("foo", (1, 0, 0), []); #[rustfmt::skip] dependency_provider.add_dependencies( "bar", (1, 0, 0), [("foo", Ranges::between((1, 0, 0), (2, 0, 0)))], ); // Run the algorithm. let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); // Solution. let mut expected_solution = Map::default(); expected_solution.insert("root", (1, 0, 0).into()); expected_solution.insert("foo", (1, 0, 0).into()); // Comparing the true solution with the one computed by the algorithm. assert_eq!(expected_solution, computed_solution); } #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#conflict-resolution-with-a-partial-satisfier fn conflict_with_partial_satisfier() { init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] // root 1.0.0 depends on foo ^1.0.0 and target ^2.0.0 dependency_provider.add_dependencies( "root", (1, 0, 0), [ ("foo", Ranges::between((1, 0, 0), (2, 0, 0))), ("target", Ranges::between((2, 0, 0), (3, 0, 0))), ], ); #[rustfmt::skip] // foo 1.1.0 depends on left ^1.0.0 and right ^1.0.0 dependency_provider.add_dependencies( "foo", (1, 1, 0), [ ("left", Ranges::between((1, 0, 0), (2, 0, 0))), ("right", Ranges::between((1, 0, 0), (2, 0, 0))), ], ); dependency_provider.add_dependencies("foo", (1, 0, 0), []); #[rustfmt::skip] // left 1.0.0 depends on shared >=1.0.0 dependency_provider.add_dependencies( "left", (1, 0, 0), [("shared", Ranges::higher_than((1, 0, 0)))], ); #[rustfmt::skip] // right 1.0.0 depends on shared <2.0.0 dependency_provider.add_dependencies( "right", (1, 0, 0), [("shared", Ranges::strictly_lower_than((2, 0, 0)))], ); dependency_provider.add_dependencies("shared", (2, 0, 0), []); #[rustfmt::skip] // shared 1.0.0 depends on target ^1.0.0 dependency_provider.add_dependencies( "shared", (1, 0, 0), [("target", Ranges::between((1, 0, 0), (2, 0, 0)))], ); dependency_provider.add_dependencies("target", (2, 0, 0), []); dependency_provider.add_dependencies("target", (1, 0, 0), []); // Run the algorithm. let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); // Solution. let mut expected_solution = Map::default(); expected_solution.insert("root", (1, 0, 0).into()); expected_solution.insert("foo", (1, 0, 0).into()); expected_solution.insert("target", (2, 0, 0).into()); // Comparing the true solution with the one computed by the algorithm. assert_eq!(expected_solution, computed_solution); } #[test] /// a0 dep on b and c /// b0 dep on d0 /// b1 dep on d1 (not existing) /// c0 has no dep /// c1 dep on d2 (not existing) /// d0 has no dep /// /// Solution: a0, b0, c0, d0 fn double_choices() { init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); dependency_provider.add_dependencies("a", 0u32, [("b", Ranges::full()), ("c", Ranges::full())]); dependency_provider.add_dependencies("b", 0u32, [("d", Ranges::singleton(0u32))]); dependency_provider.add_dependencies("b", 1u32, [("d", Ranges::singleton(1u32))]); dependency_provider.add_dependencies("c", 0u32, []); dependency_provider.add_dependencies("c", 1u32, [("d", Ranges::singleton(2u32))]); dependency_provider.add_dependencies("d", 0u32, []); // Solution. let mut expected_solution = Map::default(); expected_solution.insert("a", 0u32); expected_solution.insert("b", 0u32); expected_solution.insert("c", 0u32); expected_solution.insert("d", 0u32); // Run the algorithm. let computed_solution = resolve(&dependency_provider, "a", 0u32).unwrap(); assert_eq!(expected_solution, computed_solution); } #[test] fn confusing_with_lots_of_holes() { let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); // root depends on foo... dependency_provider.add_dependencies( "root", 1u32, vec![("foo", Ranges::full()), ("baz", Ranges::full())], ); for i in 1..6 { // foo depends on bar... dependency_provider.add_dependencies("foo", i as u32, vec![("bar", Ranges::full())]); } // This package is part of the dependency tree, but it's not part of the conflict dependency_provider.add_dependencies("baz", 1u32, vec![]); let Err(PubGrubError::NoSolution(mut derivation_tree)) = resolve(&dependency_provider, "root", 1u32) else { unreachable!() }; assert_eq!( &DefaultStringReporter::report(&derivation_tree), r#"Because there is no available version for bar and foo ==1 | ==2 | ==3 | ==4 | ==5 depends on bar, foo ==1 | ==2 | ==3 | ==4 | ==5 is forbidden. And because there is no version of foo in <1 | >1, <2 | >2, <3 | >3, <4 | >4, <5 | >5 and root ==1 depends on foo, root ==1 is forbidden."# ); derivation_tree.collapse_no_versions(); assert_eq!( &DefaultStringReporter::report(&derivation_tree), "Because foo depends on bar and root ==1 depends on foo, root ==1 is forbidden." ); assert_eq!( derivation_tree.packages(), // baz isn't shown. Set::from_iter(&["root", "foo", "bar"]) ); } astral-pubgrub-0.3.3/tests/proptest.rs000064400000000000000000000554451046102023000161660ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 #![allow(clippy::type_complexity)] use std::collections::BTreeSet as Set; use std::convert::Infallible; use std::fmt::{Debug, Display}; use proptest::collection::{btree_map, btree_set, vec}; use proptest::prelude::*; use proptest::sample::Index; use proptest::string::string_regex; use pubgrub::{ DefaultStringReporter, Dependencies, DependencyProvider, DerivationTree, External, OfflineDependencyProvider, Package, PackageResolutionStatistics, PubGrubError, Ranges, Reporter, SelectedDependencies, VersionSet, resolve, }; use crate::sat_dependency_provider::SatResolve; mod sat_dependency_provider; /// The same as [OfflineDependencyProvider] but takes versions from the opposite end: /// if [OfflineDependencyProvider] returns versions from newest to oldest, this returns them from oldest to newest. #[derive(Clone)] struct OldestVersionsDependencyProvider( OfflineDependencyProvider, ); impl DependencyProvider for OldestVersionsDependencyProvider { fn get_dependencies( &self, p: &P, v: &VS::V, ) -> Result, Infallible> { self.0.get_dependencies(p, v) } fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { Ok(self .0 .versions(package) .into_iter() .flatten() .find(|&v| range.contains(v)) .cloned()) } type Priority = as DependencyProvider>::Priority; fn prioritize( &self, package: &Self::P, range: &Self::VS, package_statistics: &PackageResolutionStatistics, ) -> Self::Priority { self.0.prioritize(package, range, package_statistics) } type Err = Infallible; type P = P; type V = VS::V; type VS = VS; type M = String; } /// The same as DP but it has a timeout. #[derive(Clone)] struct TimeoutDependencyProvider { dp: DP, start_time: std::time::Instant, call_count: std::cell::Cell, max_calls: u64, } impl TimeoutDependencyProvider { fn new(dp: DP, max_calls: u64) -> Self { Self { dp, start_time: std::time::Instant::now(), call_count: std::cell::Cell::new(0), max_calls, } } } impl DependencyProvider for TimeoutDependencyProvider { fn get_dependencies( &self, p: &DP::P, v: &DP::V, ) -> Result, DP::Err> { self.dp.get_dependencies(p, v) } fn should_cancel(&self) -> Result<(), DP::Err> { assert!(self.start_time.elapsed().as_secs() < 60); let calls = self.call_count.get(); assert!(calls < self.max_calls); self.call_count.set(calls + 1); Ok(()) } fn choose_version(&self, package: &DP::P, range: &DP::VS) -> Result, DP::Err> { self.dp.choose_version(package, range) } type Priority = DP::Priority; fn prioritize( &self, package: &Self::P, range: &Self::VS, package_statistics: &PackageResolutionStatistics, ) -> Self::Priority { self.dp.prioritize(package, range, package_statistics) } type Err = DP::Err; type P = DP::P; type V = ::V; type VS = DP::VS; type M = DP::M; } fn timeout_resolve( dependency_provider: DP, name: DP::P, version: impl Into, ) -> Result< SelectedDependencies>, PubGrubError>, > { resolve( &TimeoutDependencyProvider::new(dependency_provider, 50_000), name, version, ) } type NumVS = Ranges; #[test] #[should_panic] fn should_cancel_can_panic() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies(0, 0u32, [(666, Ranges::full())]); // Run the algorithm. let _ = resolve( &TimeoutDependencyProvider::new(dependency_provider, 1), 0, 0u32, ); } fn string_names() -> impl Strategy { string_regex("[A-Za-z][A-Za-z0-9_-]{0,5}") .unwrap() .prop_filter("reserved names", |n| { // root is the name of the thing being compiled // so it would be confusing to have it in the index // bad is a name reserved for a dep that won't work n != "root" && n != "bad" }) } /// This generates a random registry index. /// Unlike vec((Name, Ver, vec((Name, VerRq), ..), ..) /// This strategy has a high probability of having valid dependencies pub fn registry_strategy( name: impl Strategy, ) -> impl Strategy, Vec<(N, u32)>)> { let max_crates = 40; let max_versions = 15; let shrinkage = 40; let complicated_len = 10usize; let a_version = ..(max_versions as u32); let list_of_versions = btree_set(a_version, 1..=max_versions) .prop_map(move |ver| ver.into_iter().collect::>()); let list_of_crates_with_versions = btree_map(name, list_of_versions, 1..=max_crates); // each version of each crate can depend on each crate smaller then it. // In theory shrinkage should be 2, but in practice we get better trees with a larger value. let max_deps = max_versions * (max_crates * (max_crates - 1)) / shrinkage; let raw_version_range = (any::(), any::()); let raw_dependency = (any::(), any::(), raw_version_range); fn order_index(a: Index, b: Index, size: usize) -> (usize, usize) { use std::cmp::{max, min}; let (a, b) = (a.index(size), b.index(size)); (min(a, b), max(a, b)) } let list_of_raw_dependency = vec(raw_dependency, ..=max_deps); // By default a package depends only on other packages that have a smaller name, // this helps make sure that all things in the resulting index are DAGs. // If this is true then the DAG is maintained with grater instead. let reverse_alphabetical = any::().no_shrink(); ( list_of_crates_with_versions, list_of_raw_dependency, reverse_alphabetical, 1..(complicated_len + 1), ) .prop_map( move |(crate_vers_by_name, raw_dependencies, reverse_alphabetical, complicated_len)| { let mut list_of_pkgid: Vec<((N, u32), Vec<(N, NumVS)>)> = crate_vers_by_name .iter() .flat_map(|(name, vers)| vers.iter().map(move |&x| ((name.clone(), x), vec![]))) .collect(); let len_all_pkgid = list_of_pkgid.len(); for (a, b, (c, d)) in raw_dependencies { let (a, b) = order_index(a, b, len_all_pkgid); let (a, b) = if reverse_alphabetical { (b, a) } else { (a, b) }; let ((dep_name, _), _) = list_of_pkgid[a].to_owned(); if list_of_pkgid[b].0.0 == dep_name { continue; } let s = &crate_vers_by_name[&dep_name]; let s_last_index = s.len() - 1; let (c, d) = order_index(c, d, s.len() + 1); list_of_pkgid[b].1.push(( dep_name, if c > s_last_index { Ranges::empty() } else if c == 0 && d >= s_last_index { Ranges::full() } else if c == 0 { Ranges::strictly_lower_than(s[d] + 1) } else if d >= s_last_index { Ranges::higher_than(s[c]) } else if c == d { Ranges::singleton(s[c]) } else { Ranges::between(s[c], s[d] + 1) }, )); } let mut dependency_provider = OfflineDependencyProvider::::new(); let complicated_len = std::cmp::min(complicated_len, list_of_pkgid.len()); let complicated: Vec<_> = if reverse_alphabetical { &list_of_pkgid[..complicated_len] } else { &list_of_pkgid[(list_of_pkgid.len() - complicated_len)..] } .iter() .map(|(x, _)| (x.0.clone(), x.1)) .collect(); for ((name, ver), deps) in list_of_pkgid { dependency_provider.add_dependencies(name, ver, deps); } (dependency_provider, complicated) }, ) } /// Ensures that generator makes registries with large dependency trees. #[test] fn meta_test_deep_trees_from_strategy() { use proptest::strategy::ValueTree; use proptest::test_runner::TestRunner; let mut dis = [0; 21]; let strategy = registry_strategy(0u16..665); let mut test_runner = TestRunner::deterministic(); for _ in 0..128 { let (dependency_provider, cases) = strategy .new_tree(&mut TestRunner::new_with_rng( Default::default(), test_runner.new_rng(), )) .unwrap() .current(); for (name, ver) in cases { let res = resolve(&dependency_provider, name, ver); dis[res .as_ref() .map(|x| std::cmp::min(x.len(), dis.len()) - 1) .unwrap_or(0)] += 1; if dis.iter().all(|&x| x > 0) { return; } } } panic!( "In {} tries we did not see a wide enough distribution of dependency trees! dis: {:?}", dis.iter().sum::(), dis ); } /// Removes versions from the dependency provider where the retain function returns false. /// Solutions are constructed as a set of versions. /// If there are fewer versions available, there are fewer valid solutions available. /// If there was no solution to a resolution in the original dependency provider, /// then there must still be no solution with some options removed. /// If there was a solution to a resolution in the original dependency provider, /// there may not be a solution after versions are removes iif removed versions were critical for all valid solutions. fn retain_versions( dependency_provider: &OfflineDependencyProvider, mut retain: impl FnMut(&N, &VS::V) -> bool, ) -> OfflineDependencyProvider { let mut smaller_dependency_provider = OfflineDependencyProvider::new(); for n in dependency_provider.packages() { for v in dependency_provider.versions(n).unwrap() { if !retain(n, v) { continue; } let deps = match dependency_provider.get_dependencies(n, v).unwrap() { Dependencies::Unavailable(_) => panic!(), Dependencies::Available(deps) => deps, }; smaller_dependency_provider.add_dependencies(n.clone(), v.clone(), deps) } } smaller_dependency_provider } /// Removes dependencies from the dependency provider where the retain function returns false. /// Solutions are constrained by having to fulfill all the dependencies. /// If there are fewer dependencies required, there are more valid solutions. /// If there was a solution to a resolution in the original dependency provider, /// then there must still be a solution after dependencies are removed. /// If there was no solution to a resolution in the original dependency provider, /// there may now be a solution after dependencies are removed. fn retain_dependencies( dependency_provider: &OfflineDependencyProvider, mut retain: impl FnMut(&N, &VS::V, &N) -> bool, ) -> OfflineDependencyProvider { let mut smaller_dependency_provider = OfflineDependencyProvider::new(); for n in dependency_provider.packages() { for v in dependency_provider.versions(n).unwrap() { let deps = match dependency_provider.get_dependencies(n, v).unwrap() { Dependencies::Unavailable(_) => panic!(), Dependencies::Available(deps) => deps, }; smaller_dependency_provider.add_dependencies( n.clone(), v.clone(), deps.iter().filter_map(|(dep, range)| { if !retain(n, v, dep) { None } else { Some((dep.clone(), range.clone())) } }), ); } } smaller_dependency_provider } fn errors_the_same_with_only_report_dependencies( dependency_provider: OfflineDependencyProvider, name: N, ver: u32, ) { let Err(PubGrubError::NoSolution(tree)) = timeout_resolve(dependency_provider.clone(), name.clone(), ver) else { return; }; fn recursive( to_retain: &mut Vec<(N, VS, N)>, tree: &DerivationTree, ) { match tree { DerivationTree::External(External::FromDependencyOf(n1, vs1, n2, _)) => { to_retain.push((n1.clone(), vs1.clone(), n2.clone())); } DerivationTree::Derived(d) => { recursive(to_retain, &*d.cause1); recursive(to_retain, &*d.cause2); } _ => {} } } let mut to_retain = Vec::new(); recursive(&mut to_retain, &tree); let removed_provider = retain_dependencies(&dependency_provider, |p, v, d| { to_retain .iter() .any(|(n1, vs1, n2)| n1 == p && vs1.contains(v) && n2 == d) }); assert!( timeout_resolve(removed_provider.clone(), name, ver).is_err(), "The full index errored filtering to only dependencies in the derivation tree succeeded" ); } proptest! { #![proptest_config(ProptestConfig { max_shrink_iters: if std::env::var("CI").is_ok() { // This attempts to make sure that CI will fail fast, 0 } else { // but that local builds will give a small clear test case. 2048 }, result_cache: prop::test_runner::basic_result_cache, .. ProptestConfig::default() })] #[test] /// This test is mostly for profiling. fn prop_passes_string( (dependency_provider, cases) in registry_strategy(string_names()) ) { for (name, ver) in cases { _ = timeout_resolve(dependency_provider.clone(), name, ver); } } #[test] /// This test is mostly for profiling. fn prop_passes_int( (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { _ = timeout_resolve(dependency_provider.clone(), name, ver); } } #[test] fn prop_sat_errors_the_same( (dependency_provider, cases) in registry_strategy(0u16..665) ) { let mut sat = SatResolve::new(&dependency_provider); for (name, ver) in cases { let res = timeout_resolve(dependency_provider.clone(), name, ver); sat.check_resolve(&res, &name, &ver); } } #[test] fn prop_errors_the_same_with_only_report_dependencies( (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { errors_the_same_with_only_report_dependencies(dependency_provider.clone(), name, ver); } } #[test] /// This tests whether the algorithm is still deterministic. fn prop_same_on_repeated_runs( (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { let one = timeout_resolve(dependency_provider.clone(), name, ver); for _ in 0..3 { match (&one, &timeout_resolve(dependency_provider.clone(), name, ver)) { (Ok(l), Ok(r)) => assert_eq!(l, r), (Err(PubGrubError::NoSolution(derivation_l)), Err(PubGrubError::NoSolution(derivation_r))) => { prop_assert_eq!( DefaultStringReporter::report(derivation_l), DefaultStringReporter::report(derivation_r) )}, _ => panic!("not the same result") } } } } #[test] /// [ReverseDependencyProvider] changes what order the candidates /// are tried but not the existence of a solution. fn prop_reversed_version_errors_the_same( (dependency_provider, cases) in registry_strategy(0u16..665) ) { let reverse_provider = OldestVersionsDependencyProvider(dependency_provider.clone()); for (name, ver) in cases { let l = timeout_resolve(dependency_provider.clone(), name, ver); let r = timeout_resolve(reverse_provider.clone(), name, ver); match (&l, &r) { (Ok(_), Ok(_)) => (), (Err(_), Err(_)) => (), _ => panic!("not the same result") } } } #[test] fn prop_removing_a_dep_cant_break( (dependency_provider, cases) in registry_strategy(0u16..665), indexes_to_remove in vec((any::(), any::(), any::()), 1..10) ) { let packages: Vec<_> = dependency_provider.packages().collect(); let mut to_remove = Set::new(); for (package_idx, version_idx, dep_idx) in indexes_to_remove { let package = package_idx.get(&packages); let versions: Vec<_> = dependency_provider .versions(package) .unwrap().collect(); let version = version_idx.get(&versions); let dependencies: Vec<(u16, NumVS)> = match dependency_provider .get_dependencies(package, version) .unwrap() { Dependencies::Unavailable(_) => panic!(), Dependencies::Available(d) => d.into_iter().collect(), }; if !dependencies.is_empty() { to_remove.insert((package, **version, dep_idx.get(&dependencies).0)); } } let removed_provider = retain_dependencies( &dependency_provider, |p, v, d| {!to_remove.contains(&(&p, *v, *d))} ); for (name, ver) in cases { if timeout_resolve(dependency_provider.clone(), name, ver).is_ok() { prop_assert!( timeout_resolve(removed_provider.clone(), name, ver).is_ok(), "full index worked for `{} = \"={}\"` but removing some deps broke it!", name, ver, ) } } } #[test] fn prop_limited_independence_of_irrelevant_alternatives( (dependency_provider, cases) in registry_strategy(0u16..665), indexes_to_remove in vec(any::(), 1..10) ) { let all_versions: Vec<(u16, u32)> = dependency_provider .packages() .flat_map(|p| { dependency_provider .versions(p) .unwrap() .map(move |&v| (*p, v)) }) .collect(); let to_remove: Set<(_, _)> = indexes_to_remove.iter().map(|x| x.get(&all_versions)).cloned().collect(); for (name, ver) in cases { match timeout_resolve(dependency_provider.clone(), name, ver) { Ok(used) => { // If resolution was successful, then unpublishing a version of a crate // that was not selected should not change that. let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { used.get(n) == Some(v) // it was used || !to_remove.contains(&(*n, *v)) // or it is not one to be removed }); prop_assert!( timeout_resolve(smaller_dependency_provider.clone(), name, ver).is_ok(), "unpublishing {:?} stopped `{} = \"={}\"` from working", to_remove, name, ver ) } Err(_) => { // If resolution was unsuccessful, then it should stay unsuccessful // even if any version of a crate is unpublished. let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { to_remove.contains(&(*n, *v)) // it is one to be removed }); prop_assert!( timeout_resolve(smaller_dependency_provider.clone(), name, ver).is_err(), "full index did not work for `{} = \"={}\"` but unpublishing {:?} fixed it!", name, ver, to_remove, ) } } } } } #[cfg(feature = "serde")] #[test] fn large_case() { for case in std::fs::read_dir("test-examples").unwrap() { let case = case.unwrap().path(); let name = case.file_name().unwrap().to_string_lossy(); eprint!("{name} "); let data = std::fs::read_to_string(&case).unwrap(); let start_time = std::time::Instant::now(); if name.ends_with("u16_NumberVersion.ron") || name.ends_with("u16_u32.ron") { let dependency_provider: OfflineDependencyProvider = ron::de::from_str(&data).unwrap(); let mut sat = SatResolve::new(&dependency_provider); for p in dependency_provider.packages() { for &v in dependency_provider.versions(p).unwrap() { let res = resolve(&dependency_provider, *p, v); sat.check_resolve(&res, p, &v); } } } else if name.ends_with("str_SemanticVersion.ron") { let dependency_provider: OfflineDependencyProvider< &str, Ranges, > = ron::de::from_str(&data).unwrap(); let mut sat = SatResolve::new(&dependency_provider); for p in dependency_provider.packages() { for v in dependency_provider.versions(p).unwrap() { let res = resolve(&dependency_provider, *p, v); sat.check_resolve(&res, p, v); } } } eprintln!(" in {}s", start_time.elapsed().as_secs()) } } astral-pubgrub-0.3.3/tests/sat_dependency_provider.rs000064400000000000000000000126731046102023000212010ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use pubgrub::{ Dependencies, DependencyProvider, Map, OfflineDependencyProvider, Package, PubGrubError, SelectedDependencies, VersionSet, }; use varisat::ExtendFormula; fn sat_at_most_one(solver: &mut impl ExtendFormula, vars: &[varisat::Var]) { if vars.len() <= 1 { return; } else if vars.len() == 2 { solver.add_clause(&[vars[0].negative(), vars[1].negative()]); return; } else if vars.len() == 3 { solver.add_clause(&[vars[0].negative(), vars[1].negative()]); solver.add_clause(&[vars[0].negative(), vars[2].negative()]); solver.add_clause(&[vars[1].negative(), vars[2].negative()]); return; } // use the "Binary Encoding" from // https://www.it.uu.se/research/group/astra/ModRef10/papers/Alan%20M.%20Frisch%20and%20Paul%20A.%20Giannoros.%20SAT%20Encodings%20of%20the%20At-Most-k%20Constraint%20-%20ModRef%202010.pdf let len_bits = vars.len().ilog2() as usize + 1; let bits: Vec = solver.new_var_iter(len_bits).collect(); for (i, p) in vars.iter().enumerate() { for (j, &bit) in bits.iter().enumerate() { solver.add_clause(&[p.negative(), bit.lit(((1 << j) & i) > 0)]); } } } /// Resolution can be reduced to the SAT problem. So this is an alternative implementation /// of the resolver that uses a SAT library for the hard work. This is intended to be easy to read, /// as compared to the real resolver. This will find a valid resolution if one exists. /// /// The SAT library does not optimize for the newer version, /// so the selected packages may not match the real resolver. pub struct SatResolve { solver: varisat::Solver<'static>, all_versions_by_p: Map>, } impl SatResolve { pub fn new(dp: &OfflineDependencyProvider) -> Self { let mut cnf = varisat::CnfFormula::new(); let mut all_versions = vec![]; let mut all_versions_by_p: Map> = Map::default(); for p in dp.packages() { let mut versions_for_p = vec![]; for v in dp.versions(p).unwrap() { let new_var = cnf.new_var(); all_versions.push((p.clone(), v.clone(), new_var)); versions_for_p.push(new_var); all_versions_by_p .entry(p.clone()) .or_default() .push((v.clone(), new_var)); } // no two versions of the same package sat_at_most_one(&mut cnf, &versions_for_p); } // active packages need each of there `deps` to be satisfied for (p, v, var) in &all_versions { let deps = match dp.get_dependencies(p, v).unwrap() { Dependencies::Unavailable(_) => panic!(), Dependencies::Available(d) => d, }; for (p1, range) in &deps { let empty_vec = vec![]; let mut matches: Vec = all_versions_by_p .get(p1) .unwrap_or(&empty_vec) .iter() .filter(|(v1, _)| range.contains(v1)) .map(|(_, var1)| var1.positive()) .collect(); // ^ the `dep` is satisfied or matches.push(var.negative()); // ^ `p` is not active cnf.add_clause(&matches); } } let mut solver = varisat::Solver::new(); solver.add_formula(&cnf); // We dont need to `solve` now. We know that "use nothing" will satisfy all the clauses so far. // But things run faster if we let it spend some time figuring out how the constraints interact before we add assumptions. solver .solve() .expect("docs say it can't error in default config"); Self { solver, all_versions_by_p, } } pub fn resolve(&mut self, name: &P, ver: &VS::V) -> bool { if let Some(vers) = self.all_versions_by_p.get(name) { if let Some((_, var)) = vers.iter().find(|(v, _)| v == ver) { self.solver.assume(&[var.positive()]); self.solver .solve() .expect("docs say it can't error in default config") } else { false } } else { false } } pub fn is_valid_solution>( &mut self, pids: &SelectedDependencies, ) -> bool { let mut assumption = vec![]; for (p, vs) in &self.all_versions_by_p { let pid_for_p = pids.get(p); for (v, var) in vs { assumption.push(var.lit(pid_for_p == Some(v))) } } self.solver.assume(&assumption); self.solver .solve() .expect("docs say it can't error in default config") } pub fn check_resolve>( &mut self, res: &Result, PubGrubError>, p: &P, v: &VS::V, ) { match res { Ok(s) => { assert!(self.is_valid_solution::(s)); } Err(_) => { assert!(!self.resolve(p, v)); } } } } astral-pubgrub-0.3.3/tests/tests.rs000064400000000000000000000035211046102023000154340ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 use pubgrub::{OfflineDependencyProvider, PubGrubError, Ranges, resolve}; type NumVS = Ranges; #[test] fn same_result_on_repeated_runs() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies("c", 0u32, []); dependency_provider.add_dependencies("c", 2u32, []); dependency_provider.add_dependencies("b", 0u32, []); dependency_provider.add_dependencies("b", 1u32, [("c", Ranges::between(0u32, 1u32))]); dependency_provider.add_dependencies("a", 0u32, [("b", Ranges::full()), ("c", Ranges::full())]); let name = "a"; let ver: u32 = 0; let one = resolve(&dependency_provider, name, ver); for _ in 0..10 { match (&one, &resolve(&dependency_provider, name, ver)) { (Ok(l), Ok(r)) => assert_eq!(l, r), _ => panic!("not the same result"), } } } #[test] fn should_always_find_a_satisfier() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies("a", 0u32, [("b", Ranges::empty())]); assert!(matches!( resolve(&dependency_provider, "a", 0u32), Err(PubGrubError::NoSolution { .. }) )); dependency_provider.add_dependencies("c", 0u32, [("a", Ranges::full())]); assert!(matches!( resolve(&dependency_provider, "c", 0u32), Err(PubGrubError::NoSolution { .. }) )); } #[test] fn depend_on_self() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies("a", 0u32, [("a", Ranges::full())]); assert!(resolve(&dependency_provider, "a", 0u32).is_ok()); dependency_provider.add_dependencies("a", 66u32, [("a", Ranges::singleton(111u32))]); assert!(resolve(&dependency_provider, "a", 66u32).is_err()); }