mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-21 13:08:42 +00:00
Merge branch 'main' of github.com:ryankurte/cargo-binstall into main
This commit is contained in:
commit
b7b8f36109
7 changed files with 879 additions and 413 deletions
285
Cargo.lock
generated
285
Cargo.lock
generated
|
@ -6,6 +6,15 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -17,9 +26,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.35"
|
version = "1.0.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
|
checksum = "ee67c11feeac938fae061b232e38e0b6d94f97a9df10e6271319325ac4c56a86"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
|
@ -125,11 +134,14 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_metadata",
|
"cargo_metadata",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
|
"crates-index",
|
||||||
"crates_io_api",
|
"crates_io_api",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"env_logger",
|
||||||
"flate2",
|
"flate2",
|
||||||
"log",
|
"log",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
|
@ -170,6 +182,9 @@ name = "cc"
|
||||||
version = "1.0.66"
|
version = "1.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||||
|
dependencies = [
|
||||||
|
"jobserver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
@ -212,16 +227,6 @@ dependencies = [
|
||||||
"vec_map",
|
"vec_map",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_error_panic_hook"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.10",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "constant_time_eq"
|
name = "constant_time_eq"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -244,6 +249,24 @@ version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crates-index"
|
||||||
|
version = "0.16.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24823d553339d125040d989d2a593a01b034fe5ac17714423bcd2c3d168878"
|
||||||
|
dependencies = [
|
||||||
|
"git2",
|
||||||
|
"glob",
|
||||||
|
"hex",
|
||||||
|
"home",
|
||||||
|
"memchr",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"smartstring",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crates_io_api"
|
name = "crates_io_api"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
@ -319,6 +342,19 @@ dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"humantime",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fake-simd"
|
name = "fake-simd"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -517,6 +553,27 @@ dependencies = [
|
||||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "git2"
|
||||||
|
version = "0.13.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44f267c9da8a4de3c615b59e23606c75f164f84896e97f4dd6c15a4294de4359"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
"libgit2-sys",
|
||||||
|
"log",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
|
@ -545,9 +602,9 @@ checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
@ -562,10 +619,28 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "hex"
|
||||||
version = "0.2.1"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "home"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "84129d298a6d57d246960ff8eb831ca4af3f96d29e2e28848dae275408658e26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -594,6 +669,12 @@ version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
|
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.13.9"
|
version = "0.13.9"
|
||||||
|
@ -644,9 +725,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.6.0"
|
version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
|
checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
@ -669,9 +750,18 @@ checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.6"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
|
@ -704,6 +794,46 @@ version = "0.2.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
|
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libgit2-sys"
|
||||||
|
version = "0.12.17+1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4ebdf65ca745126df8824688637aa0535a88900b83362d8ca63893bcf4e8841"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"libssh2-sys",
|
||||||
|
"libz-sys",
|
||||||
|
"openssl-sys",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libssh2-sys"
|
||||||
|
version = "0.2.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df40b13fe7ea1be9b9dffa365a51273816c345fc1811478b57ed7d964fbfc4ce"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"libz-sys",
|
||||||
|
"openssl-sys",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libz-sys"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -790,9 +920,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.6"
|
version = "0.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6fcc7939b5edc4e4f86b1b4a04bb1498afaaf871b1a6691838ed06fcb48d3a3f"
|
checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -860,9 +990,9 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.31"
|
version = "0.10.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d008f51b1acffa0d3450a68606e6a51c123012edaacb0f4e1426bd978869187"
|
checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
|
@ -880,9 +1010,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.59"
|
version = "0.9.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de52d8eabd217311538a39bba130d7dea1f1e118010fee7a033d966845e7d5fe"
|
checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cc",
|
"cc",
|
||||||
|
@ -1057,9 +1187,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -1159,6 +1289,24 @@ dependencies = [
|
||||||
"rust-argon2",
|
"rust-argon2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
"thread_local",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
@ -1170,9 +1318,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.10.9"
|
version = "0.10.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb15d6255c792356a0f578d8a645c677904dc02e862bebe2ecc18e0c01b9a0ce"
|
checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1200,7 +1348,6 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"wasm-bindgen-test",
|
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
@ -1233,12 +1380,6 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scoped-tls"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -1304,9 +1445,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.60"
|
version = "1.0.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
|
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -1355,17 +1496,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "smartstring"
|
||||||
version = "0.3.17"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
|
checksum = "1ada87540bf8ef4cf8a1789deb175626829bb59b1fefd816cf7f7f55efcdbae9"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.3.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -1416,9 +1572,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.54"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
|
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1479,6 +1635,15 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
|
@ -1571,9 +1736,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
|
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -1798,30 +1963,6 @@ version = "0.2.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
|
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test"
|
|
||||||
version = "0.3.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0355fa0c1f9b792a09b6dcb6a8be24d51e71e6d74972f9eb4a44c4c004d24a25"
|
|
||||||
dependencies = [
|
|
||||||
"console_error_panic_hook",
|
|
||||||
"js-sys",
|
|
||||||
"scoped-tls",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"wasm-bindgen-test-macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test-macro"
|
|
||||||
version = "0.3.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "27e07b46b98024c2ba2f9e83a10c2ef0515f057f2da299c1762a2017de80438b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.46"
|
version = "0.3.46"
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -8,6 +8,9 @@ authors = ["ryan <ryan@kurte.nz>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[[pkg_bin]]
|
||||||
|
name = "cargo-binstall"
|
||||||
|
path = "cargo-binstall-{ target }"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crates_io_api = "0.6.1"
|
crates_io_api = "0.6.1"
|
||||||
|
@ -18,7 +21,7 @@ log = "0.4.11"
|
||||||
structopt = "0.3.21"
|
structopt = "0.3.21"
|
||||||
simplelog = "0.8.0"
|
simplelog = "0.8.0"
|
||||||
anyhow = "1.0.35"
|
anyhow = "1.0.35"
|
||||||
reqwest = { version = "0.10.9" }
|
reqwest = "0.10.10"
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
flate2 = "1.0.19"
|
flate2 = "1.0.19"
|
||||||
tar = "0.4.30"
|
tar = "0.4.30"
|
||||||
|
@ -28,3 +31,8 @@ strum_macros = "0.20.1"
|
||||||
strum = "0.20.0"
|
strum = "0.20.0"
|
||||||
dirs = "3.0.1"
|
dirs = "3.0.1"
|
||||||
serde_derive = "1.0.118"
|
serde_derive = "1.0.118"
|
||||||
|
crates-index = "0.16.2"
|
||||||
|
semver = "0.11.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.8.2"
|
||||||
|
|
132
README.md
132
README.md
|
@ -1,87 +1,123 @@
|
||||||
# Cargo B(inary)Install
|
# Cargo B(inary)Install
|
||||||
|
|
||||||
A helper for distributing / installing CI built rust binaries in a pseudo-distributed and maybe-one-day secure manner.
|
A helper for distribution and installation of CI built rust binaries in a pseudo-distributed and maybe-one-day secure manner.
|
||||||
This tool is not intended to manage the _building_ of rust binaries as CI can already readily manage this, but to provide a simple project-level mechanism for the distribution and consumption of rust binary packages.
|
This is part experiment, part solving a personal problem, and part hope that we can solve / never re-visit this. I hope you find it helpful and, good luck!
|
||||||
|
|
||||||
To support `binstall` maintainers must add configuration values to `Cargo.toml` to allow the tool to locate the appropriate CI-produced binary package for a given version and target. For further information on adding `binstall` support, see [Supporting Binary Installation](#Supporting-Binary-Installation) for further detail on supporting `binstall`.
|
To get started _using_ `cargo-binstall`, first install the binary (either via `cargo install cargo-binstall` or by downloading a precompiled [release](https://github.com/ryankurte/cargo-binstall/releases)).
|
||||||
|
Once `cargo-binstall` is installed, supported packages can be installed using `cargo binstall NAME` where `NAME` is the crate.io package name.
|
||||||
|
Package versions and targets may be specified using the `--version` and `--target` arguments respectively, and install directory with `--install-dir` (this defaults to `$HOME/.cargo/bin`, with fall-backs to `$HOME/.bin` if unavailable). For additional options please see `cargo binstall --help`.
|
||||||
|
|
||||||
For packages with `binstall` support, the command `cargo binstall PACKAGE` will then look-up the crate metadata for the specified (or latest) version, fetch the associated binary package, and install this to `$HOME/.cargo/bin` (or `$HOME/.bin` if this is unavailable). See [Installing Binaries](#Installing-Binaries) for further information on using `binstall`.
|
To support `binstall` maintainers must add configuration values to `Cargo.toml` to allow the tool to locate the appropriate CI-produced binary package for a given version and target. See [Supporting Binary Installation](#Supporting-Binary-Installation) for instructions on how to support `binstall` in your projects.
|
||||||
|
|
||||||
Cargo metadata is used to avoid the need for an additional centralised index or binary repository, and to provide project maintainers with the maximum possible agency for binary distribution with no additional dependencies and minimal additional complexity. This is part experiment, part solving a personal problem, and part hope that we can solve / never re-visit this. I hope you find it helpful and, good luck!
|
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||

|

|
||||||
[](https://github.com/ryankurte/cargo-binstall)
|
[](https://github.com/ryankurte/cargo-binstall)
|
||||||
[](https://crates.io/crates/cargo-binstall)
|
[](https://crates.io/crates/cargo-binstall)
|
||||||
[](https://docs.rs/cargo-binstall)
|
[](https://docs.rs/cargo-binstal
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Manifest discovery
|
- Manifest discovery
|
||||||
- [x] Fetch manifest via crates.io
|
- [x] Fetch crate/manifest via crates.io
|
||||||
- [ ] Fetch manifest via git
|
- [ ] Fetch crate/manifest via git
|
||||||
- [x] Use local manifest (`--manifest-path`)
|
- [x] Use local crate/manifest (`--manifest-path`)
|
||||||
- Package formats
|
- Package formats
|
||||||
- [x] Tgz
|
- [x] Tgz
|
||||||
- [x] Tar
|
- [x] Tar
|
||||||
- [x] Bin
|
- [x] Bin
|
||||||
- Extraction / Transformation
|
- Extraction / Transformation
|
||||||
- [ ] Extract from subdirectory in archive (ie. support archives with platform or target subdirectories)
|
- [x] Extract from subdirectory in archive (ie. support archives with platform or target subdirectories)
|
||||||
- [ ] Extract specific files from archive (ie. support single archive with multiple platform binaries)
|
- [x] Extract specific files from archive (ie. support single archive with multiple platform binaries)
|
||||||
- Security
|
- Security
|
||||||
- [ ] Package signing
|
- [ ] Package signing
|
||||||
- [ ] Package verification
|
- [ ] Package verification
|
||||||
|
|
||||||
## Installing Binaries
|
|
||||||
|
|
||||||
First you'll need to install `cargo-binstall` either via `cargo install cargo-binstall` (and it'll have to compile, sorry...), or by grabbing a pre-compiled version from the [releases](https://github.com/ryankurte/cargo-binstall/releases) page and putting that somewhere on your path. It's like there's a problem we're trying to solve?
|
|
||||||
|
|
||||||
Once a project supports `binstall` you can then install binaries via `cargo binstall NAME` where `NAME` is the name of the crate. This will then fetch the metadata for the provided crate, lookup the associated binary file, and download this onto your system.
|
|
||||||
By default the latest version is installed, which can be overridden using the `--version` argument, and packages are installed to `$HOME/.cargo/bin` as is consistent with `cargo install`, which can be overridden via the `--install-path` argument. As always `--help` will show available options.
|
|
||||||
|
|
||||||
We hope the defaults will work without configuration in _some_ cases, however, different projects have wildly different CI and build output configurations. You will likely need to add some cargo metadata to support `binstall` in your project, see [Supporting Binary Installation](#Supporting-Binary-Installation) for details.
|
|
||||||
|
|
||||||
|
|
||||||
## Supporting Binary Installation
|
## Supporting Binary Installation
|
||||||
|
|
||||||
`cargo-binstall` installs binary packages first by reading `[package.metadata]` values from the Cargo manifest (`Cargo.toml`) to discover a template URL, then by building a download path from this template, and finally by downloading and extracting the binary package onto the users path.
|
`binstall` works with existing CI-built binary outputs, with configuration via `[package.metadata.binstall]` keys in the relevant crate manifest.
|
||||||
|
When configuring `binstall` you can test against a local manifest with `--manifest-path=PATH` argument to use the crate and manifest at the provided `PATH`, skipping crate discovery and download.
|
||||||
|
|
||||||
To support `binstall` first you need working CI that places binary outputs at a reasonably deterministic location (github releases, S3 bucket, so long as it's internet accessible you're good to go), then to add configuration values to your Cargo manifest to specify a template string for `binstall` to use when downloading packages.
|
|
||||||
|
|
||||||
By default `binstall` will look for pre-built packages using the following template:
|
By default `binstall` is setup to work with github releases, and expects to find:
|
||||||
```
|
|
||||||
{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }`,
|
- an archive named `{ name }-{ target }-v{ version }.tgz`
|
||||||
|
- so that this does not overwrite different targets or versions when manually downloaded
|
||||||
|
- located at `{ repo }/releases/download/v{ version }/`
|
||||||
|
- compatible with github tags / releases
|
||||||
|
- containing a folder named `{ name }-{ target }-v{ version }`
|
||||||
|
- so that prior binary files are not overwritten when manually executing `tar -xvf ...`
|
||||||
|
- containing binary files in the form `{ bin }{ format }` (where `bin` is the cargo binary name and `format` is `.exe` on windows and empty on other platforms)
|
||||||
|
|
||||||
|
|
||||||
|
These defaults can be overridden using the following configuration keys:
|
||||||
|
|
||||||
|
- `pkg-url` specifies the binary package URL for a given target/version, templated (defaults to: `{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }`)
|
||||||
|
- `bin-path` specifies the binary path within the package, templated (defaults to: `{ name }-{ target }-v{ version }/{ bin }` with a `.exe` suffix on windows)
|
||||||
|
- `pkg-fmt` overrides the package format for download/extraction (defaults to: `tgz`)
|
||||||
|
|
||||||
|
|
||||||
|
Template variables use the format `{ VAR }` where `VAR` is the name of the variable, with the following variables available:
|
||||||
|
- `name` is the name of the crate / package
|
||||||
|
- `version` is the crate version (per `--version` and the crate manifest)
|
||||||
|
- `repo` is the repository linked in `Cargo.toml`
|
||||||
|
- `bin` is the name of a specific binary, inferred from the crate configuration
|
||||||
|
- `target` is the rust target name (defaults to your architecture, but can be overridden using the `--target` command line option if required().
|
||||||
|
|
||||||
|
|
||||||
|
### Operation
|
||||||
|
|
||||||
|
- Lookup a viable crate version (currently via `crates.io`, in future via git tags too)
|
||||||
|
- Download crate snapshot (currently via `crates.io`)
|
||||||
|
- Parse configuration metadata and binary information from the downloaded snapshot
|
||||||
|
- Download and extract binary package using configured URL (`pkg-url`, `pkg-fmt`)
|
||||||
|
- Install versioned binary files to the relevant install dir
|
||||||
|
- Generate symlinks to versioned binaries
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
For example, the default configuration (if specified in `Cargo.toml`) would be:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package.metadata.binstall]
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }"
|
||||||
|
bin-dir = "{ name }-{ target }-v{ version }/{ bin }{ format }"
|
||||||
|
pkg-fmt = "tgz"
|
||||||
```
|
```
|
||||||
|
|
||||||
Template variables use the format `{ NAME }` where `NAME` is the name of the variable.
|
For a crate called `radio-sx128x` ( at version `v0.14.1-alpha.5` on x86_64 linux), this would be interpolated to:
|
||||||
`repo`, `name`, and `version` are those specified in the crate manifest (`Cargo.toml`).
|
|
||||||
`target` defaults to your (the machine calling `cargo binstall`) architecture, but can be overridden using the `--target` command line option if required.
|
|
||||||
`format` defaults to `tgz` and can be specified via the `pkg-fmt` key under `[package.metadata]`. You may need this if you have sneaky `tgz` files that are actually not gzipped.
|
|
||||||
|
|
||||||
For example, for the `radio-sx128x` crate at version `v0.14.1-alpha.5`, this would be interpolated to a download URL of:
|
- A download URL of `https://github.com/rust-iot/rust-radio-sx128x/releases/download/v0.14.1-alpha.5/rust-radio-sx128x-x86_64-unknown-linux-gnu-v0.14.1-alpha.5.tgz`
|
||||||
```
|
- Containing a single binary file `rust-radio-sx128x-x86_64-unknown-linux-gnu-v0.14.1-alpha.5/rust-radio-x86_64-unknown-linux-gnu`
|
||||||
https://github.com/rust-iot/rust-radio-sx128x/releases/download/v0.14.1-alpha.5/rust-radio-sx128x-x86_64-unknown-linux-gnu-v0.14.1-alpha.5.tgz`
|
- Installed to`$HOME/.cargo/bin/rust-radio-sx128x-v0.14.1-alpha.5`
|
||||||
|
- With a symlink from `$HOME/.cargo/bin/rust-radio-sx128x`
|
||||||
|
|
||||||
|
#### If the package name does not match the crate name
|
||||||
|
|
||||||
|
As is common with libraries / utilities (and the `radio-sx128x` example), this can be overridden by specifying:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package.metadata.binstall]
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ format }"
|
||||||
```
|
```
|
||||||
|
|
||||||
Custom template URLS may be specified using the `pkg-url` field under `[package.metadata]`, using the same keywords for interpolation as discussed above. Again for the `radio-sx128x` package this could be:
|
Which provides a download URL of: `https://github.com/rust-iot/rust-radio-sx128x/releases/download/v0.14.1-alpha.5/sx128x-util-x86_64-unknown-linux-gnu-v0.14.1-alpha.5.tgz`
|
||||||
|
|
||||||
```
|
|
||||||
[package.metadata]
|
#### If the package structure differs from the default
|
||||||
pkg-url = "https://github.com/ryankurte/rust-radio-sx128x/releases/download/v{ version }/radio-sx128x-{ target }-v{ version }.tgz"
|
|
||||||
|
While it's nice to have the default format discussed above, often it's not the case...
|
||||||
|
|
||||||
|
Were the package to contain binaries in the form `name-target[.exe]`, this could be specified as:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package.metadata.binstall]
|
||||||
|
bin-dir = "{ bin }-{ target }{ format }"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you have separate crate and package names, you can specify a `pkg-name` under `[package.metadata]`, replacing _only_ the `{ name }` field in the default template.
|
Which provides a binary path of: `sx128x-util-x86_64-unknown-linux-gnu[.exe]` (binary names are inferred from the crate, so long as cargo builds them this _should_ just work).
|
||||||
This is useful if you have a library crate with utilities, and the crate and binary package names differ, but can equally well be addressed via defining the `pkg-url` field as described above.
|
|
||||||
For example, the real-world `ryankurte/radio-sx128x` crate produces a `sx128x-util` package, which can be configured using the following:
|
|
||||||
|
|
||||||
```
|
|
||||||
[package.metadata]
|
|
||||||
pkg-name = "sx128x-util"
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you've added a `pkg-name` or a `pkg-url` you should be good to go! For the purposes of testing this integration you may find it useful to manually specify the manifest path using the `--manifest-path=PATH` argument, this skips discovery of the crate manifest and uses the one at the provided `PATH`. For testing purposes you can also override a variety of configuration options on the command line, see `--help` for more details.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
If anything is not working the way you expect, add a `--log-level debug` to see debug information, and feel free to open an issue or PR.
|
If you have ideas / contributions or anything is not working the way you expect (in which case, please include an output with `--log-level debug`) and feel free to open an issue or PR.
|
||||||
|
|
118
src/drivers.rs
Normal file
118
src/drivers.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use log::{debug};
|
||||||
|
use anyhow::{Context, anyhow};
|
||||||
|
use semver::{Version, VersionReq};
|
||||||
|
|
||||||
|
use crates_io_api::AsyncClient;
|
||||||
|
|
||||||
|
use crate::PkgFmt;
|
||||||
|
use crate::helpers::*;
|
||||||
|
|
||||||
|
fn find_version<'a, V: Iterator<Item=&'a str>>(requirement: &str, version_iter: V) -> Result<String, anyhow::Error> {
|
||||||
|
// Parse version requirement
|
||||||
|
let version_req = VersionReq::parse(requirement)?;
|
||||||
|
|
||||||
|
// Filter for matching versions
|
||||||
|
let mut filtered: Vec<_> = version_iter.filter(|v| {
|
||||||
|
// Remove leading `v` for git tags
|
||||||
|
let ver_str = match v.strip_prefix("s") {
|
||||||
|
Some(v) => v,
|
||||||
|
None => v,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse out version
|
||||||
|
let ver = match Version::parse(ver_str) {
|
||||||
|
Ok(sv) => sv,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Version: {:?}", ver);
|
||||||
|
|
||||||
|
// Filter by version match
|
||||||
|
version_req.matches(&ver)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
// Sort by highest matching version
|
||||||
|
filtered.sort_by(|a, b| {
|
||||||
|
let a = Version::parse(a).unwrap();
|
||||||
|
let b = Version::parse(b).unwrap();
|
||||||
|
|
||||||
|
b.partial_cmp(&a).unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
debug!("Filtered: {:?}", filtered);
|
||||||
|
|
||||||
|
// Return highest version
|
||||||
|
match filtered.get(0) {
|
||||||
|
Some(v) => Ok(v.to_string()),
|
||||||
|
None => Err(anyhow!("No matching version for requirement: '{}'", version_req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch a crate by name and version from crates.io
|
||||||
|
pub async fn fetch_crate_cratesio(name: &str, version_req: &str, temp_dir: &Path) -> Result<PathBuf, anyhow::Error> {
|
||||||
|
|
||||||
|
// Fetch / update index
|
||||||
|
debug!("Updating crates.io index");
|
||||||
|
let index = crates_index::Index::new_cargo_default();
|
||||||
|
index.retrieve_or_update()?;
|
||||||
|
|
||||||
|
// Lookup crate in index
|
||||||
|
debug!("Looking up crate information");
|
||||||
|
let base_info = match index.crate_(name) {
|
||||||
|
Some(i) => i,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow::anyhow!("Error fetching information for crate {}", name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Locate matching version
|
||||||
|
let version_iter = base_info.versions().iter().map(|v| v.version() );
|
||||||
|
let version_name = find_version(version_req, version_iter)?;
|
||||||
|
|
||||||
|
// Build crates.io api client
|
||||||
|
let api_client = AsyncClient::new("cargo-binstall (https://github.com/ryankurte/cargo-binstall)", Duration::from_millis(100))?;
|
||||||
|
|
||||||
|
// Fetch online crate information
|
||||||
|
let crate_info = api_client.get_crate(name.as_ref()).await
|
||||||
|
.context("Error fetching crate information")?;
|
||||||
|
|
||||||
|
// Fetch information for the filtered version
|
||||||
|
let version = match crate_info.versions.iter().find(|v| v.num == version_name) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow::anyhow!("No information found for crate: '{}' version: '{}'",
|
||||||
|
name, version_name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Found information for crate version: '{}'", version.num);
|
||||||
|
|
||||||
|
// Download crate to temporary dir (crates.io or git?)
|
||||||
|
let crate_url = format!("https://crates.io/{}", version.dl_path);
|
||||||
|
let tgz_path = temp_dir.join(format!("{}.tgz", name));
|
||||||
|
|
||||||
|
debug!("Fetching crate from: {}", crate_url);
|
||||||
|
|
||||||
|
// Download crate
|
||||||
|
download(&crate_url, &tgz_path).await?;
|
||||||
|
|
||||||
|
// Decompress downloaded tgz
|
||||||
|
debug!("Decompressing crate archive");
|
||||||
|
extract(&tgz_path, PkgFmt::Tgz, &temp_dir)?;
|
||||||
|
let crate_path = temp_dir.join(format!("{}-{}", name, version_name));
|
||||||
|
|
||||||
|
// Return crate directory
|
||||||
|
Ok(crate_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch a crate by name and version from github
|
||||||
|
/// TODO: implement this
|
||||||
|
pub async fn fetch_crate_gh_releases(_name: &str, _version: Option<&str>, _temp_dir: &Path) -> Result<PathBuf, anyhow::Error> {
|
||||||
|
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
131
src/helpers.rs
Normal file
131
src/helpers.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use log::{debug, info, error};
|
||||||
|
|
||||||
|
use cargo_toml::{Manifest};
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
|
use tar::Archive;
|
||||||
|
|
||||||
|
|
||||||
|
use crate::{Meta};
|
||||||
|
|
||||||
|
use super::PkgFmt;
|
||||||
|
|
||||||
|
/// Load binstall metadata from the crate `Cargo.toml` at the provided path
|
||||||
|
pub fn load_manifest_path<P: AsRef<Path>>(manifest_path: P) -> Result<Manifest<Meta>, anyhow::Error> {
|
||||||
|
debug!("Reading manifest: {}", manifest_path.as_ref().display());
|
||||||
|
|
||||||
|
// Load and parse manifest (this checks file system for binary output names)
|
||||||
|
let manifest = Manifest::<Meta>::from_path_with_metadata(manifest_path)?;
|
||||||
|
|
||||||
|
// Return metadata
|
||||||
|
Ok(manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Download a file from the provided URL to the provided path
|
||||||
|
pub async fn download<P: AsRef<Path>>(url: &str, path: P) -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
|
debug!("Downloading from: '{}'", url);
|
||||||
|
|
||||||
|
let resp = reqwest::get(url).await?;
|
||||||
|
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
error!("Download error: {}", resp.status());
|
||||||
|
return Err(anyhow::anyhow!(resp.status()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = resp.bytes().await?;
|
||||||
|
|
||||||
|
debug!("Download OK, writing to file: '{:?}'", path.as_ref());
|
||||||
|
|
||||||
|
std::fs::write(&path, bytes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract files from the specified source onto the specified path
|
||||||
|
pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(source: S, fmt: PkgFmt, path: P) -> Result<(), anyhow::Error> {
|
||||||
|
match fmt {
|
||||||
|
PkgFmt::Tar => {
|
||||||
|
// Extract to install dir
|
||||||
|
debug!("Extracting from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
||||||
|
|
||||||
|
let dat = std::fs::File::open(source)?;
|
||||||
|
let mut tar = Archive::new(dat);
|
||||||
|
|
||||||
|
tar.unpack(path)?;
|
||||||
|
},
|
||||||
|
PkgFmt::Tgz => {
|
||||||
|
// Extract to install dir
|
||||||
|
debug!("Decompressing from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
||||||
|
|
||||||
|
let dat = std::fs::File::open(source)?;
|
||||||
|
let tar = GzDecoder::new(dat);
|
||||||
|
let mut tgz = Archive::new(tar);
|
||||||
|
|
||||||
|
tgz.unpack(path)?;
|
||||||
|
},
|
||||||
|
PkgFmt::Bin => {
|
||||||
|
debug!("Copying data from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
||||||
|
// Copy to install dir
|
||||||
|
std::fs::copy(source, path)?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch install path from environment
|
||||||
|
/// roughly follows https://doc.rust-lang.org/cargo/commands/cargo-install.html#description
|
||||||
|
pub fn get_install_path<P: AsRef<Path>>(install_path: Option<P>) -> Option<PathBuf> {
|
||||||
|
// Command line override first first
|
||||||
|
if let Some(p) = install_path {
|
||||||
|
return Some(PathBuf::from(p.as_ref()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environmental variables
|
||||||
|
if let Ok(p) = std::env::var("CARGO_INSTALL_ROOT") {
|
||||||
|
debug!("using CARGO_INSTALL_ROOT ({})", p);
|
||||||
|
let b = PathBuf::from(p);
|
||||||
|
return Some(b.join("bin"));
|
||||||
|
}
|
||||||
|
if let Ok(p) = std::env::var("CARGO_HOME") {
|
||||||
|
debug!("using CARGO_HOME ({})", p);
|
||||||
|
let b = PathBuf::from(p);
|
||||||
|
return Some(b.join("bin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard $HOME/.cargo/bin
|
||||||
|
if let Some(d) = dirs::home_dir() {
|
||||||
|
let d = d.join(".cargo/bin");
|
||||||
|
if d.exists() {
|
||||||
|
debug!("using $HOME/.cargo/bin");
|
||||||
|
|
||||||
|
return Some(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local executable dir if no cargo is found
|
||||||
|
if let Some(d) = dirs::executable_dir() {
|
||||||
|
debug!("Fallback to {}", d.display());
|
||||||
|
return Some(d.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn confirm() -> Result<bool, anyhow::Error> {
|
||||||
|
info!("Do you wish to continue? yes/no");
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin().read_line(&mut input)?;
|
||||||
|
|
||||||
|
match input.as_str().trim() {
|
||||||
|
"yes" => Ok(true),
|
||||||
|
"no" => Ok(false),
|
||||||
|
_ => {
|
||||||
|
Err(anyhow::anyhow!("Valid options are 'yes', 'no', please try again"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
src/lib.rs
Normal file
158
src/lib.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use strum_macros::{Display, EnumString, EnumVariantNames};
|
||||||
|
use tinytemplate::TinyTemplate;
|
||||||
|
|
||||||
|
|
||||||
|
pub mod helpers;
|
||||||
|
pub use helpers::*;
|
||||||
|
|
||||||
|
pub mod drivers;
|
||||||
|
pub use drivers::*;
|
||||||
|
|
||||||
|
|
||||||
|
/// Compiled target triple, used as default for binary fetching
|
||||||
|
pub const TARGET: &'static str = env!("TARGET");
|
||||||
|
|
||||||
|
/// Default package path template (may be overridden in package Cargo.toml)
|
||||||
|
pub const DEFAULT_PKG_URL: &'static str = "{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }";
|
||||||
|
|
||||||
|
/// Default binary name template (may be overridden in package Cargo.toml)
|
||||||
|
pub const DEFAULT_BIN_PATH: &'static str = "{ name }-{ target }-v{ version }/{ bin }{ format }";
|
||||||
|
|
||||||
|
|
||||||
|
/// Binary format enumeration
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[derive(Display, EnumString, EnumVariantNames)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum PkgFmt {
|
||||||
|
/// Download format is TAR (uncompressed)
|
||||||
|
Tar,
|
||||||
|
/// Download format is TGZ (TAR + GZip)
|
||||||
|
Tgz,
|
||||||
|
/// Download format is raw / binary
|
||||||
|
Bin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PkgFmt {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Tgz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `binstall` metadata container
|
||||||
|
///
|
||||||
|
/// Required to nest metadata under `package.metadata.binstall`
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct Meta {
|
||||||
|
pub binstall: Option<PkgMeta>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata for binary installation use.
|
||||||
|
///
|
||||||
|
/// Exposed via `[package.metadata]` in `Cargo.toml`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", default)]
|
||||||
|
pub struct PkgMeta {
|
||||||
|
/// URL template for package downloads
|
||||||
|
pub pkg_url: String,
|
||||||
|
|
||||||
|
/// Format for package downloads
|
||||||
|
pub pkg_fmt: PkgFmt,
|
||||||
|
|
||||||
|
/// Path template for binary files in packages
|
||||||
|
pub bin_dir: String,
|
||||||
|
|
||||||
|
/// Public key for package verification (base64 encoded)
|
||||||
|
pub pub_key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PkgMeta {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pkg_url: DEFAULT_PKG_URL.to_string(),
|
||||||
|
pkg_fmt: PkgFmt::default(),
|
||||||
|
bin_dir: DEFAULT_BIN_PATH.to_string(),
|
||||||
|
pub_key: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct BinMeta {
|
||||||
|
/// Binary name
|
||||||
|
pub name: String,
|
||||||
|
/// Binary template path (within package)
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Template for constructing download paths
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct Context {
|
||||||
|
pub name: String,
|
||||||
|
pub repo: Option<String>,
|
||||||
|
pub target: String,
|
||||||
|
pub version: String,
|
||||||
|
pub format: String,
|
||||||
|
pub bin: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
/// Render the context into the provided template
|
||||||
|
pub fn render(&self, template: &str) -> Result<String, anyhow::Error> {
|
||||||
|
// Create template instance
|
||||||
|
let mut tt = TinyTemplate::new();
|
||||||
|
|
||||||
|
// Add template to instance
|
||||||
|
tt.add_template("path", &template)?;
|
||||||
|
|
||||||
|
// Render output
|
||||||
|
let rendered = tt.render("path", self)?;
|
||||||
|
|
||||||
|
Ok(rendered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{load_manifest_path};
|
||||||
|
|
||||||
|
use cargo_toml::Product;
|
||||||
|
|
||||||
|
fn init() {
|
||||||
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_meta() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
let mut manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
manifest_dir.push_str("/Cargo.toml");
|
||||||
|
|
||||||
|
let manifest = load_manifest_path(&manifest_dir).expect("Error parsing metadata");
|
||||||
|
let package = manifest.package.unwrap();
|
||||||
|
let meta = package.metadata.map(|m| m.binstall ).flatten().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(&package.name, "cargo-binstall");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&meta.pkg_url,
|
||||||
|
"https://github.com/ryankurte/cargo-binstall/releases/download/v{ version }/cargo-binstall-{ target }.tgz"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
manifest.bin.as_slice(),
|
||||||
|
&[
|
||||||
|
Product{
|
||||||
|
name: Some("cargo-binstall".to_string()),
|
||||||
|
path: Some("src/main.rs".to_string()),
|
||||||
|
edition: Some(cargo_toml::Edition::E2018),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
456
src/main.rs
456
src/main.rs
|
@ -1,114 +1,61 @@
|
||||||
use std::time::Duration;
|
use std::path::{PathBuf};
|
||||||
use std::path::{PathBuf, Path};
|
|
||||||
|
|
||||||
use log::{debug, info, error, LevelFilter};
|
use log::{debug, info, warn, error, LevelFilter};
|
||||||
use simplelog::{TermLogger, ConfigBuilder, TerminalMode};
|
use simplelog::{TermLogger, ConfigBuilder, TerminalMode};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
use crates_io_api::AsyncClient;
|
|
||||||
use cargo_toml::Manifest;
|
|
||||||
|
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
use flate2::read::GzDecoder;
|
|
||||||
use tar::Archive;
|
|
||||||
|
|
||||||
use tinytemplate::TinyTemplate;
|
use cargo_binstall::*;
|
||||||
|
|
||||||
/// Compiled target triple, used as default for binary fetching
|
|
||||||
const TARGET: &'static str = env!("TARGET");
|
|
||||||
|
|
||||||
/// Default binary path for use if no path is specified
|
|
||||||
const DEFAULT_BIN_PATH: &'static str = "{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }";
|
|
||||||
|
|
||||||
/// Binary format enumeration
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[derive(strum_macros::Display, strum_macros::EnumString, strum_macros::EnumVariantNames)]
|
|
||||||
#[strum(serialize_all = "snake_case")]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum PkgFmt {
|
|
||||||
/// Download format is TAR (uncompressed)
|
|
||||||
Tar,
|
|
||||||
/// Download format is TGZ (TAR + GZip)
|
|
||||||
Tgz,
|
|
||||||
/// Download format is raw / binary
|
|
||||||
Bin,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
struct Options {
|
struct Options {
|
||||||
/// Crate name to install
|
/// Package name or URL for installation
|
||||||
|
/// This must be either a crates.io package name or github or gitlab url
|
||||||
#[structopt()]
|
#[structopt()]
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
/// Crate version to install
|
/// Filter for package version to install
|
||||||
#[structopt(long)]
|
#[structopt(long, default_value = "*")]
|
||||||
version: Option<String>,
|
version: String,
|
||||||
|
|
||||||
/// Override the package path template.
|
/// Override binary target, ignoring compiled version
|
||||||
/// If no `metadata.pkg_url` key is set or `--pkg-url` argument provided, this
|
#[structopt(long, default_value = TARGET)]
|
||||||
/// defaults to `{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.tgz`
|
target: String,
|
||||||
#[structopt(long)]
|
|
||||||
pkg_url: Option<String>,
|
|
||||||
|
|
||||||
/// Override format for binary file download.
|
|
||||||
/// Defaults to `tgz`
|
|
||||||
#[structopt(long)]
|
|
||||||
pkg_fmt: Option<PkgFmt>,
|
|
||||||
|
|
||||||
/// Override the package name.
|
|
||||||
/// This is only useful for diagnostics when using the default `pkg_url`
|
|
||||||
/// as you can otherwise customise this in the path.
|
|
||||||
/// Defaults to the crate name.
|
|
||||||
#[structopt(long)]
|
|
||||||
pkg_name: Option<String>,
|
|
||||||
|
|
||||||
/// Override install path for downloaded binary.
|
/// Override install path for downloaded binary.
|
||||||
/// Defaults to `$HOME/.cargo/bin`
|
/// Defaults to `$HOME/.cargo/bin`
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
install_path: Option<String>,
|
install_path: Option<String>,
|
||||||
|
|
||||||
/// Override binary target, ignoring compiled version
|
/// Disable symlinking / versioned updates
|
||||||
#[structopt(long, default_value = TARGET)]
|
#[structopt(long)]
|
||||||
target: String,
|
no_symlinks: bool,
|
||||||
|
|
||||||
|
/// Dry run, fetch and show changes without installing binaries
|
||||||
|
#[structopt(long)]
|
||||||
|
dry_run: bool,
|
||||||
|
|
||||||
|
/// Disable interactive mode / confirmation
|
||||||
|
#[structopt(long)]
|
||||||
|
no_confirm: bool,
|
||||||
|
|
||||||
|
/// Do not cleanup temporary files on success
|
||||||
|
#[structopt(long)]
|
||||||
|
no_cleanup: bool,
|
||||||
|
|
||||||
/// Override manifest source.
|
/// Override manifest source.
|
||||||
/// This skips searching crates.io for a manifest and uses
|
/// This skips searching crates.io for a manifest and uses
|
||||||
/// the specified path directly, useful for debugging
|
/// the specified path directly, useful for debugging and
|
||||||
|
/// when adding `binstall` support.
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
manifest_path: Option<PathBuf>,
|
manifest_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Utility log level
|
/// Utility log level
|
||||||
#[structopt(long, default_value = "info")]
|
#[structopt(long, default_value = "info")]
|
||||||
log_level: LevelFilter,
|
log_level: LevelFilter,
|
||||||
|
|
||||||
/// Do not cleanup temporary files on success
|
|
||||||
#[structopt(long)]
|
|
||||||
no_cleanup: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Metadata for cargo-binstall exposed via cargo.toml
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct Meta {
|
|
||||||
/// Path template override for binary downloads
|
|
||||||
pub pkg_url: Option<String>,
|
|
||||||
/// Package name override for binary downloads
|
|
||||||
pub pkg_name: Option<String>,
|
|
||||||
/// Format override for binary downloads
|
|
||||||
pub pkg_fmt: Option<PkgFmt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Template for constructing download paths
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct Context {
|
|
||||||
name: String,
|
|
||||||
repo: Option<String>,
|
|
||||||
target: String,
|
|
||||||
version: String,
|
|
||||||
format: PkgFmt,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -136,91 +83,42 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
// Create a temporary directory for downloads etc.
|
// Create a temporary directory for downloads etc.
|
||||||
let temp_dir = TempDir::new("cargo-binstall")?;
|
let temp_dir = TempDir::new("cargo-binstall")?;
|
||||||
|
|
||||||
|
info!("Installing package: '{}'", opts.name);
|
||||||
|
|
||||||
// Fetch crate via crates.io, git, or use a local manifest path
|
// Fetch crate via crates.io, git, or use a local manifest path
|
||||||
// TODO: work out which of these to do based on `opts.name`
|
// TODO: work out which of these to do based on `opts.name`
|
||||||
let crate_path = match opts.manifest_path {
|
// TODO: support git-based fetches (whole repo name rather than just crate name)
|
||||||
|
let manifest_path = match opts.manifest_path.clone() {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => fetch_crate_cratesio(&opts.name, opts.version.as_deref(), temp_dir.path()).await?,
|
None => fetch_crate_cratesio(&opts.name, &opts.version, temp_dir.path()).await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read cargo manifest
|
debug!("Reading manifest: {}", manifest_path.display());
|
||||||
let manifest_path = crate_path.join("Cargo.toml");
|
let manifest = load_manifest_path(manifest_path.join("Cargo.toml"))?;
|
||||||
|
let package = manifest.package.unwrap();
|
||||||
|
|
||||||
debug!("Reading manifest: {}", manifest_path.to_str().unwrap());
|
let (meta, binaries) = (
|
||||||
let package = match Manifest::<Meta>::from_path_with_metadata(&manifest_path) {
|
package.metadata.map(|m| m.binstall ).flatten().unwrap_or(PkgMeta::default()),
|
||||||
Ok(m) => m.package.unwrap(),
|
manifest.bin,
|
||||||
Err(e) => {
|
);
|
||||||
error!("Error reading manifest '{}': {:?}", manifest_path.to_str().unwrap(), e);
|
|
||||||
return Err(e.into());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let meta = package.metadata;
|
// Generate context for URL interpolation
|
||||||
debug!("Retrieved metadata: {:?}", meta);
|
|
||||||
|
|
||||||
// Select which binary path to use
|
|
||||||
let pkg_url = match (opts.pkg_url, meta.as_ref().map(|m| m.pkg_url.clone() ).flatten()) {
|
|
||||||
(Some(p), _) => {
|
|
||||||
info!("Using package url override: '{}'", p);
|
|
||||||
p
|
|
||||||
},
|
|
||||||
(_, Some(m)) => {
|
|
||||||
info!("Using package url: '{}'", &m);
|
|
||||||
m
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
info!("No `pkg-url` key found in Cargo.toml or `--pkg-url` argument provided");
|
|
||||||
info!("Using default url: {}", DEFAULT_BIN_PATH);
|
|
||||||
DEFAULT_BIN_PATH.to_string()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Select bin format to use
|
|
||||||
let pkg_fmt = match (opts.pkg_fmt, meta.as_ref().map(|m| m.pkg_fmt.clone() ).flatten()) {
|
|
||||||
(Some(o), _) => o,
|
|
||||||
(_, Some(m)) => m.clone(),
|
|
||||||
_ => PkgFmt::Tgz,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Override package name if required
|
|
||||||
let pkg_name = match (&opts.pkg_name, meta.as_ref().map(|m| m.pkg_name.clone() ).flatten()) {
|
|
||||||
(Some(o), _) => o.clone(),
|
|
||||||
(_, Some(m)) => m,
|
|
||||||
_ => opts.name.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate context for interpolation
|
|
||||||
let ctx = Context {
|
let ctx = Context {
|
||||||
name: pkg_name.to_string(),
|
name: opts.name.clone(),
|
||||||
repo: package.repository,
|
repo: package.repository,
|
||||||
target: opts.target.clone(),
|
target: opts.target.clone(),
|
||||||
version: package.version.clone(),
|
version: package.version.clone(),
|
||||||
format: pkg_fmt.clone(),
|
format: meta.pkg_fmt.to_string(),
|
||||||
|
bin: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Using context: {:?}", ctx);
|
debug!("Using context: {:?}", ctx);
|
||||||
|
|
||||||
// Interpolate version / target / etc.
|
// Interpolate version / target / etc.
|
||||||
let mut tt = TinyTemplate::new();
|
let rendered = ctx.render(&meta.pkg_url)?;
|
||||||
tt.add_template("path", &pkg_url)?;
|
|
||||||
let rendered = tt.render("path", &ctx)?;
|
|
||||||
|
|
||||||
info!("Downloading package from: '{}'", rendered);
|
|
||||||
|
|
||||||
// Download package
|
|
||||||
let pkg_path = temp_dir.path().join(format!("pkg-{}.{}", pkg_name, pkg_fmt));
|
|
||||||
download(&rendered, pkg_path.to_str().unwrap()).await?;
|
|
||||||
|
|
||||||
|
|
||||||
if opts.no_cleanup {
|
|
||||||
// Do not delete temporary directory
|
|
||||||
let _ = temp_dir.into_path();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check signature
|
|
||||||
|
|
||||||
// Compute install directory
|
// Compute install directory
|
||||||
let install_path = match get_install_path(opts.install_path) {
|
let install_path = match get_install_path(opts.install_path.as_deref()) {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => {
|
None => {
|
||||||
error!("No viable install path found of specified, try `--install-path`");
|
error!("No viable install path found of specified, try `--install-path`");
|
||||||
|
@ -228,152 +126,128 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Install package
|
debug!("Using install path: {}", install_path.display());
|
||||||
info!("Installing to: '{}'", install_path);
|
|
||||||
extract(&pkg_path, pkg_fmt, &install_path)?;
|
|
||||||
|
|
||||||
|
info!("Downloading package from: '{}'", rendered);
|
||||||
|
|
||||||
info!("Installation done!");
|
// Download package
|
||||||
|
let pkg_path = temp_dir.path().join(format!("pkg-{}.{}", opts.name, meta.pkg_fmt));
|
||||||
|
download(&rendered, pkg_path.to_str().unwrap()).await?;
|
||||||
|
|
||||||
|
#[cfg(incomplete)]
|
||||||
|
{
|
||||||
|
// Fetch and check package signature if available
|
||||||
|
if let Some(pub_key) = meta.as_ref().map(|m| m.pub_key.clone() ).flatten() {
|
||||||
|
debug!("Found public key: {}", pub_key);
|
||||||
|
|
||||||
|
// Generate signature file URL
|
||||||
|
let mut sig_ctx = ctx.clone();
|
||||||
|
sig_ctx.format = "sig".to_string();
|
||||||
|
let sig_url = sig_ctx.render(&pkg_url)?;
|
||||||
|
|
||||||
|
debug!("Fetching signature file: {}", sig_url);
|
||||||
|
|
||||||
|
// Download signature file
|
||||||
|
let sig_path = temp_dir.path().join(format!("{}.sig", pkg_name));
|
||||||
|
download(&sig_url, &sig_path).await?;
|
||||||
|
|
||||||
|
// TODO: do the signature check
|
||||||
|
unimplemented!()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
warn!("No public key found, package signature could not be validated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract files
|
||||||
|
let bin_path = temp_dir.path().join(format!("bin-{}", opts.name));
|
||||||
|
extract(&pkg_path, meta.pkg_fmt, &bin_path)?;
|
||||||
|
|
||||||
|
// Bypass cleanup if disabled
|
||||||
|
if opts.no_cleanup {
|
||||||
|
let _ = temp_dir.into_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
if binaries.len() == 0 {
|
||||||
|
error!("No binaries specified (or inferred from file system)");
|
||||||
|
return Err(anyhow::anyhow!("No binaries specified (or inferred from file system)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// List files to be installed
|
||||||
|
// based on those found via Cargo.toml
|
||||||
|
let bin_files = binaries.iter().map(|p| {
|
||||||
|
// Fetch binary base name
|
||||||
|
let base_name = p.name.clone().unwrap();
|
||||||
|
|
||||||
|
// Generate binary path via interpolation
|
||||||
|
let mut bin_ctx = ctx.clone();
|
||||||
|
bin_ctx.bin = Some(base_name.clone());
|
||||||
|
|
||||||
|
// Append .exe to windows binaries
|
||||||
|
bin_ctx.format = match &opts.target.clone().contains("windows") {
|
||||||
|
true => ".exe".to_string(),
|
||||||
|
false => "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate install paths
|
||||||
|
// Source path is the download dir + the generated binary path
|
||||||
|
let source_file_path = bin_ctx.render(&meta.bin_dir)?;
|
||||||
|
let source = bin_path.join(&source_file_path);
|
||||||
|
|
||||||
|
// Destination path is the install dir + base-name-version{.format}
|
||||||
|
let dest_file_path = bin_ctx.render("{ bin }-v{ version }{ format }")?;
|
||||||
|
let dest = install_path.join(dest_file_path);
|
||||||
|
|
||||||
|
// Link at install dir + base name
|
||||||
|
let link = install_path.join(&base_name);
|
||||||
|
|
||||||
|
Ok((base_name, source, dest, link))
|
||||||
|
}).collect::<Result<Vec<_>, anyhow::Error>>()?;
|
||||||
|
|
||||||
|
// Prompt user for confirmation
|
||||||
|
info!("This will install the following binaries:");
|
||||||
|
for (name, source, dest, _link) in &bin_files {
|
||||||
|
info!(" - {} ({} -> {})", name, source.file_name().unwrap().to_string_lossy(), dest.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.no_symlinks {
|
||||||
|
info!("And create (or update) the following symlinks:");
|
||||||
|
for (name, _source, dest, link) in &bin_files {
|
||||||
|
info!(" - {} ({} -> {})", name, dest.display(), link.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.no_confirm && !confirm()? {
|
||||||
|
warn!("Installation cancelled");
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Installing binaries...");
|
||||||
|
|
||||||
|
// Install binaries
|
||||||
|
for (_name, source, dest, _link) in &bin_files {
|
||||||
|
// TODO: check if file already exists
|
||||||
|
std::fs::copy(source, dest)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate symlinks
|
||||||
|
if !opts.no_symlinks {
|
||||||
|
for (_name, _source, dest, link) in &bin_files {
|
||||||
|
// Remove existing symlink
|
||||||
|
// TODO: check if existing symlink is correct
|
||||||
|
if link.exists() {
|
||||||
|
std::fs::remove_file(&link)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
std::os::unix::fs::symlink(dest, link)?;
|
||||||
|
#[cfg(target_family = "windows")]
|
||||||
|
std::os::windows::fs::symlink_file(dest, link)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Installation complete!");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download a file from the provided URL to the provided path
|
|
||||||
async fn download<P: AsRef<Path>>(url: &str, path: P) -> Result<(), anyhow::Error> {
|
|
||||||
|
|
||||||
debug!("Downloading from: '{}'", url);
|
|
||||||
|
|
||||||
let resp = reqwest::get(url).await?;
|
|
||||||
|
|
||||||
if !resp.status().is_success() {
|
|
||||||
error!("Download error: {}", resp.status());
|
|
||||||
return Err(anyhow::anyhow!(resp.status()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = resp.bytes().await?;
|
|
||||||
|
|
||||||
debug!("Download OK, writing to file: '{:?}'", path.as_ref());
|
|
||||||
|
|
||||||
std::fs::write(&path, bytes)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract<S: AsRef<Path>, P: AsRef<Path>>(source: S, fmt: PkgFmt, path: P) -> Result<(), anyhow::Error> {
|
|
||||||
match fmt {
|
|
||||||
PkgFmt::Tar => {
|
|
||||||
// Extract to install dir
|
|
||||||
debug!("Extracting from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
|
||||||
|
|
||||||
let dat = std::fs::File::open(source)?;
|
|
||||||
let mut tar = Archive::new(dat);
|
|
||||||
|
|
||||||
tar.unpack(path)?;
|
|
||||||
},
|
|
||||||
PkgFmt::Tgz => {
|
|
||||||
// Extract to install dir
|
|
||||||
debug!("Decompressing from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
|
||||||
|
|
||||||
let dat = std::fs::File::open(source)?;
|
|
||||||
let tar = GzDecoder::new(dat);
|
|
||||||
let mut tgz = Archive::new(tar);
|
|
||||||
|
|
||||||
tgz.unpack(path)?;
|
|
||||||
},
|
|
||||||
PkgFmt::Bin => {
|
|
||||||
debug!("Copying data from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
|
||||||
// Copy to install dir
|
|
||||||
std::fs::copy(source, path)?;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch a crate by name and version from crates.io
|
|
||||||
async fn fetch_crate_cratesio(name: &str, version: Option<&str>, temp_dir: &Path) -> Result<PathBuf, anyhow::Error> {
|
|
||||||
// Build crates.io api client and fetch info
|
|
||||||
// TODO: support git-based fetches (whole repo name rather than just crate name)
|
|
||||||
let api_client = AsyncClient::new("cargo-binstall (https://github.com/ryankurte/cargo-binstall)", Duration::from_millis(100))?;
|
|
||||||
|
|
||||||
info!("Fetching information for crate: '{}'", name);
|
|
||||||
|
|
||||||
// Fetch overall crate info
|
|
||||||
let info = match api_client.get_crate(name.as_ref()).await {
|
|
||||||
Ok(i) => i,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Error fetching information for crate {}: {}", name, e);
|
|
||||||
return Err(e.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use specified or latest version
|
|
||||||
let version_num = match version {
|
|
||||||
Some(v) => v.to_string(),
|
|
||||||
None => info.crate_data.max_version,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch crates.io information for the specified version
|
|
||||||
// TODO: could do a semver match and sort here?
|
|
||||||
let version = match info.versions.iter().find(|v| v.num == version_num) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => {
|
|
||||||
error!("No crates.io information found for crate: '{}' version: '{}'",
|
|
||||||
name, version_num);
|
|
||||||
return Err(anyhow::anyhow!("No crate information found"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("Found information for crate version: '{}'", version.num);
|
|
||||||
|
|
||||||
// Download crate to temporary dir (crates.io or git?)
|
|
||||||
let crate_url = format!("https://crates.io/{}", version.dl_path);
|
|
||||||
let tgz_path = temp_dir.join(format!("{}.tgz", name));
|
|
||||||
|
|
||||||
debug!("Fetching crate from: {}", crate_url);
|
|
||||||
|
|
||||||
// Download crate
|
|
||||||
download(&crate_url, &tgz_path).await?;
|
|
||||||
|
|
||||||
// Decompress downloaded tgz
|
|
||||||
debug!("Decompressing crate archive");
|
|
||||||
extract(&tgz_path, PkgFmt::Tgz, &temp_dir)?;
|
|
||||||
let crate_path = temp_dir.join(format!("{}-{}", name, version_num));
|
|
||||||
|
|
||||||
// Return crate directory
|
|
||||||
Ok(crate_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch install path
|
|
||||||
/// roughly follows https://doc.rust-lang.org/cargo/commands/cargo-install.html#description
|
|
||||||
fn get_install_path(opt: Option<String>) -> Option<String> {
|
|
||||||
// Command line override first first
|
|
||||||
if let Some(p) = opt {
|
|
||||||
return Some(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environmental variables
|
|
||||||
if let Ok(p) = std::env::var("CARGO_INSTALL_ROOT") {
|
|
||||||
return Some(format!("{}/bin", p))
|
|
||||||
}
|
|
||||||
if let Ok(p) = std::env::var("CARGO_HOME") {
|
|
||||||
return Some(format!("{}/bin", p))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard $HOME/.cargo/bin
|
|
||||||
if let Some(mut d) = dirs::home_dir() {
|
|
||||||
d.push(".cargo/bin");
|
|
||||||
let p = d.as_path();
|
|
||||||
|
|
||||||
if p.exists() {
|
|
||||||
return Some(p.to_str().unwrap().to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local executable dir if no cargo is found
|
|
||||||
if let Some(d) = dirs::executable_dir() {
|
|
||||||
return Some(d.to_str().unwrap().to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue