├── .gitignore ├── .gitlab-ci.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── doc └── arch-rebuild-order.md ├── src ├── args.rs ├── bin │ └── completions.rs ├── error.rs ├── lib.rs └── main.rs └── tests ├── fixtures.rs └── test_rebuilder.rs /.gitignore: -------------------------------------------------------------------------------- 1 | ### Rust ### 2 | 3 | # Generated by Cargo 4 | # Contains compiled files and executables 5 | /target 6 | 7 | # Uncomment Cargo.lock for a library, leave it out for a binary. 8 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 9 | # Cargo.lock 10 | 11 | # Backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | # man pages 15 | doc/*.1 16 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: "archlinux:latest" 2 | 3 | before_script: 4 | - pacman -Syu --needed --noconfirm rust gcc cargo-audit pkgconf 5 | 6 | stages: 7 | - lint 8 | - test 9 | 10 | format: 11 | stage: lint 12 | script: 13 | - cargo fmt --all -- --check 14 | 15 | clippy: 16 | stage: lint 17 | script: 18 | - cargo clippy --all -- -D warnings 19 | 20 | audit: 21 | stage: lint 22 | script: 23 | - cargo audit 24 | 25 | test: 26 | stage: test 27 | script: 28 | - cargo test --all --release 29 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "alpm" 16 | version = "4.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d07c43bf396280fdda8928f725a89cdb8c525bba0240494f5da6b40aafe61387" 19 | dependencies = [ 20 | "alpm-sys", 21 | "bitflags", 22 | ] 23 | 24 | [[package]] 25 | name = "alpm-sys" 26 | version = "4.0.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "ad5eb56a41bf4f036c600aa1ddff354971926dd8ea29a3fb6589c6c375bf918b" 29 | dependencies = [ 30 | "pkg-config", 31 | ] 32 | 33 | [[package]] 34 | name = "anstream" 35 | version = "0.6.15" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 38 | dependencies = [ 39 | "anstyle", 40 | "anstyle-parse", 41 | "anstyle-query", 42 | "anstyle-wincon", 43 | "colorchoice", 44 | "is_terminal_polyfill", 45 | "utf8parse", 46 | ] 47 | 48 | [[package]] 49 | name = "anstyle" 50 | version = "1.0.8" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 53 | 54 | [[package]] 55 | name = "anstyle-parse" 56 | version = "0.2.5" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 59 | dependencies = [ 60 | "utf8parse", 61 | ] 62 | 63 | [[package]] 64 | name = "anstyle-query" 65 | version = "1.1.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 68 | dependencies = [ 69 | "windows-sys 0.52.0", 70 | ] 71 | 72 | [[package]] 73 | name = "anstyle-wincon" 74 | version = "3.0.4" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 77 | dependencies = [ 78 | "anstyle", 79 | "windows-sys 0.52.0", 80 | ] 81 | 82 | [[package]] 83 | name = "anyhow" 84 | version = "1.0.89" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 87 | 88 | [[package]] 89 | name = "arch-rebuild-order" 90 | version = "0.1.0" 91 | dependencies = [ 92 | "alpm", 93 | "anyhow", 94 | "clap", 95 | "clap_complete", 96 | "petgraph", 97 | "rstest", 98 | "tar", 99 | "tempfile", 100 | "thiserror", 101 | ] 102 | 103 | [[package]] 104 | name = "autocfg" 105 | version = "1.3.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 108 | 109 | [[package]] 110 | name = "bitflags" 111 | version = "2.6.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 114 | 115 | [[package]] 116 | name = "cfg-if" 117 | version = "1.0.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 120 | 121 | [[package]] 122 | name = "clap" 123 | version = "4.5.17" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" 126 | dependencies = [ 127 | "clap_builder", 128 | "clap_derive", 129 | ] 130 | 131 | [[package]] 132 | name = "clap_builder" 133 | version = "4.5.17" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" 136 | dependencies = [ 137 | "anstream", 138 | "anstyle", 139 | "clap_lex", 140 | "strsim", 141 | ] 142 | 143 | [[package]] 144 | name = "clap_complete" 145 | version = "4.5.26" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "205d5ef6d485fa47606b98b0ddc4ead26eb850aaa86abfb562a94fb3280ecba0" 148 | dependencies = [ 149 | "clap", 150 | ] 151 | 152 | [[package]] 153 | name = "clap_derive" 154 | version = "4.5.13" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" 157 | dependencies = [ 158 | "heck", 159 | "proc-macro2", 160 | "quote", 161 | "syn", 162 | ] 163 | 164 | [[package]] 165 | name = "clap_lex" 166 | version = "0.7.2" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 169 | 170 | [[package]] 171 | name = "colorchoice" 172 | version = "1.0.2" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 175 | 176 | [[package]] 177 | name = "equivalent" 178 | version = "1.0.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 181 | 182 | [[package]] 183 | name = "errno" 184 | version = "0.3.9" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 187 | dependencies = [ 188 | "libc", 189 | "windows-sys 0.52.0", 190 | ] 191 | 192 | [[package]] 193 | name = "fastrand" 194 | version = "2.1.1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 197 | 198 | [[package]] 199 | name = "filetime" 200 | version = "0.2.25" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 203 | dependencies = [ 204 | "cfg-if", 205 | "libc", 206 | "libredox", 207 | "windows-sys 0.59.0", 208 | ] 209 | 210 | [[package]] 211 | name = "fixedbitset" 212 | version = "0.4.2" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 215 | 216 | [[package]] 217 | name = "futures" 218 | version = "0.3.30" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 221 | dependencies = [ 222 | "futures-channel", 223 | "futures-core", 224 | "futures-executor", 225 | "futures-io", 226 | "futures-sink", 227 | "futures-task", 228 | "futures-util", 229 | ] 230 | 231 | [[package]] 232 | name = "futures-channel" 233 | version = "0.3.30" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 236 | dependencies = [ 237 | "futures-core", 238 | "futures-sink", 239 | ] 240 | 241 | [[package]] 242 | name = "futures-core" 243 | version = "0.3.30" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 246 | 247 | [[package]] 248 | name = "futures-executor" 249 | version = "0.3.30" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 252 | dependencies = [ 253 | "futures-core", 254 | "futures-task", 255 | "futures-util", 256 | ] 257 | 258 | [[package]] 259 | name = "futures-io" 260 | version = "0.3.30" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 263 | 264 | [[package]] 265 | name = "futures-macro" 266 | version = "0.3.30" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 269 | dependencies = [ 270 | "proc-macro2", 271 | "quote", 272 | "syn", 273 | ] 274 | 275 | [[package]] 276 | name = "futures-sink" 277 | version = "0.3.30" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 280 | 281 | [[package]] 282 | name = "futures-task" 283 | version = "0.3.30" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 286 | 287 | [[package]] 288 | name = "futures-timer" 289 | version = "3.0.3" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" 292 | 293 | [[package]] 294 | name = "futures-util" 295 | version = "0.3.30" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 298 | dependencies = [ 299 | "futures-channel", 300 | "futures-core", 301 | "futures-io", 302 | "futures-macro", 303 | "futures-sink", 304 | "futures-task", 305 | "memchr", 306 | "pin-project-lite", 307 | "pin-utils", 308 | "slab", 309 | ] 310 | 311 | [[package]] 312 | name = "glob" 313 | version = "0.3.1" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 316 | 317 | [[package]] 318 | name = "hashbrown" 319 | version = "0.14.5" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 322 | 323 | [[package]] 324 | name = "heck" 325 | version = "0.5.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 328 | 329 | [[package]] 330 | name = "indexmap" 331 | version = "2.5.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" 334 | dependencies = [ 335 | "equivalent", 336 | "hashbrown", 337 | ] 338 | 339 | [[package]] 340 | name = "is_terminal_polyfill" 341 | version = "1.70.1" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 344 | 345 | [[package]] 346 | name = "libc" 347 | version = "0.2.158" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 350 | 351 | [[package]] 352 | name = "libredox" 353 | version = "0.1.3" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 356 | dependencies = [ 357 | "bitflags", 358 | "libc", 359 | "redox_syscall", 360 | ] 361 | 362 | [[package]] 363 | name = "linux-raw-sys" 364 | version = "0.4.14" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 367 | 368 | [[package]] 369 | name = "memchr" 370 | version = "2.7.4" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 373 | 374 | [[package]] 375 | name = "once_cell" 376 | version = "1.20.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" 379 | 380 | [[package]] 381 | name = "petgraph" 382 | version = "0.6.5" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 385 | dependencies = [ 386 | "fixedbitset", 387 | "indexmap", 388 | ] 389 | 390 | [[package]] 391 | name = "pin-project-lite" 392 | version = "0.2.14" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 395 | 396 | [[package]] 397 | name = "pin-utils" 398 | version = "0.1.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 401 | 402 | [[package]] 403 | name = "pkg-config" 404 | version = "0.3.30" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 407 | 408 | [[package]] 409 | name = "proc-macro-crate" 410 | version = "3.2.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 413 | dependencies = [ 414 | "toml_edit", 415 | ] 416 | 417 | [[package]] 418 | name = "proc-macro2" 419 | version = "1.0.86" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 422 | dependencies = [ 423 | "unicode-ident", 424 | ] 425 | 426 | [[package]] 427 | name = "quote" 428 | version = "1.0.37" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 431 | dependencies = [ 432 | "proc-macro2", 433 | ] 434 | 435 | [[package]] 436 | name = "redox_syscall" 437 | version = "0.5.4" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" 440 | dependencies = [ 441 | "bitflags", 442 | ] 443 | 444 | [[package]] 445 | name = "regex" 446 | version = "1.10.6" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 449 | dependencies = [ 450 | "aho-corasick", 451 | "memchr", 452 | "regex-automata", 453 | "regex-syntax", 454 | ] 455 | 456 | [[package]] 457 | name = "regex-automata" 458 | version = "0.4.7" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 461 | dependencies = [ 462 | "aho-corasick", 463 | "memchr", 464 | "regex-syntax", 465 | ] 466 | 467 | [[package]] 468 | name = "regex-syntax" 469 | version = "0.8.4" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 472 | 473 | [[package]] 474 | name = "relative-path" 475 | version = "1.9.3" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" 478 | 479 | [[package]] 480 | name = "rstest" 481 | version = "0.22.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "7b423f0e62bdd61734b67cd21ff50871dfaeb9cc74f869dcd6af974fbcb19936" 484 | dependencies = [ 485 | "futures", 486 | "futures-timer", 487 | "rstest_macros", 488 | "rustc_version", 489 | ] 490 | 491 | [[package]] 492 | name = "rstest_macros" 493 | version = "0.22.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "c5e1711e7d14f74b12a58411c542185ef7fb7f2e7f8ee6e2940a883628522b42" 496 | dependencies = [ 497 | "cfg-if", 498 | "glob", 499 | "proc-macro-crate", 500 | "proc-macro2", 501 | "quote", 502 | "regex", 503 | "relative-path", 504 | "rustc_version", 505 | "syn", 506 | "unicode-ident", 507 | ] 508 | 509 | [[package]] 510 | name = "rustc_version" 511 | version = "0.4.1" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 514 | dependencies = [ 515 | "semver", 516 | ] 517 | 518 | [[package]] 519 | name = "rustix" 520 | version = "0.38.37" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 523 | dependencies = [ 524 | "bitflags", 525 | "errno", 526 | "libc", 527 | "linux-raw-sys", 528 | "windows-sys 0.52.0", 529 | ] 530 | 531 | [[package]] 532 | name = "semver" 533 | version = "1.0.23" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 536 | 537 | [[package]] 538 | name = "slab" 539 | version = "0.4.9" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 542 | dependencies = [ 543 | "autocfg", 544 | ] 545 | 546 | [[package]] 547 | name = "strsim" 548 | version = "0.11.1" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 551 | 552 | [[package]] 553 | name = "syn" 554 | version = "2.0.77" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 557 | dependencies = [ 558 | "proc-macro2", 559 | "quote", 560 | "unicode-ident", 561 | ] 562 | 563 | [[package]] 564 | name = "tar" 565 | version = "0.4.41" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" 568 | dependencies = [ 569 | "filetime", 570 | "libc", 571 | "xattr", 572 | ] 573 | 574 | [[package]] 575 | name = "tempfile" 576 | version = "3.12.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" 579 | dependencies = [ 580 | "cfg-if", 581 | "fastrand", 582 | "once_cell", 583 | "rustix", 584 | "windows-sys 0.59.0", 585 | ] 586 | 587 | [[package]] 588 | name = "thiserror" 589 | version = "1.0.63" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 592 | dependencies = [ 593 | "thiserror-impl", 594 | ] 595 | 596 | [[package]] 597 | name = "thiserror-impl" 598 | version = "1.0.63" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 601 | dependencies = [ 602 | "proc-macro2", 603 | "quote", 604 | "syn", 605 | ] 606 | 607 | [[package]] 608 | name = "toml_datetime" 609 | version = "0.6.8" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 612 | 613 | [[package]] 614 | name = "toml_edit" 615 | version = "0.22.20" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" 618 | dependencies = [ 619 | "indexmap", 620 | "toml_datetime", 621 | "winnow", 622 | ] 623 | 624 | [[package]] 625 | name = "unicode-ident" 626 | version = "1.0.13" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 629 | 630 | [[package]] 631 | name = "utf8parse" 632 | version = "0.2.2" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 635 | 636 | [[package]] 637 | name = "windows-sys" 638 | version = "0.52.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 641 | dependencies = [ 642 | "windows-targets", 643 | ] 644 | 645 | [[package]] 646 | name = "windows-sys" 647 | version = "0.59.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 650 | dependencies = [ 651 | "windows-targets", 652 | ] 653 | 654 | [[package]] 655 | name = "windows-targets" 656 | version = "0.52.6" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 659 | dependencies = [ 660 | "windows_aarch64_gnullvm", 661 | "windows_aarch64_msvc", 662 | "windows_i686_gnu", 663 | "windows_i686_gnullvm", 664 | "windows_i686_msvc", 665 | "windows_x86_64_gnu", 666 | "windows_x86_64_gnullvm", 667 | "windows_x86_64_msvc", 668 | ] 669 | 670 | [[package]] 671 | name = "windows_aarch64_gnullvm" 672 | version = "0.52.6" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 675 | 676 | [[package]] 677 | name = "windows_aarch64_msvc" 678 | version = "0.52.6" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 681 | 682 | [[package]] 683 | name = "windows_i686_gnu" 684 | version = "0.52.6" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 687 | 688 | [[package]] 689 | name = "windows_i686_gnullvm" 690 | version = "0.52.6" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 693 | 694 | [[package]] 695 | name = "windows_i686_msvc" 696 | version = "0.52.6" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 699 | 700 | [[package]] 701 | name = "windows_x86_64_gnu" 702 | version = "0.52.6" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 705 | 706 | [[package]] 707 | name = "windows_x86_64_gnullvm" 708 | version = "0.52.6" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 711 | 712 | [[package]] 713 | name = "windows_x86_64_msvc" 714 | version = "0.52.6" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 717 | 718 | [[package]] 719 | name = "winnow" 720 | version = "0.6.18" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" 723 | dependencies = [ 724 | "memchr", 725 | ] 726 | 727 | [[package]] 728 | name = "xattr" 729 | version = "1.3.1" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" 732 | dependencies = [ 733 | "libc", 734 | "linux-raw-sys", 735 | "rustix", 736 | ] 737 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "arch-rebuild-order" 3 | version = "0.1.0" 4 | authors = ["Jelle van der Waa "] 5 | edition = "2021" 6 | description = "A CLI tool to determine the rebuild order of provided package(s)." 7 | documentation = "https://gitlab.archlinux.org/archlinux/arch-rebuild-order" 8 | readme = "README.md" 9 | homepage = "https://gitlab.archlinux.org/archlinux/arch-rebuild-order" 10 | repository = "https://gitlab.archlinux.org/archlinux/arch-rebuild-order" 11 | license-file = "LICENSE" 12 | keywords = ["archlinux", "build", "alpm"] 13 | categories = ["command-line-utilities"] 14 | publish = false 15 | default-run = "arch-rebuild-order" 16 | 17 | [dependencies] 18 | alpm = "4.0" 19 | petgraph = "0.6.0" 20 | clap = { version = "4", features = ["derive"] } 21 | thiserror = "1.0.30" 22 | anyhow = "1.0.52" 23 | clap_complete = "4.0.7" 24 | 25 | [dev-dependencies] 26 | rstest = "0.22.0" 27 | tar = "0.4.38" 28 | tempfile = "3.3.0" 29 | 30 | 31 | [profile.release] 32 | lto = true 33 | codegen-units = 1 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Jelle van der Waa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | BINDIR ?= $(PREFIX)/bin 3 | MANDIR ?= $(PREFIX)/share/man 4 | OUT_DIR ?= target 5 | 6 | # Tools 7 | 8 | CARGO ?= cargo 9 | MANDOWN ?= mandown 10 | 11 | BIN=arch-rebuild-order 12 | MANPAGE=arch-rebuild-order.1 13 | DOCDIR=doc 14 | 15 | all: build doc 16 | doc: $(DOCDIR)/$(MANPAGE) 17 | 18 | %.1: %.md 19 | $(MANDOWN) $< > $@ 20 | 21 | build: 22 | $(CARGO) build --release --locked 23 | 24 | completions: 25 | OUT_DIR=$(OUT_DIR) $(CARGO) run --bin completions 26 | 27 | .PHONY: install 28 | install: build completions doc 29 | install -Dm755 target/release/$(BIN) $(DESTDIR)$(BINDIR)/$(BIN) 30 | install -Dm644 $(DOCDIR)/$(MANPAGE) $(DESTDIR)$(MANDIR)/man1/$(MANPAGE) 31 | install -Dm644 target/_$(BIN) $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(BIN) 32 | install -Dm644 target/$(BIN).bash $(DESTDIR)$(PREFIX)/share/bash-completion/$(BIN) 33 | install -Dm644 target/$(BIN).fish $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/$(BIN) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arch Linux Rebuild Order Tool 2 | 3 | A CLI tool to determine the rebuild order of provided package(s). 4 | 5 | ## Usage 6 | 7 | To show the rebuild order of opencolorio 8 | 9 | ``` 10 | cargo run opencolorio 11 | ``` 12 | 13 | ## Requirements 14 | 15 | - Generate a list of packages to rebuild in order for given package(s). 16 | - Generate the build order within one second. 17 | 18 | ## Algorithm 19 | 20 | Arch-rebuild-order uses the local syncdb to build a hashmap, mapping packages 21 | to their reverse (make) dependencies. The provided pkgnames are looked up in 22 | the syncdb and a hashmap is built of the package provides and the pkgname 23 | called **provides_map**. The **pkgnames** and **provides** are added to the 24 | **to_visit** list and this then starts the iteration over every entry in the 25 | list. During each iteration, the real package name is resolved if the provided 26 | package comes from provides using the **provides_map**, a graph node is created 27 | for the entry. For all reverse (make) dependencies of the entry, the dependency is added to 28 | the **to_visit** list, a new graph node is created and added as an edge of the 29 | pkg node. This repeats until the **to_visit** list is empty. 30 | 31 | ## DOT output 32 | 33 | Arch-rebuild-order can generate a DOT file of the rebuild order for a given package. 34 | 35 | ``` 36 | cargo run -- -d opencolorio.dot opencolorio 37 | dot -Tpng opencolorio.dot > opencolorio.png 38 | ``` 39 | 40 | ## Limitations 41 | 42 | * `testing` and `extra-testing` repositories are not included. 43 | * Arch-rebuild-order expects an up-to-date syncdb and does not provide warning if it is not. 44 | 45 | ## Completions 46 | 47 | Shell completions can be created with `cargo run --bin completions` in a 48 | directory specified by the env variable `OUT_DIR`. 49 | -------------------------------------------------------------------------------- /doc/arch-rebuild-order.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | arch-rebuild-order - Rebuild order generation script 4 | 5 | # SYNOPSIS 6 | 7 | **arch-rebuild-order** [OPTION]... [PACKAGES]... 8 | 9 | # DESCRIPTION 10 | 11 | Generate a rebuild order for given packages using pacman's local syncdb's. 12 | 13 | **--d=FILE, --dotfile=FILE** Generate a .dot graph file with the rebuild order of the gives packages 14 | 15 | **--dbpath=PATH** the path to pacman's database path 16 | 17 | **--repos=REPOS** the repositories to retrieve the package information from 18 | 19 | **--no-reverse-depends** only use pkgnames provided as input to calculate the build order, does not expand reverse (make)dependencies 20 | 21 | **--with-check-depends** include checkdependencies in the rebuild order 22 | 23 | **-V, --version** prints version information 24 | 25 | **-h, --help** prints help information 26 | 27 | # EXAMPLES 28 | 29 | Generating an image of the rebuild order of provided package(s): 30 | 31 | $ **arch-rebuild-order** -d linux-rebuild-order.dot linux 32 | 33 | 34 | $ dot -Tpng linux-rebuild-order.dot > linux-rebuild-order.png 35 | 36 | # BUGS 37 | 38 | [Bug tracker](https://gitlab.archlinux.org/archlinux/arch-rebuild-order/-/issues) 39 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, clap::Parser)] 2 | #[clap(name = "arch-rebuild-order", about, author)] 3 | pub struct Args { 4 | /// List of input packages 5 | #[arg(required = true)] 6 | pub pkgnames: Vec, 7 | 8 | /// Repositories 9 | #[arg( 10 | default_value = "core,extra,multilib", 11 | long, 12 | use_value_delimiter = true 13 | )] 14 | pub repos: Vec, 15 | 16 | /// The path to the pacman database, default ( /var/lib/pacman ) 17 | #[arg(long)] 18 | pub dbpath: Option, 19 | 20 | /// Write a dotfile into the given file 21 | #[arg(short, long)] 22 | pub dotfile: Option, 23 | 24 | /// Only use the pkgnames provided as input 25 | #[arg(long)] 26 | pub no_reverse_depends: bool, 27 | 28 | /// Include checkdepends 29 | #[arg(long)] 30 | pub with_check_depends: bool, 31 | } 32 | -------------------------------------------------------------------------------- /src/bin/completions.rs: -------------------------------------------------------------------------------- 1 | use clap::{CommandFactory, ValueEnum}; 2 | use clap_complete::Shell; 3 | use std::env; 4 | 5 | use arch_rebuild_order::args::Args; 6 | 7 | fn main() -> anyhow::Result<()> { 8 | // https://doc.rust-lang.org/cargo/reference/environment-variables.html 9 | let out_dir = env::var_os("OUT_DIR").expect("out dir not set"); 10 | for variant in Shell::value_variants() { 11 | clap_complete::generate_to( 12 | *variant, 13 | &mut Args::command(), 14 | env!("CARGO_PKG_NAME"), 15 | &out_dir, 16 | )?; 17 | } 18 | println!("completion scripts generated in {:?}", out_dir); 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum RebuildOrderError { 6 | /// Given package is not present in database 7 | #[error("package not found")] 8 | PackageNotFound, 9 | 10 | /// Pacman database failed to initialize 11 | #[error("could not initialize pacman db: `{0}`")] 12 | PacmanDbInit(#[from] alpm::Error), 13 | 14 | /// Writing dotfile failed 15 | #[error("could not write to file: `{0}`")] 16 | DotfileError(#[from] io::Error), 17 | 18 | /// Unknown cases 19 | #[error("unknown error")] 20 | Unknown, 21 | } 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use alpm::{Package, SigLevel}; 2 | use anyhow::{anyhow, Result}; 3 | use error::RebuildOrderError; 4 | use petgraph::dot::{Config, Dot}; 5 | use petgraph::graph::DiGraph; 6 | use petgraph::visit::DfsPostOrder; 7 | use std::collections::{HashMap, HashSet, VecDeque}; 8 | use std::fs::File; 9 | use std::io::{BufWriter, Write}; 10 | 11 | pub mod args; 12 | pub mod error; 13 | 14 | const ROOT_DIR: &str = "/"; 15 | const DB_PATH: &str = "/var/lib/pacman/"; 16 | 17 | /// Attempt to find any match of a package in the syncdb. 18 | fn find_package_anywhere<'a>(pkgname: &str, pacman: &'a alpm::Alpm) -> Result<&'a Package> { 19 | let dbs = pacman.syncdbs(); 20 | for db in dbs { 21 | if let Ok(pkg) = db.pkg(pkgname) { 22 | return Ok(pkg); 23 | } 24 | } 25 | Err(anyhow!(RebuildOrderError::PackageNotFound)) 26 | } 27 | 28 | /// Retrieve a HashMap of all reverse dependencies. 29 | fn get_reverse_deps_map( 30 | pacman: &alpm::Alpm, 31 | with_check_depends: bool, 32 | ) -> HashMap> { 33 | let mut reverse_deps: HashMap> = HashMap::new(); 34 | let dbs = pacman.syncdbs(); 35 | 36 | for db in dbs { 37 | for pkg in db.pkgs() { 38 | for dep in pkg.depends() { 39 | reverse_deps 40 | .entry(dep.name().to_string()) 41 | .and_modify(|e| { 42 | e.insert(pkg.name().to_string()); 43 | }) 44 | .or_insert_with(|| { 45 | let mut modify = HashSet::new(); 46 | modify.insert(pkg.name().to_string()); 47 | modify 48 | }); 49 | } 50 | 51 | for dep in pkg.makedepends() { 52 | reverse_deps 53 | .entry(dep.name().to_string()) 54 | .and_modify(|e| { 55 | e.insert(pkg.name().to_string()); 56 | }) 57 | .or_insert_with(|| { 58 | let mut modify = HashSet::new(); 59 | modify.insert(pkg.name().to_string()); 60 | modify 61 | }); 62 | } 63 | 64 | if with_check_depends { 65 | for dep in pkg.checkdepends() { 66 | reverse_deps 67 | .entry(dep.name().to_string()) 68 | .and_modify(|e| { 69 | e.insert(pkg.name().to_string()); 70 | }) 71 | .or_insert_with(|| { 72 | let mut modify = HashSet::new(); 73 | modify.insert(pkg.name().to_string()); 74 | modify 75 | }); 76 | } 77 | } 78 | } 79 | } 80 | 81 | reverse_deps 82 | } 83 | 84 | /// Write a given DiGraph to a given file using a buffered writer. 85 | fn write_dotfile(filename: String, graph: DiGraph<&str, u16>) -> Result<()> { 86 | let dotgraph = Dot::with_config(&graph, &[Config::EdgeNoLabel]); 87 | let file = File::create(filename)?; 88 | let mut bufw = BufWriter::new(file); 89 | bufw.write_all(dotgraph.to_string().as_bytes())?; 90 | 91 | Ok(()) 92 | } 93 | 94 | /// Run arch-rebuild-order, returning the rebuild order of provided package(s). 95 | pub fn run( 96 | pkgnames: Vec, 97 | dbpath: Option, 98 | repos: Vec, 99 | dotfile: Option, 100 | no_reverse_depends: bool, 101 | with_check_depends: bool, 102 | ) -> Result { 103 | let pacman = match dbpath { 104 | Some(path) => alpm::Alpm::new(ROOT_DIR, &path), 105 | None => alpm::Alpm::new(ROOT_DIR, DB_PATH), 106 | } 107 | .map_err(RebuildOrderError::PacmanDbInit)?; 108 | 109 | for repo in repos { 110 | let _repo = pacman.register_syncdb(repo, SigLevel::DATABASE_OPTIONAL); 111 | } 112 | 113 | let reverse_deps_map = get_reverse_deps_map(&pacman, with_check_depends); 114 | let mut provides = Vec::new(); 115 | let mut provides_map = HashMap::new(); 116 | 117 | for pkg in &pkgnames { 118 | let repopkg = find_package_anywhere(pkg, &pacman)?; 119 | for provide in repopkg.provides() { 120 | provides.push(provide.name()); 121 | provides_map.insert(provide.name(), repopkg.name()); 122 | } 123 | } 124 | 125 | let mut graph = DiGraph::<&str, u16>::new(); 126 | 127 | let mut to_visit = VecDeque::new(); 128 | let mut to_build = HashSet::new(); 129 | 130 | to_visit.extend(pkgnames.iter().map(|x| x.as_str())); 131 | to_visit.extend(provides.iter()); 132 | 133 | let mut cache_node: HashMap<&str, petgraph::graph::NodeIndex> = HashMap::new(); 134 | 135 | while !to_visit.is_empty() { 136 | let pkg = if let Some(pkg) = to_visit.pop_front() { 137 | pkg 138 | } else { 139 | break; 140 | }; 141 | 142 | // Resolve the provided package to the real package as provided packages are not real 143 | // packages in the Arch Linux repository. 144 | let rootpkg = provides_map.get(&pkg).unwrap_or(&pkg); 145 | let root = *cache_node 146 | .entry(rootpkg) 147 | .or_insert_with(|| graph.add_node(rootpkg)); 148 | 149 | if let Some(rev_deps_for_pkg) = reverse_deps_map.get(pkg) { 150 | if !to_build.contains(&pkg.to_string()) { 151 | to_visit.extend(rev_deps_for_pkg.iter().map(|x| x.as_str())); 152 | } 153 | 154 | let mut rev_deps_for_pkg_vec = rev_deps_for_pkg.iter().collect::>(); 155 | rev_deps_for_pkg_vec.sort(); 156 | 157 | for rev_dep in rev_deps_for_pkg_vec { 158 | let depnode = *cache_node 159 | .entry(rev_dep.as_str()) 160 | .or_insert_with(|| graph.add_node(rev_dep)); 161 | if !graph.contains_edge(root, depnode) { 162 | graph.add_edge(root, depnode, 1); 163 | } 164 | } 165 | to_build.extend(rev_deps_for_pkg); 166 | }; 167 | } 168 | 169 | // Visit nodes in our graph in a depth-first-search adding nodes in post-order. The provided 170 | // packages are added first to the stack. 171 | let mut rebuild_order_packages = Vec::new(); 172 | let mut bfs = DfsPostOrder::empty(&graph); 173 | bfs.stack.extend( 174 | pkgnames 175 | .iter() 176 | .filter_map(|pkg| cache_node.get(pkg.as_str())), 177 | ); 178 | 179 | while let Some(nx) = bfs.next(&graph) { 180 | let node = graph[nx]; 181 | rebuild_order_packages.push(node); 182 | } 183 | 184 | // Reverse the rebuild order as DfsPostOrder starts with the first pkgname and therefore 185 | // shows it as last package 186 | rebuild_order_packages.reverse(); 187 | 188 | // We only retain the packages we want to when using `--no-reverse_depends` 189 | // This logic is hard to parse because retain is an inverse filter, 190 | // thus we use the negated form of: no_reverse_depends && !pkgnames.contains(&pkg.to_string() 191 | rebuild_order_packages.retain(|pkg| !no_reverse_depends || pkgnames.contains(&pkg.to_string())); 192 | 193 | if let Some(filename) = dotfile { 194 | write_dotfile(filename, graph)?; 195 | } 196 | 197 | Ok(rebuild_order_packages.join(" ")) 198 | } 199 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | use arch_rebuild_order::args::Args; 4 | 5 | fn main() { 6 | let args = Args::parse(); 7 | match arch_rebuild_order::run( 8 | args.pkgnames, 9 | args.dbpath, 10 | args.repos, 11 | args.dotfile, 12 | args.no_reverse_depends, 13 | args.with_check_depends, 14 | ) { 15 | Ok(output) => { 16 | println!("{output}"); 17 | std::process::exit(0); 18 | } 19 | Err(e) => { 20 | eprintln!("Critical failure - arch-rebuild-order has stopped working"); 21 | eprintln!("Reason: {}", e); 22 | std::process::exit(1); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/fixtures.rs: -------------------------------------------------------------------------------- 1 | use rstest::fixture; 2 | use std::convert::TryFrom; 3 | use std::fmt; 4 | use std::fs; 5 | use std::fs::File; 6 | use std::io::Write; 7 | use tar::{Builder, Header}; 8 | use tempfile::{tempdir, TempDir}; 9 | 10 | // pacman database version (lib/libalpm/be_local.c) 11 | const ALPM_DB_VERSION: &str = "9"; 12 | 13 | #[derive(Hash, Clone, Eq, PartialEq, Debug)] 14 | pub struct Package { 15 | pub name: String, 16 | pub base: String, 17 | pub version: String, 18 | pub depends: Vec, 19 | pub makedepends: Vec, 20 | pub checkdepends: Vec, 21 | pub provides: Vec, 22 | } 23 | 24 | impl Package { 25 | fn new( 26 | name: &str, 27 | base: &str, 28 | version: &str, 29 | depends: Vec, 30 | makedepends: Vec, 31 | provides: Vec, 32 | checkdepends: Vec, 33 | ) -> Package { 34 | Package { 35 | name: name.to_string(), 36 | base: base.to_string(), 37 | version: version.to_string(), 38 | depends, 39 | makedepends, 40 | provides, 41 | checkdepends, 42 | } 43 | } 44 | 45 | fn desc(&self) -> String { 46 | let mut desc = String::from(""); 47 | 48 | let name = format!("%NAME%\n{}\n", self.name); 49 | desc.push_str(&name); 50 | 51 | let base = format!("%BASE%\n{}\n", self.base); 52 | desc.push_str(&base); 53 | 54 | if !self.depends.is_empty() { 55 | desc.push_str("%DEPENDS%\n"); 56 | for dep in self.depends.iter() { 57 | desc.push_str(dep); 58 | desc.push_str("\n"); 59 | } 60 | desc.push_str("\n"); 61 | } 62 | 63 | if !self.checkdepends.is_empty() { 64 | desc.push_str("%CHECKDEPENDS%\n"); 65 | for dep in self.checkdepends.iter() { 66 | desc.push_str(dep); 67 | desc.push_str("\n"); 68 | } 69 | desc.push_str("\n"); 70 | } 71 | 72 | if !self.makedepends.is_empty() { 73 | desc.push_str("%MAKEDEPENDS%\n"); 74 | for dep in self.makedepends.iter() { 75 | desc.push_str(dep); 76 | desc.push_str("\n"); 77 | } 78 | desc.push_str("\n"); 79 | } 80 | 81 | if !self.provides.is_empty() { 82 | desc.push_str("%PROVIDES%\n"); 83 | for dep in self.provides.iter() { 84 | desc.push_str(dep); 85 | desc.push_str("\n"); 86 | } 87 | desc.push_str("\n"); 88 | } 89 | 90 | desc 91 | } 92 | 93 | fn path(&self) -> String { 94 | format!("{}-{}/desc", self.name, self.version) 95 | } 96 | 97 | fn tarheader(&self) -> Header { 98 | let mut header = Header::new_gnu(); 99 | let desc = self.desc(); 100 | let datalen = u64::try_from(desc.len()).unwrap(); 101 | header.set_path(self.path()).unwrap(); 102 | header.set_size(datalen); 103 | header.set_mode(0o644); 104 | header.set_cksum(); 105 | 106 | header 107 | } 108 | } 109 | 110 | impl fmt::Display for Package { 111 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 112 | write!(f, "{}", self.name) 113 | } 114 | } 115 | 116 | impl PartialEq<&str> for Package { 117 | fn eq(&self, other: &&str) -> bool { 118 | &self.name.as_str() == other 119 | } 120 | } 121 | 122 | fn init_repodb(reponame: String, packages: Vec) -> (TempDir, String) { 123 | let tempdir = tempdir().unwrap(); 124 | let dbpath = tempdir.path().display().to_string(); 125 | 126 | // local dir 127 | let localdir = tempdir.path().join("local"); 128 | fs::create_dir(&localdir).unwrap(); 129 | 130 | let mut file = File::create(localdir.join("ALPM_DB_VERSION")).unwrap(); 131 | file.write_all(ALPM_DB_VERSION.as_bytes()).unwrap(); 132 | 133 | // sync dir 134 | let syncdir = tempdir.path().join("sync"); 135 | fs::create_dir(&syncdir).unwrap(); 136 | 137 | let dbloc = syncdir.join(format!("{}.db", reponame)); 138 | create_db(dbloc.display().to_string(), packages); 139 | 140 | (tempdir, dbpath) 141 | } 142 | 143 | fn create_db(dbloc: String, pkgs: Vec) { 144 | let mut archive = Builder::new(Vec::new()); 145 | 146 | for pkg in pkgs { 147 | let header = pkg.tarheader(); 148 | let desc = pkg.desc(); 149 | let data = desc.as_bytes(); 150 | archive.append(&header, data).unwrap(); 151 | } 152 | 153 | archive.finish().unwrap(); 154 | let data = archive.into_inner().unwrap(); 155 | 156 | let mut afile = File::create(dbloc).unwrap(); 157 | afile.write_all(&data).unwrap(); 158 | } 159 | 160 | #[fixture] 161 | pub fn invalid_dbpath() -> (Vec, Option) { 162 | let pkgnames = vec![String::from("testpkg1")]; 163 | let dbpath = Some(String::from("/non-existant-path")); 164 | 165 | (pkgnames, dbpath) 166 | } 167 | 168 | #[fixture] 169 | pub fn no_reverse_deps() -> (Vec, Option, Vec, TempDir) { 170 | let testpkg = Package::new( 171 | "testpkg1", 172 | "testpkg1", 173 | "1.0-1", 174 | vec![], 175 | vec![], 176 | vec![], 177 | vec![], 178 | ); 179 | let packages = vec![testpkg]; 180 | 181 | let reponame = "test"; 182 | let (rootdir, dbpath) = init_repodb(reponame.to_string(), packages.clone()); 183 | 184 | (packages, Some(dbpath), vec![reponame.to_string()], rootdir) 185 | } 186 | 187 | #[fixture] 188 | pub fn reverse_deps() -> (Vec, Option, Vec, TempDir) { 189 | let testpkg = Package::new( 190 | "testpkg1", 191 | "testpkg1", 192 | "1-1", 193 | vec![], 194 | vec![], 195 | vec![], 196 | vec![], 197 | ); 198 | let testpkg2 = Package::new( 199 | "testpkg2", 200 | "testpkg2", 201 | "1-1", 202 | vec![testpkg.name.clone()], 203 | vec![], 204 | vec![], 205 | vec![], 206 | ); 207 | let pkgnames = vec![testpkg.name.clone(), testpkg2.name.clone()]; 208 | let packages = vec![testpkg, testpkg2]; 209 | 210 | let reponame = "test"; 211 | let (tempdir, dbpath) = init_repodb(reponame.to_string(), packages); 212 | 213 | (pkgnames, Some(dbpath), vec![reponame.to_string()], tempdir) 214 | } 215 | 216 | #[fixture] 217 | pub fn multiple_deps() -> (Vec, Option, Vec, TempDir) { 218 | let testpkg = Package::new( 219 | "testpkg1", 220 | "testpkg1", 221 | "1.0-1", 222 | vec![], 223 | vec![], 224 | vec![], 225 | vec![], 226 | ); 227 | let testpkg2 = Package::new( 228 | "testpkg2", 229 | "testpkg2", 230 | "1.0-1", 231 | vec![testpkg.name.clone()], 232 | vec![], 233 | vec![], 234 | vec![], 235 | ); 236 | let testpkg3 = Package::new( 237 | "testpkg3", 238 | "testpkg3", 239 | "1-1", 240 | vec![testpkg.name.clone(), testpkg2.name.clone()], 241 | vec![], 242 | vec![], 243 | vec![], 244 | ); 245 | let packages = vec![testpkg3, testpkg2, testpkg]; 246 | 247 | let reponame = "test"; 248 | let (tempdir, dbpath) = init_repodb(reponame.to_string(), packages.clone()); 249 | 250 | (packages, Some(dbpath), vec![reponame.to_string()], tempdir) 251 | } 252 | 253 | #[fixture] 254 | pub fn reverse_make_deps() -> (Vec, Option, Vec, TempDir) { 255 | let testpkg = Package::new( 256 | "testpkg1", 257 | "testpkg1", 258 | "1-1", 259 | vec![], 260 | vec![], 261 | vec![], 262 | vec![], 263 | ); 264 | let testpkg2 = Package::new( 265 | "testpkg2", 266 | "testpkg2", 267 | "1-1", 268 | vec![], 269 | vec![testpkg.name.clone()], 270 | vec![], 271 | vec![], 272 | ); 273 | let packages = vec![testpkg, testpkg2]; 274 | 275 | let reponame = "test"; 276 | let (tempdir, dbpath) = init_repodb(reponame.to_string(), packages.clone()); 277 | 278 | (packages, Some(dbpath), vec![reponame.to_string()], tempdir) 279 | } 280 | 281 | #[fixture] 282 | pub fn reverse_check_deps() -> (Vec, Option, Vec, TempDir) { 283 | let testpkg = Package::new( 284 | "testpkg1", 285 | "testpkg1", 286 | "1-1", 287 | vec![], 288 | vec![], 289 | vec![], 290 | vec![], 291 | ); 292 | let testpkg2 = Package::new( 293 | "testpkg2", 294 | "testpkg2", 295 | "1-1", 296 | vec![], 297 | vec![], 298 | vec![], 299 | vec![testpkg.name.clone()], 300 | ); 301 | let packages = vec![testpkg, testpkg2]; 302 | 303 | let reponame = "test"; 304 | let (tempdir, dbpath) = init_repodb(reponame.to_string(), packages.clone()); 305 | 306 | (packages, Some(dbpath), vec![reponame.to_string()], tempdir) 307 | } 308 | 309 | #[fixture] 310 | pub fn provides_make_depends() -> (Vec, Option, Vec, TempDir) { 311 | let testpkg = Package::new( 312 | "testpkg1", 313 | "testpkg1", 314 | "1-1", 315 | vec![], 316 | vec![], 317 | vec!["pkg1".to_string()], 318 | vec![], 319 | ); 320 | let testpkg2 = Package::new( 321 | "testpkg2", 322 | "testpkg2", 323 | "1-1", 324 | vec![], 325 | vec![testpkg.provides[0].clone()], 326 | vec![], 327 | vec![], 328 | ); 329 | let packages = vec![testpkg, testpkg2]; 330 | 331 | let reponame = "test"; 332 | let (tempdir, dbpath) = init_repodb(reponame.to_string(), packages.clone()); 333 | 334 | (packages, Some(dbpath), vec![reponame.to_string()], tempdir) 335 | } 336 | 337 | #[fixture] 338 | pub fn dependency_depth() -> (Vec, Option, Vec, TempDir) { 339 | let testpkg = Package::new( 340 | "testpkg1", 341 | "testpkg1", 342 | "1-1", 343 | vec![], 344 | vec![], 345 | vec![], 346 | vec![], 347 | ); 348 | let testpkg2 = Package::new( 349 | "testpkg2", 350 | "testpkg2", 351 | "1-1", 352 | vec![], 353 | vec![testpkg.name.clone()], 354 | vec![], 355 | vec![], 356 | ); 357 | let testpkg3 = Package::new( 358 | "testpkg3", 359 | "testpkg3", 360 | "1-1", 361 | vec![], 362 | vec![testpkg2.name.clone()], 363 | vec![], 364 | vec![], 365 | ); 366 | let packages = vec![testpkg, testpkg2, testpkg3]; 367 | 368 | let reponame = "test"; 369 | let (tempdir, dbpath) = init_repodb(reponame.to_string(), packages.clone()); 370 | 371 | (packages, Some(dbpath), vec![reponame.to_string()], tempdir) 372 | } 373 | 374 | #[fixture] 375 | pub fn dependency_cycle() -> (Vec, Option, Vec, TempDir) { 376 | let testpkg = Package::new( 377 | "testpkg1", 378 | "testpkg1", 379 | "1-1", 380 | vec![String::from("testpkg2")], 381 | vec![], 382 | vec![], 383 | vec![], 384 | ); 385 | let testpkg2 = Package::new( 386 | "testpkg2", 387 | "testpkg2", 388 | "1-1", 389 | vec![testpkg.name.clone()], 390 | vec![], 391 | vec![], 392 | vec![], 393 | ); 394 | let packages = vec![testpkg, testpkg2]; 395 | 396 | let reponame = "test"; 397 | let (tempdir, dbpath) = init_repodb(reponame.to_string(), packages.clone()); 398 | 399 | (packages, Some(dbpath), vec![reponame.to_string()], tempdir) 400 | } 401 | 402 | #[fixture] 403 | pub fn multiple_pkgnames() -> (Vec, Option, Vec, TempDir) { 404 | let testpkg = Package::new( 405 | "testpkg1", 406 | "testpkg1", 407 | "1-1", 408 | vec![], 409 | vec![], 410 | vec![], 411 | vec![], 412 | ); 413 | let testpkg2 = Package::new( 414 | "testpkg2", 415 | "testpkg2", 416 | "1-1", 417 | vec![testpkg.name.clone()], 418 | vec![], 419 | vec![], 420 | vec![], 421 | ); 422 | let testpkg3 = Package::new( 423 | "testpkg3", 424 | "testpkg3", 425 | "1-1", 426 | vec![testpkg.name.clone()], 427 | vec![], 428 | vec![], 429 | vec![], 430 | ); 431 | let testpkg4 = Package::new( 432 | "testpkg4", 433 | "testpkg4", 434 | "1-1", 435 | vec![testpkg2.name.clone()], 436 | vec![], 437 | vec![], 438 | vec![], 439 | ); 440 | 441 | let packages = vec![testpkg, testpkg2, testpkg3, testpkg4]; 442 | 443 | let reponame = "test"; 444 | let (tempdir, dbpath) = init_repodb(reponame.to_string(), packages.clone()); 445 | 446 | (packages, Some(dbpath), vec![reponame.to_string()], tempdir) 447 | } 448 | -------------------------------------------------------------------------------- /tests/test_rebuilder.rs: -------------------------------------------------------------------------------- 1 | use rstest::rstest; 2 | use tempfile::TempDir; 3 | 4 | pub mod fixtures; 5 | 6 | use fixtures::{ 7 | dependency_cycle, dependency_depth, invalid_dbpath, multiple_deps, multiple_pkgnames, 8 | no_reverse_deps, provides_make_depends, reverse_check_deps, reverse_deps, reverse_make_deps, 9 | Package, 10 | }; 11 | 12 | #[rstest] 13 | #[should_panic] 14 | fn test_invalid_dbpath(invalid_dbpath: (Vec, Option)) { 15 | let pkgnames = invalid_dbpath.0; 16 | let dbpath = invalid_dbpath.1; 17 | arch_rebuild_order::run(pkgnames, dbpath, vec![], None, false, false).unwrap(); 18 | } 19 | 20 | /// A package without any reverse dependencies should only print the given package 21 | #[rstest] 22 | fn test_no_reverse_deps(no_reverse_deps: (Vec, Option, Vec, TempDir)) { 23 | let packages = no_reverse_deps.0; 24 | 25 | let res = arch_rebuild_order::run( 26 | vec![packages[0].name.clone()], 27 | no_reverse_deps.1, 28 | no_reverse_deps.2, 29 | None, 30 | false, 31 | false, 32 | ) 33 | .unwrap(); 34 | assert_eq!(packages[0], res.trim()); 35 | } 36 | 37 | /// Given a package 'testpkg1' with a reverse dependency on 'testpkg2', the rebuild order should be 38 | /// 'testpkg1 testpkg2' 39 | #[rstest] 40 | fn test_reverse_deps(reverse_deps: (Vec, Option, Vec, TempDir)) { 41 | let pkgnames = reverse_deps.0; 42 | let pkgname = &pkgnames[0]; 43 | 44 | let res = arch_rebuild_order::run( 45 | vec![pkgname.to_string()], 46 | reverse_deps.1, 47 | reverse_deps.2, 48 | None, 49 | false, 50 | false, 51 | ) 52 | .unwrap(); 53 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 54 | assert_eq!(pkgnames, res_pkgs); 55 | } 56 | 57 | /// Given a package 'testpkg1' with a reverse make dependency on 'testpkg2', the rebuild order 58 | /// should be 'testpkg1 testpkg2' 59 | #[rstest] 60 | fn test_reverse_make_deps(reverse_make_deps: (Vec, Option, Vec, TempDir)) { 61 | let packages = reverse_make_deps.0; 62 | let pkgname = &packages[0].name; 63 | 64 | let res = arch_rebuild_order::run( 65 | vec![pkgname.to_string()], 66 | reverse_make_deps.1, 67 | reverse_make_deps.2, 68 | None, 69 | false, 70 | false, 71 | ) 72 | .unwrap(); 73 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 74 | assert_eq!(packages, res_pkgs); 75 | } 76 | 77 | /// Given a package 'testpkg1' with a reverse check dependency on 'testpkg2', the rebuild order 78 | /// should be 'testpkg1' as we did not pass --with-check-depends 79 | #[rstest] 80 | fn test_reverse_check_deps_default( 81 | reverse_check_deps: (Vec, Option, Vec, TempDir), 82 | ) { 83 | let packages = reverse_check_deps.0; 84 | let pkgname = &packages[0].name; 85 | 86 | let res = arch_rebuild_order::run( 87 | vec![pkgname.to_string()], 88 | reverse_check_deps.1, 89 | reverse_check_deps.2, 90 | None, 91 | false, 92 | false, 93 | ) 94 | .unwrap(); 95 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 96 | assert_eq!(vec![pkgname.to_string()], res_pkgs); 97 | } 98 | 99 | /// Given a package 'testpkg1' with a reverse check dependency on 'testpkg2', the rebuild order 100 | /// should be 'testpkg1 testpkg2' 101 | #[rstest] 102 | fn test_reverse_check_deps( 103 | reverse_check_deps: (Vec, Option, Vec, TempDir), 104 | ) { 105 | let packages = reverse_check_deps.0; 106 | let pkgname = &packages[0].name; 107 | 108 | let res = arch_rebuild_order::run( 109 | vec![pkgname.to_string()], 110 | reverse_check_deps.1, 111 | reverse_check_deps.2, 112 | None, 113 | false, 114 | true, 115 | ) 116 | .unwrap(); 117 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 118 | assert_eq!(packages, res_pkgs); 119 | } 120 | 121 | /// Given a package 'testpkg1' which provides 'pkg1' where 'testpkg2' make depends on 'pkg1', 122 | /// the rebuild order should be 'testpkg1 testpkg2' 123 | #[rstest] 124 | fn test_provides_make_depends( 125 | provides_make_depends: (Vec, Option, Vec, TempDir), 126 | ) { 127 | let packages = provides_make_depends.0; 128 | let pkgname = &packages[0].name; 129 | 130 | let res = arch_rebuild_order::run( 131 | vec![pkgname.to_string()], 132 | provides_make_depends.1, 133 | provides_make_depends.2, 134 | None, 135 | false, 136 | false, 137 | ) 138 | .unwrap(); 139 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 140 | assert_eq!(packages, res_pkgs); 141 | } 142 | 143 | /// Given a package 'testpkg1' with a reverse dependency of 'testpkg2' and 'testpkg3', the rebuild 144 | /// order should be 'testpkg1 'testpkg2 testpkg3' 145 | #[rstest] 146 | fn test_multiple_deps(multiple_deps: (Vec, Option, Vec, TempDir)) { 147 | let packages = multiple_deps.0; 148 | let pkgname = &packages[0]; 149 | 150 | let res = arch_rebuild_order::run( 151 | vec![pkgname.to_string()], 152 | multiple_deps.1, 153 | multiple_deps.2, 154 | None, 155 | false, 156 | false, 157 | ) 158 | .unwrap(); 159 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 160 | assert_eq!(packages[0], res_pkgs[0]); 161 | } 162 | 163 | /// Given a package 'testpkg1' with a reverse dependency of 'testpkg2' which has a reverse 164 | /// dependency on 'testpkg3' the rebuild order should be 'testpkg1 'testpkg2 testpkg3' 165 | #[rstest] 166 | fn test_dependency_depth(dependency_depth: (Vec, Option, Vec, TempDir)) { 167 | let packages = dependency_depth.0; 168 | let pkgname = &packages[0]; 169 | 170 | let res = arch_rebuild_order::run( 171 | vec![pkgname.to_string()], 172 | dependency_depth.1, 173 | dependency_depth.2, 174 | None, 175 | false, 176 | false, 177 | ) 178 | .unwrap(); 179 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 180 | assert_eq!(packages[0], res_pkgs[0]); 181 | } 182 | 183 | /// Given a package 'testpkg1' with a dependency on 'testpkg2' and 'testpkg2' having a dependency 184 | /// on 'testpkg1'. Providing 'testpkg1' should return 'testpkg1 testpkg2' 185 | /// TODO: There should be a warning that there is a dependency cycle and the dep cycle should be 186 | /// broken #3. 187 | #[rstest] 188 | fn test_dependency_cycle(dependency_cycle: (Vec, Option, Vec, TempDir)) { 189 | let packages = dependency_cycle.0; 190 | let pkgname = &packages[0]; 191 | 192 | let res = arch_rebuild_order::run( 193 | vec![pkgname.to_string()], 194 | dependency_cycle.1, 195 | dependency_cycle.2, 196 | None, 197 | false, 198 | false, 199 | ) 200 | .unwrap(); 201 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 202 | assert_eq!(packages[0], res_pkgs[0]); 203 | } 204 | 205 | /// Given two packages names as input, with testpkg1 being a reverse dependency for testpkg2 and 206 | /// testpkg3 and testpkg4 being a dependency of testpkg2. Providing "testpkg1 testpkg2" should 207 | /// return "testpkg1 testpkg3 testpkg2 testpkg4" 208 | #[rstest] 209 | fn test_multiple_pkgnames(multiple_pkgnames: (Vec, Option, Vec, TempDir)) { 210 | let packages = multiple_pkgnames.0; 211 | let pkgname1 = &packages[0]; 212 | let pkgname2 = &packages[1]; 213 | 214 | let res = arch_rebuild_order::run( 215 | vec![pkgname1.to_string(), pkgname2.to_string()], 216 | multiple_pkgnames.1, 217 | multiple_pkgnames.2, 218 | None, 219 | false, 220 | false, 221 | ) 222 | .unwrap(); 223 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 224 | let expected = vec!["testpkg1", "testpkg3", "testpkg2", "testpkg4"]; 225 | assert_eq!(res_pkgs, expected); 226 | } 227 | 228 | /// Given two packages names as input, with testpkg1 being a reverse dependency for testpkg2 and 229 | /// testpkg3 and testpkg4 being a dependency of testpkg2. Providing "testpkg1 testpkg2" in 230 | /// combination with the no reverse dependenies flag should 231 | /// return "testpkg1 testpkg2" 232 | #[rstest] 233 | fn test_no_reverse_deps_flag( 234 | multiple_pkgnames: (Vec, Option, Vec, TempDir), 235 | ) { 236 | let packages = multiple_pkgnames.0; 237 | let pkgname1 = &packages[0]; 238 | let pkgname2 = &packages[1]; 239 | 240 | let res = arch_rebuild_order::run( 241 | vec![pkgname1.to_string(), pkgname2.to_string()], 242 | multiple_pkgnames.1, 243 | multiple_pkgnames.2, 244 | None, 245 | true, 246 | false, 247 | ) 248 | .unwrap(); 249 | let res_pkgs: Vec<&str> = res.trim().split_ascii_whitespace().collect(); 250 | let expected = vec!["testpkg1", "testpkg2"]; 251 | assert_eq!(res_pkgs, expected); 252 | } 253 | --------------------------------------------------------------------------------