From 520ed21a021f0623dcba4f77ae791668193de441 Mon Sep 17 00:00:00 2001 From: Tao <79176764+yeetfield@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:06:35 -0600 Subject: [PATCH] Add Rust implementation (#32) Co-authored-by: Tao Lu --- rozy/.gitignore | 2 + rozy/.idea/.gitignore | 8 + rozy/.idea/modules.xml | 8 + rozy/.idea/rozy.iml | 11 + rozy/.idea/vcs.xml | 6 + rozy/Cargo.lock | 1838 ++++++++++++++++++++++ rozy/Cargo.toml | 27 + rozy/README.md | 17 + rozy/install.sh | 4 + rozy/src/app.rs | 327 ++++ rozy/src/config.rs | 117 ++ rozy/src/files.rs | 74 + rozy/src/installers/conda.rs | 101 ++ rozy/src/installers/file.rs | 50 + rozy/src/installers/installer.rs | 6 + rozy/src/installers/mod.rs | 8 + rozy/src/installers/pip.rs | 60 + rozy/src/installers/shell.rs | 89 ++ rozy/src/installers/single_binary_zip.rs | 68 + rozy/src/installers/tarball.rs | 88 ++ rozy/src/installers/zip.rs | 48 + rozy/src/main.rs | 424 +++++ rozy/src/utils.rs | 11 + rozy/test_resource/unittest.ozy.yaml | 65 + 24 files changed, 3457 insertions(+) create mode 100644 rozy/.gitignore create mode 100644 rozy/.idea/.gitignore create mode 100644 rozy/.idea/modules.xml create mode 100644 rozy/.idea/rozy.iml create mode 100644 rozy/.idea/vcs.xml create mode 100644 rozy/Cargo.lock create mode 100644 rozy/Cargo.toml create mode 100644 rozy/README.md create mode 100755 rozy/install.sh create mode 100644 rozy/src/app.rs create mode 100644 rozy/src/config.rs create mode 100644 rozy/src/files.rs create mode 100644 rozy/src/installers/conda.rs create mode 100644 rozy/src/installers/file.rs create mode 100644 rozy/src/installers/installer.rs create mode 100644 rozy/src/installers/mod.rs create mode 100644 rozy/src/installers/pip.rs create mode 100644 rozy/src/installers/shell.rs create mode 100644 rozy/src/installers/single_binary_zip.rs create mode 100644 rozy/src/installers/tarball.rs create mode 100644 rozy/src/installers/zip.rs create mode 100644 rozy/src/main.rs create mode 100644 rozy/src/utils.rs create mode 100644 rozy/test_resource/unittest.ozy.yaml diff --git a/rozy/.gitignore b/rozy/.gitignore new file mode 100644 index 0000000..54f9ea1 --- /dev/null +++ b/rozy/.gitignore @@ -0,0 +1,2 @@ +/target +/test_resource/integration/.* diff --git a/rozy/.idea/.gitignore b/rozy/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/rozy/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/rozy/.idea/modules.xml b/rozy/.idea/modules.xml new file mode 100644 index 0000000..4e398b4 --- /dev/null +++ b/rozy/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/rozy/.idea/rozy.iml b/rozy/.idea/rozy.iml new file mode 100644 index 0000000..c254557 --- /dev/null +++ b/rozy/.idea/rozy.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/rozy/.idea/vcs.xml b/rozy/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/rozy/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/rozy/Cargo.lock b/rozy/Cargo.lock new file mode 100644 index 0000000..11a1e76 --- /dev/null +++ b/rozy/Cargo.lock @@ -0,0 +1,1838 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +dependencies = [ + "backtrace", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.5.4", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64ct" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "clap" +version = "4.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "exec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886b70328cba8871bfc025858e1de4be16b1d5088f2ba50b57816f4210672615" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "file-lock" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0815fc2a1924e651b71ae6d13df07b356a671a09ecaf857dbd344a2ba937a496" +dependencies = [ + "cc", + "libc", + "mktemp", + "nix", +] + +[[package]] +name = "filetime" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.42.0", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide 0.6.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "ipnet" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" + +[[package]] +name = "is-terminal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "linux-raw-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", +] + +[[package]] +name = "mktemp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975de676448231fcde04b9149d2543077e166b78fc29eae5aa219e7928410da2" +dependencies = [ + "uuid 0.8.2", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi 0.1.19", + "libc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "111.24.0+1.1.1s" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "ozy" +version = "0.1.3" +dependencies = [ + "anyhow", + "bzip2", + "clap", + "exec", + "file-lock", + "flate2", + "openssl", + "regex", + "reqwest", + "semver", + "serde", + "serde_yaml", + "tar", + "tempfile", + "uuid 1.2.2", + "which", + "xz2", + "zip 0.6.3", + "zip-extract", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustix" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + +[[package]] +name = "serde" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "windows-sys 0.42.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "uuid" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "bzip2", + "crc32fast", + "flate2", + "thiserror", + "time 0.1.45", +] + +[[package]] +name = "zip" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time 0.3.17", + "zstd", +] + +[[package]] +name = "zip-extract" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c5cc0309f6e81ab96c2b43d5e935025f8732c886690be8f78f68e06bad1d274" +dependencies = [ + "log", + "thiserror", + "zip 0.5.13", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.4+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" +dependencies = [ + "cc", + "libc", +] diff --git a/rozy/Cargo.toml b/rozy/Cargo.toml new file mode 100644 index 0000000..3da2743 --- /dev/null +++ b/rozy/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "ozy" +version = "0.1.3" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { version = "1.0", features = ["backtrace"] } +serde = "1.0" +serde_yaml = "0.9" +reqwest = { version = "0.11", features = ["blocking"] } +tempfile = "3" +zip-extract = "0.1.1" +file-lock = "2.1.6" +uuid = { version = "1.2.2", features = ["v4"] } +clap = { version = "4.0.29", features = ["derive"] } +flate2 = "1.0.25" +tar = "0.4.38" +regex = "1.7.0" +bzip2 = "0.4.3" +xz2 = "0.1" +which = "4.3.0" +zip = "0.6.3" +openssl = { version = "0.10.45", features = ["vendored"] } +exec = "0.3.1" +semver = "1.0.16" diff --git a/rozy/README.md b/rozy/README.md new file mode 100644 index 0000000..19e4d67 --- /dev/null +++ b/rozy/README.md @@ -0,0 +1,17 @@ +This is a Rust implementation of Ozy. + +## Instructions +1. Install [`rustup`](https://rustup.rs/) +2. Run `install.sh` +4. Prepend `$HOME/.ozy/bin` to your path to have access to managed apps + +Performance is really only a consideration on the common case of running a ozy-managed binary. This is why we prefer not to introduce asynchrony or even template caching in the `install-all` path. + +## Currently implemented commands +* `ozy clean` deletes managed directories +* `ozy init` sets up app symlinks from a base config from a provided URL +* `ozy update` accepts an optional URL, but will use the init-configured one without one +* `ozy install [app name]` installs a single app +* `ozy install-all` installs them all +* `ozy run [app name]` will run that app. Typically, you'll just run `[app name]` in the shell directly +* `ozy makefile-config` output `Makefile` compatible configuration for a list of apps diff --git a/rozy/install.sh b/rozy/install.sh new file mode 100755 index 0000000..2649fd1 --- /dev/null +++ b/rozy/install.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cargo build --release --quiet +cp target/release/ozy ~/.ozy/bin diff --git a/rozy/src/app.rs b/rozy/src/app.rs new file mode 100644 index 0000000..86ef2b6 --- /dev/null +++ b/rozy/src/app.rs @@ -0,0 +1,327 @@ +use anyhow::{anyhow, Context, Error, Result}; +use file_lock::{FileLock, FileOptions}; + +use crate::config::{apply_overrides, load_config, resolve}; +use crate::files::{delete_if_exists, get_ozy_cache_dir}; +use crate::installers::conda::Conda; +use crate::installers::file::File; +use crate::installers::installer::Installer; +use crate::installers::pip::Pip; +use crate::installers::shell::Shell; +use crate::installers::single_binary_zip::SingleBinaryZip; +use crate::installers::tarball::Tarball; +use crate::installers::zip::Zip; + +pub enum AppType { + SingleBinaryZip, + Tarball, + Shell, + File, + Conda, + Pip, + Zip, +} + +pub struct App { + pub name: String, + pub version: String, + installer: Box, + relocatable: bool, + executable_path: String, +} + +pub fn find_app(app: &String, version: &Option) -> Result { + if version.is_some() { + return Err(anyhow!("find_app w/ version not implemented")); + } + + let config = load_config(None).context("While loading ozy config")?; + App::new(app, &config) + .with_context(|| format!("While attempting to find the app {} to run", app)) +} + +impl App { + pub fn new(name: &String, config: &serde_yaml::Mapping) -> Result { + let app_configs = config + .get("apps") + .ok_or_else(|| anyhow!("Expected an apps section in the YAML"))? + .as_mapping() + .unwrap(); + + let mut app_config = app_configs + .get(name) + .ok_or_else(|| anyhow!("Could not find app {} in config", name))? + .as_mapping() + .unwrap() + .clone(); + + if let Some(serde_yaml::Value::String(template)) = app_config.get("template") { + let mut new_app_config = config["templates"][template].clone(); + apply_overrides(&app_config, new_app_config.as_mapping_mut().unwrap()); + app_config = new_app_config.as_mapping().unwrap().clone(); + } + + resolve(&mut app_config); + let version = match app_config.get("version") { + Some(serde_yaml::Value::String(version)) => version.clone(), + _ => { + return Err(anyhow!("Expected a string version in config for {}", name)); + } + }; + + let relocatable = match app_config.get("relocatable") { + Some(serde_yaml::Value::Bool(value)) => value.to_owned(), + _ => true, + }; + + let app_type = match app_config.get("type") { + Some(serde_yaml::Value::String(app_type)) => match &app_type[..] { + "single_binary_zip" => AppType::SingleBinaryZip, + "tarball" => AppType::Tarball, + "shell_install" => AppType::Shell, + "single_file" => AppType::File, + "pip" => AppType::Pip, + "conda" => AppType::Conda, + "zip" => AppType::Zip, + _ => { + return Err(anyhow!("App type {} not yet supported", &app_type[..])); + } + }, + _ => { + return Err(anyhow!( + "Expected a type field for app {} that contains a string in the YAML", + name + )); + } + }; + + let installer: Box = match app_type { + AppType::SingleBinaryZip => { + Box::new(SingleBinaryZip::new(name, &version, &app_config)?) + } + AppType::Zip => Box::new(Zip::new(name, &version, &app_config)?), + AppType::Tarball => Box::new(Tarball::new(name, &version, &app_config)?), + AppType::Shell => Box::new(Shell::new(name, &version, &app_config)?), + AppType::File => Box::new(File::new(name, &version, &app_config)?), + AppType::Conda => Box::new(Conda::new(name, &version, &app_config)?), + AppType::Pip => Box::new(Pip::new(name, &version, &app_config)?), + }; + + let executable_path = match app_config.get("executable_path") { + Some(serde_yaml::Value::String(value)) => value, + _ => name, + }; + + Ok(App { + name: name.clone(), + version, + installer, + relocatable, + executable_path: executable_path.clone(), + }) + } + + pub fn ensure_installed(&self) -> Result<()> { + let result = self.ensure_installed_internal(); + if result.is_err() { + delete_if_exists(self.get_install_path()?.parent().unwrap())?; + } + + result + } + + pub fn delete(&self) -> Result<()> { + delete_if_exists(self.get_install_path()?.as_path()) + } + + fn ensure_installed_internal(&self) -> Result<()> { + if self.is_installed().context("Checking if it's installed")? { + return Ok(()); + } + + let install_dir = self.get_install_path()?; + std::fs::create_dir_all(install_dir.parent().unwrap()) + .context("While creating parent directory of install directory")?; + + let lock_for_writing = FileOptions::new().create(true).write(true).read(true); + let lockfile_path = install_dir.parent().unwrap().join(format!( + "{}.lock", + install_dir.file_name().unwrap().to_string_lossy() + )); + let _lock = + FileLock::lock(lockfile_path, true, lock_for_writing).context("Locking file")?; + if self.is_installed().context("Checking if it's installed")? { + return Ok(()); + } + + // In Python we generate a UUID here; is that necessary? + let uniq_install_dir = self + .get_internal_install_path() + .context("Checking its install path")?; + delete_if_exists(uniq_install_dir.as_path())?; + + self.installer + .install(uniq_install_dir.as_path()) + .context("While running the Installer") + .and_then(|_| { + match self.relocatable { + true => std::fs::rename(&uniq_install_dir, &install_dir).context("Renaming")?, + false => std::os::unix::fs::symlink(&uniq_install_dir, &install_dir) + .context("Symlinking")?, + }; + Ok(()) + }) + .with_context(|| format!("While installing {} v.{}", &self.name, &self.version))?; + + Ok(()) + } + + pub fn get_absolute_executable_path(&self) -> Result { + Ok(self.get_install_path()?.join(self.executable_path.clone())) + } + + fn is_installed(&self) -> Result { + match std::fs::metadata(self.get_install_path()?) { + Ok(metadata) => Ok(metadata.is_dir()), + Err(_) => Ok(false), + } + } + + fn get_install_path(&self) -> Result { + Ok(get_ozy_cache_dir()?.join(&self.name).join(&self.version)) + } + + fn get_internal_install_path(&self) -> Result { + Ok(get_ozy_cache_dir()? + .join("internal_install") + .join(&self.name) + .join(&self.version)) + } +} + +impl std::hash::Hash for App { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.version.hash(state); + self.relocatable.hash(state); + self.executable_path.hash(state); + self.installer.describe().hash(state); + } +} + +impl PartialEq for App { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.version == other.version + && self.relocatable == other.relocatable + && self.executable_path == other.executable_path + && self.installer.describe() == other.installer.describe() + } +} + +impl Eq for App {} + +#[cfg(test)] +mod tests { + use serde_yaml::Mapping; + + use crate::config::parse_ozy_config; + + use super::*; + + fn get_test_config() -> Mapping { + let test_yaml_path = std::path::Path::new("test_resource/unittest.ozy.yaml").to_path_buf(); + parse_ozy_config(&test_yaml_path).expect("Failed to load YAML") + } + + #[test] + fn general_constructor_test() { + let config = get_test_config(); + let app = App::new(&"single_binary_zip_app".to_string(), &config) + .expect("Failed to construct App"); + assert_eq!(app.name, "single_binary_zip_app"); + assert_eq!(app.version, "1.10.1"); + assert_eq!(app.relocatable, true); + assert_eq!(app.executable_path, "bin/single_binary_zip_app"); + } + + #[test] + fn reloctable_detection_test() { + let config = get_test_config(); + let app = App::new(&"explicitly_relocatable_app".to_string(), &config) + .expect("Failed to construct App"); + assert_eq!(app.relocatable, true); + + let app = App::new(&"explicitly_nonrelocatable_app".to_string(), &config) + .expect("Failed to construct App"); + assert_eq!(app.relocatable, false); + + let app = App::new(&"unspecified_relocatability_app".to_string(), &config) + .expect("Failed to construct App"); + assert_eq!(app.relocatable, true); + } + + #[test] + fn single_binary_zip_test() { + let config = get_test_config(); + let single_binary_zip_app = App::new(&"single_binary_zip_app".to_string(), &config) + .expect("Failed to construct App"); + assert_eq!( + single_binary_zip_app.installer.describe(), + "single binary zip installer for single_binary_zip_app v.1.10.1" + ); + } + + #[test] + fn tarball_test() { + let config = get_test_config(); + let tarball_app = + App::new(&"tarball_app".to_string(), &config).expect("Failed to construct App"); + assert_eq!( + tarball_app.installer.describe(), + "tarball installer for tarball_app v.0.7.0" + ); + } + + #[test] + fn shell_test() { + let config = get_test_config(); + let shell_app = + App::new(&"shell_app".to_string(), &config).expect("Failed to construct App"); + assert_eq!( + shell_app.installer.describe(), + "shell installer for shell_app v.4.11.0" + ); + } + + #[test] + fn pip_test() { + let config = get_test_config(); + let pip_app = App::new(&"pip_app".to_string(), &config).expect("Failed to construct App"); + assert_eq!( + pip_app.installer.describe(), + "pip installer for pip_package=1.20.4" + ); + } + + #[test] + fn conda_test() { + let config = get_test_config(); + let conda_app = + App::new(&"conda_app".to_string(), &config).expect("Failed to construct App"); + assert_eq!( + conda_app.installer.describe(), + "conda installer for conda-package=45" + ); + } + + #[test] + fn file_test() { + let config = get_test_config(); + let file_app = App::new(&"file_app".to_string(), &config).expect("Failed to construct App"); + assert_eq!( + file_app.installer.describe(), + "file installer for file_app v.6.6.0" + ); + } +} diff --git a/rozy/src/config.rs b/rozy/src/config.rs new file mode 100644 index 0000000..f5f2e9b --- /dev/null +++ b/rozy/src/config.rs @@ -0,0 +1,117 @@ +use crate::files::get_ozy_dir; +use anyhow::{Context, Result}; +use std::collections::HashMap; + +use serde_yaml::Mapping as Config; + +pub fn load_config(base_config_filename_override: Option<&str>) -> Result { + let mut curr_config = parse_ozy_config( + &get_ozy_dir()?.join(base_config_filename_override.unwrap_or("ozy.yaml")), + )?; + let curr_dir = std::env::current_dir()?; + + let ancestors: Vec<&std::path::Path> = curr_dir.ancestors().collect(); + for dir in ancestors.iter().rev() { + let ozy_config_path = dir.join(".ozy.yaml"); + if !ozy_config_path.exists() { + continue; + } + + let new_config = parse_ozy_config(&ozy_config_path)?; + apply_overrides(&new_config, &mut curr_config); + } + + Ok(curr_config) +} + +pub fn parse_ozy_config(path: &std::path::PathBuf) -> Result { + let raw_config = std::fs::read_to_string(path).with_context(|| "Reading config YAML")?; + serde_yaml::from_str(&raw_config).with_context(|| "Deserializing config YAML") +} + +pub fn save_ozy_user_conf(conf: &serde_yaml::Mapping) -> Result<()> { + let path = get_ozy_dir()?.join("ozy.user.yaml"); + let file = std::fs::File::create(path)?; + serde_yaml::to_writer(file, conf)?; + Ok(()) +} + +pub fn get_ozy_user_conf() -> Result { + load_config(Some("ozy.user.yaml")) +} + +pub fn apply_overrides(source: &serde_yaml::Mapping, dest: &mut serde_yaml::Mapping) { + for (key, value) in source.into_iter() { + match (value, dest.get_mut(key)) { + ( + serde_yaml::Value::Mapping(src_child_mapping), + Some(serde_yaml::Value::Mapping(dst_child_mapping)), + ) => { + apply_overrides(src_child_mapping, dst_child_mapping); + } + _ => { + dest.insert(key.clone(), value.clone()); + } + }; + } +} + +fn get_candidate_substitutions(mapping: &serde_yaml::Mapping) -> HashMap { + let mut variables: HashMap = HashMap::new(); + + variables.insert( + "ozy_os".to_string(), + match std::env::consts::OS { + "macos" => "darwin".to_string(), + _ => std::env::consts::OS.to_string(), + }, + ); + variables.insert( + "ozy_arch".to_string(), + match std::env::consts::ARCH { + "x86_64" => "amd64".to_string(), + _ => std::env::consts::ARCH.to_string(), + }, + ); + variables.insert( + "ozy_machine".to_string(), + std::env::consts::ARCH.to_string(), + ); + + for (key, value) in mapping.into_iter() { + if let (Some(k), Some(v)) = (key.as_str(), value.as_str()) { + variables.insert(k.to_string(), v.to_string()); + } + } + + variables +} + +pub fn resolve(mapping: &mut serde_yaml::Mapping) { + let variables = get_candidate_substitutions(mapping); + + let re = regex::Regex::new(r"\{.*?\}").unwrap(); + for (_, value) in mapping.into_iter() { + let maybe_s = value.as_str(); + if maybe_s.is_none() { + continue; + } + + let mut s = maybe_s.unwrap().to_string(); + + let potential_variables: Vec = re + .find_iter(&s) + .map(|m| s[m.start() + 1..m.end() - 1].to_string()) + .collect(); + for potential_variable in potential_variables.iter() { + let replacement: Option<&String> = variables.get(potential_variable); + if replacement.is_none() { + continue; + } + + s = s.replace(&format!("{{{}}}", potential_variable), replacement.unwrap()); + } + + *value = serde_yaml::Value::String(s); + } +} diff --git a/rozy/src/files.rs b/rozy/src/files.rs new file mode 100644 index 0000000..378f30f --- /dev/null +++ b/rozy/src/files.rs @@ -0,0 +1,74 @@ +use std::env; + +use anyhow::{Context, Error, Result}; + +fn get_home_dir() -> Result { + let home_dir = std::env::var("HOME").context("While checking $HOME")?; + Ok(std::path::PathBuf::from(home_dir)) +} + +pub fn delete_if_exists(path: &std::path::Path) -> Result<()> { + if path.exists() { + if path.is_dir() { + std::fs::remove_dir_all(path)? + } else { + std::fs::remove_file(path)? + } + } + + Ok(()) +} + +pub fn get_ozy_dir() -> Result { + Ok(get_home_dir()?.join(".ozy")) +} + +pub fn get_ozy_bin_dir() -> Result { + Ok(get_ozy_dir()?.join("bin")) +} + +pub fn get_ozy_cache_dir() -> Result { + Ok(get_home_dir()?.join(".cache").join("ozy")) +} + +pub fn ensure_ozy_dirs() -> Result<(), Error> { + std::fs::create_dir_all(get_ozy_dir()?.as_path()).context("While checking Ozy dir")?; + std::fs::create_dir_all(get_ozy_bin_dir()?.as_path()).context("While checking Ozy bin dir")?; + std::fs::create_dir_all(get_ozy_cache_dir()?.as_path()) + .context("While checking Ozy cache dir")?; + Ok(()) +} + +pub fn delete_ozy_dirs() -> Result<(), Error> { + delete_if_exists(get_ozy_dir()?.as_path()).context("While deleting Ozy dir")?; + delete_if_exists(get_ozy_bin_dir()?.as_path()).context("While deleting Ozy bin dir")?; + delete_if_exists(get_ozy_cache_dir()?.as_path()).context("While deleting Ozy cache dir")?; + Ok(()) +} + +pub fn check_path(ozy_bin_dir: &std::path::Path) -> Result { + let bin_path_canonical = ozy_bin_dir.canonicalize()?; + if let Some(paths) = env::var_os("PATH") { + for path in env::split_paths(&paths) { + if let Ok(path) = path.canonicalize() { + if bin_path_canonical == path { + return Ok(true); + } + } + } + } + Ok(false) +} + +pub fn softlink(from_command: &str, to_command: &str) -> Result { + let from_command_path = get_ozy_bin_dir()?.join(from_command); + let to_command_path = get_ozy_bin_dir()?.join(to_command); + let was_there = from_command_path.as_path().exists(); + if was_there { + std::fs::remove_dir_all(&from_command_path)?; + // TODO: unlink from_command_path? Also is this equivalent to what's happening in Python where we unlink path_to_app? + } + + std::os::unix::fs::symlink(to_command_path, from_command_path)?; + Ok(was_there) +} diff --git a/rozy/src/installers/conda.rs b/rozy/src/installers/conda.rs new file mode 100644 index 0000000..ae7a186 --- /dev/null +++ b/rozy/src/installers/conda.rs @@ -0,0 +1,101 @@ +use super::installer::Installer; + +use anyhow::{anyhow, Error, Result}; + +use tempfile::tempdir; + +pub struct Conda { + package: String, + version: String, + channels: Vec, + conda_bin: String, + pyinstaller: bool, +} + +pub fn conda_install( + conda_bin: &String, + channels: &[String], + to_dir: &std::path::Path, + to_install: &[String], +) -> Result<()> { + std::fs::create_dir_all(to_dir)?; + + let conda_cache_dir = tempdir()?; + let mut command = std::process::Command::new(conda_bin); + command.env("CONDA_PKGS_DIRS", conda_cache_dir.path()); + + command.arg("create"); + command.arg("-y"); + + for arg in channels.iter() { + command.arg("-c"); + command.arg(arg); + } + + command.arg("-p"); + command.arg(to_dir); + + for arg in to_install.iter() { + command.arg(arg); + } + + let output = command.output().unwrap(); + if !output.status.success() { + return Err(anyhow!("Conda installation exited with {:#?}", output)); + } + + Ok(()) +} + +impl Conda { + pub fn new(_: &str, version: &str, app_config: &serde_yaml::Mapping) -> Result { + let package = app_config["package"].as_str().unwrap().to_string(); + let channels = match app_config.get("channels") { + // TODO: Probably a way to do this without unwrapping + Some(serde_yaml::Value::Sequence(seq)) => seq + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect(), + _ => vec![], + }; + let conda_bin = match app_config.get("conda_bin") { + Some(serde_yaml::Value::String(value)) => value.to_owned(), + _ => String::from("conda"), + }; + + let pyinstaller = match app_config.get("pyinstaller") { + Some(serde_yaml::Value::Bool(value)) => value.to_owned(), + _ => false, + }; + + Ok(Conda { + package, + version: version.to_string(), + channels, + conda_bin, + pyinstaller, + }) + } +} + +impl Installer for Conda { + fn install(&self, to_dir: &std::path::Path) -> Result<()> { + eprintln!("Running {}", self.describe()); + + if self.pyinstaller { + return Err(anyhow!("Pyinstaller mode not yet implemented")); + } + let versioned_package = format!("{}={}", self.package, self.version); + conda_install( + &self.conda_bin, + &self.channels, + to_dir, + &[versioned_package], + )?; + Ok(()) + } + + fn describe(&self) -> String { + format!("conda installer for {}={}", self.package, self.version) + } +} diff --git a/rozy/src/installers/file.rs b/rozy/src/installers/file.rs new file mode 100644 index 0000000..19674b8 --- /dev/null +++ b/rozy/src/installers/file.rs @@ -0,0 +1,50 @@ +use crate::utils::download_to; + +use super::installer::Installer; + +use anyhow::{anyhow, Error, Result}; +use std::os::unix::fs::PermissionsExt; + +pub struct File { + name: String, + version: String, + url: String, +} + +impl File { + pub fn new( + name: &String, + version: &str, + app_config: &serde_yaml::Mapping, + ) -> Result { + let url = match app_config.get("url") { + Some(serde_yaml::Value::String(url)) => url.clone(), + _ => { + return Err(anyhow!("Expected a string url in config for {}", name)); + } + }; + + Ok(File { + name: name.clone(), + version: version.to_string(), + url, + }) + } +} + +impl Installer for File { + fn install(&self, to_dir: &std::path::Path) -> Result<()> { + eprintln!("Running {}", self.describe()); + std::fs::create_dir_all(to_dir)?; + + let file = to_dir.join(&self.name); + download_to(&file, &self.url)?; + std::fs::set_permissions(file, std::fs::Permissions::from_mode(0o774)).unwrap(); + + Ok(()) + } + + fn describe(&self) -> String { + format!("file installer for {} v.{}", self.name, self.version) + } +} diff --git a/rozy/src/installers/installer.rs b/rozy/src/installers/installer.rs new file mode 100644 index 0000000..425b787 --- /dev/null +++ b/rozy/src/installers/installer.rs @@ -0,0 +1,6 @@ +use anyhow::Result; + +pub trait Installer { + fn install(&self, to_dir: &std::path::Path) -> Result<()>; + fn describe(&self) -> String; +} diff --git a/rozy/src/installers/mod.rs b/rozy/src/installers/mod.rs new file mode 100644 index 0000000..fbc636b --- /dev/null +++ b/rozy/src/installers/mod.rs @@ -0,0 +1,8 @@ +pub mod conda; +pub mod file; +pub mod installer; +pub mod pip; +pub mod shell; +pub mod single_binary_zip; +pub mod tarball; +pub mod zip; diff --git a/rozy/src/installers/pip.rs b/rozy/src/installers/pip.rs new file mode 100644 index 0000000..3703d9f --- /dev/null +++ b/rozy/src/installers/pip.rs @@ -0,0 +1,60 @@ +use crate::installers::conda::conda_install; + +use super::installer::Installer; + +use anyhow::{anyhow, Error, Result}; + +pub struct Pip { + package: String, + version: String, + channels: Vec, +} + +impl Pip { + pub fn new(_: &String, version: &str, app_config: &serde_yaml::Mapping) -> Result { + let package = app_config["package"].as_str().unwrap().to_string(); + let channels = match app_config.get("channels") { + // TODO: Probably a way to do this without unwrapping + Some(serde_yaml::Value::Sequence(seq)) => seq + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect(), + _ => vec![], + }; + + Ok(Pip { + package, + version: version.to_string(), + channels, + }) + } +} + +impl Installer for Pip { + fn install(&self, to_dir: &std::path::Path) -> Result<()> { + eprintln!("Running {}", self.describe()); + std::fs::create_dir_all(to_dir)?; + + conda_install( + &String::from("conda"), + &self.channels, + to_dir, + &[String::from("pip")], + )?; + let pip_path = to_dir.join("bin").join("pip"); + let mut command = std::process::Command::new(pip_path); + command.arg("install"); + command.arg(format!("{}=={}", self.package, self.version)); + + let output = command.output().unwrap(); + if !output.status.success() { + return Err(anyhow!("Pip installation exited with {:?}", output)); + } + + Ok(()) + } + + fn describe(&self) -> String { + format!("pip installer for {}={}", self.package, self.version) + } +} diff --git a/rozy/src/installers/shell.rs b/rozy/src/installers/shell.rs new file mode 100644 index 0000000..11c2674 --- /dev/null +++ b/rozy/src/installers/shell.rs @@ -0,0 +1,89 @@ +use super::installer::Installer; +use crate::utils::download_to; +use tempfile::tempdir; + +use anyhow::{anyhow, Context, Error, Result}; + +pub struct Shell { + name: String, + version: String, + url: String, + extra_path_during_install: Option, + shell_args: Vec, +} + +impl Shell { + pub fn new( + name: &String, + version: &str, + app_config: &serde_yaml::Mapping, + ) -> Result { + let url = match app_config.get("url") { + Some(serde_yaml::Value::String(url)) => url.clone(), + _ => { + return Err(anyhow!("Expected a string url in config for {}", name)); + } + }; + + let extra_path_during_install = match app_config.get("extra_path_during_install") { + Some(serde_yaml::Value::String(value)) => Some(value.clone()), + _ => None, + }; + + let shell_args = match app_config.get("shell_args") { + // TODO: Probably a way to do this without unwrapping + Some(serde_yaml::Value::Sequence(seq)) => seq + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect(), + _ => vec![], + }; + + Ok(Shell { + name: name.clone(), + version: version.to_string(), + url, + extra_path_during_install, + shell_args, + }) + } +} + +impl Installer for Shell { + fn install(&self, to_dir: &std::path::Path) -> Result<()> { + eprintln!("Running {}", self.describe()); + std::fs::create_dir_all(to_dir)?; + + let dir = tempdir()?; + let file = dir.path().join("installer.sh"); + download_to(&file, &self.url)?; + + let mut command = std::process::Command::new("/bin/bash"); + + if let Some(extra_path) = &self.extra_path_during_install { + let path = format!( + "{}:{}", + extra_path, + std::env::var("PATH").context("While checking $PATH")? + ); + command.env("PATH", path); + } + command.env("INSTALL_DIR", to_dir.as_os_str().to_str().unwrap()); + + command.arg(file.as_os_str().to_str().unwrap()); + for arg in &self.shell_args { + command.arg(arg); + } + + let output = command.output().unwrap(); + if !output.status.success() { + return Err(anyhow!("Shell installer exited with {:?}", output)); + } + + Ok(()) + } + + fn describe(&self) -> String { + format!("shell installer for {} v.{}", self.name, self.version) + } +} diff --git a/rozy/src/installers/single_binary_zip.rs b/rozy/src/installers/single_binary_zip.rs new file mode 100644 index 0000000..c2dcff5 --- /dev/null +++ b/rozy/src/installers/single_binary_zip.rs @@ -0,0 +1,68 @@ +use std::os::unix::fs::PermissionsExt; +use std::{fs, io}; + +use anyhow::{anyhow, Context, Error, Result}; + +use zip; + +use super::installer::Installer; + +pub struct SingleBinaryZip { + name: String, + version: String, + url: String, +} + +impl SingleBinaryZip { + pub fn new( + name: &String, + version: &str, + app_config: &serde_yaml::Mapping, + ) -> Result { + let url = match app_config.get("url") { + Some(serde_yaml::Value::String(url)) => url.clone(), + _ => { + return Err(anyhow!("Expected a string url in config for {}", name)); + } + }; + + Ok(SingleBinaryZip { + name: name.clone(), + version: version.to_string(), + url, + }) + } +} + +impl Installer for SingleBinaryZip { + fn install(&self, to_dir: &std::path::Path) -> Result<()> { + eprintln!("Running {}", self.describe()); + fs::create_dir_all(to_dir)?; + + let app_path = to_dir.join(&self.name); + let response = reqwest::blocking::get(&self.url)?; + let content = response.bytes()?.to_vec(); + let mut archive = zip::ZipArchive::new(std::io::Cursor::new(content)) + .context("While unzipping downloaded file")?; + if archive.len() != 1 { + return Err(anyhow!("More than one file in the zipfile at {}", self.url)); + } + + let mut file = archive.by_index(0)?; + let mut outfile = fs::File::create(&app_path)?; + io::copy(&mut file, &mut outfile).context("While unzipping downloaded file")?; + + let mut perms = fs::metadata(&app_path)?.permissions(); + perms.set_mode(0o755); + fs::set_permissions(&app_path, perms)?; + + Ok(()) + } + + fn describe(&self) -> String { + format!( + "single binary zip installer for {} v.{}", + self.name, self.version + ) + } +} diff --git a/rozy/src/installers/tarball.rs b/rozy/src/installers/tarball.rs new file mode 100644 index 0000000..e5893ef --- /dev/null +++ b/rozy/src/installers/tarball.rs @@ -0,0 +1,88 @@ +use std::io::Read; + +use crate::utils::download_to; + +use super::installer::Installer; + +use anyhow::{anyhow, Error, Result}; +use bzip2::read::BzDecoder; +use flate2::read::GzDecoder; +use tar::Archive; +use tempfile::NamedTempFile; +use xz2::read::XzDecoder; + +pub struct Tarball { + name: String, + version: String, + url: String, +} + +enum CompressedFileType { + BZIP2, + GZIP2, + XZ, +} + +fn get_compressed_file_type(path: &std::path::Path) -> Result { + let mut command = std::process::Command::new("file"); + command.arg(path); + let output = command.output().unwrap(); + + let stdout = std::str::from_utf8(&output.stdout)?; + if stdout.contains("bzip2") { + return Ok(CompressedFileType::BZIP2); + } else if stdout.contains("gzip") { + return Ok(CompressedFileType::GZIP2); + } else if stdout.contains("XZ") { + return Ok(CompressedFileType::XZ); + } + + Err(anyhow!("Unknown data type")) +} + +impl Tarball { + pub fn new( + name: &String, + version: &str, + app_config: &serde_yaml::Mapping, + ) -> Result { + let url = match app_config.get("url") { + Some(serde_yaml::Value::String(url)) => url.clone(), + _ => { + return Err(anyhow!("Expected a string url in config for {}", name)); + } + }; + + Ok(Tarball { + name: name.clone(), + version: version.to_string(), + url, + }) + } +} + +impl Installer for Tarball { + fn install(&self, to_dir: &std::path::Path) -> Result<()> { + eprintln!("Running {}", self.describe()); + std::fs::create_dir_all(to_dir)?; + + let file = NamedTempFile::new()?; + download_to(&file.path().to_path_buf(), &self.url)?; + + let downloaded_file = std::fs::File::open(file.path())?; + let reader: Box = match get_compressed_file_type(file.path())? { + CompressedFileType::GZIP2 => Box::new(GzDecoder::new(downloaded_file)), + CompressedFileType::BZIP2 => Box::new(BzDecoder::new(downloaded_file)), + CompressedFileType::XZ => Box::new(XzDecoder::new(downloaded_file)), + }; + + let mut archive = Archive::new(reader); + archive.unpack(to_dir)?; + + Ok(()) + } + + fn describe(&self) -> String { + format!("tarball installer for {} v.{}", self.name, self.version) + } +} diff --git a/rozy/src/installers/zip.rs b/rozy/src/installers/zip.rs new file mode 100644 index 0000000..2b96815 --- /dev/null +++ b/rozy/src/installers/zip.rs @@ -0,0 +1,48 @@ +use super::installer::Installer; + +use anyhow::{anyhow, Context, Error, Result}; + +pub struct Zip { + name: String, + version: String, + url: String, +} + +impl Zip { + pub fn new( + name: &String, + version: &str, + app_config: &serde_yaml::Mapping, + ) -> Result { + let url = match app_config.get("url") { + Some(serde_yaml::Value::String(url)) => url.clone(), + _ => { + return Err(anyhow!("Expected a string url in config for {}", name)); + } + }; + + Ok(Zip { + name: name.clone(), + version: version.to_string(), + url, + }) + } +} + +impl Installer for Zip { + fn install(&self, to_dir: &std::path::Path) -> Result<()> { + eprintln!("Running {}", self.describe()); + std::fs::create_dir_all(to_dir)?; + + let response = reqwest::blocking::get(&self.url)?; + let content = response.bytes()?.to_vec(); + zip_extract::extract(std::io::Cursor::new(content), to_dir, false) + .context("While unzipping downloaded file")?; + + Ok(()) + } + + fn describe(&self) -> String { + format!("zip installer for {} v.{}", self.name, self.version) + } +} diff --git a/rozy/src/main.rs b/rozy/src/main.rs new file mode 100644 index 0000000..1efb1c5 --- /dev/null +++ b/rozy/src/main.rs @@ -0,0 +1,424 @@ +use std::path; + +use anyhow::{anyhow, Context, Error, Result}; +use clap::{Parser, Subcommand}; +use semver::Version; +use std::os::unix::fs::PermissionsExt; +use which::which; + +use crate::files::{check_path, get_ozy_bin_dir}; + +mod app; +mod config; +mod files; +mod installers; +mod utils; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn symlink_binaries(path_to_ozy: &std::path::PathBuf, config: &serde_yaml::Mapping) -> Result<()> { + // If this binary isn't installed in the correct location, move it there + let expected_path_to_ozy = files::get_ozy_bin_dir()?.join("ozy"); + if path_to_ozy != &expected_path_to_ozy { + files::delete_if_exists(&expected_path_to_ozy)?; + std::fs::rename(path_to_ozy, expected_path_to_ozy)?; + } + + let app_configs = match config.get("apps") { + Some(serde_yaml::Value::Mapping(app_configs)) => app_configs, + _ => { + return Err(anyhow!("Expected an mapping-type apps section in the YAML",)); + } + }; + + for (name, _) in app_configs { + files::softlink(name.as_str().unwrap(), "ozy")?; + } + + Ok(()) +} + +fn init(path_to_ozy: &std::path::PathBuf, url: &str) -> Result<()> { + files::ensure_ozy_dirs()?; + + let mut user_conf = serde_yaml::Mapping::new(); + user_conf.insert( + serde_yaml::Value::String("url".to_string()), + serde_yaml::Value::String(url.to_string()), + ); + config::save_ozy_user_conf(&user_conf)?; + + let base_ozy_conf = files::get_ozy_dir()?.join("ozy.yaml"); + utils::download_to(&base_ozy_conf, url)?; + + let config = config::load_config(None)?; + symlink_binaries(path_to_ozy, &config)?; + Ok(()) +} + +fn install(app_names: &[String]) -> Result<()> { + files::ensure_ozy_dirs()?; + let config = config::load_config(None)?; + + for app_name in app_names.iter() { + let app = app::App::new(app_name, &config)?; + app.ensure_installed()?; + } + + Ok(()) +} + +fn install_all() -> Result<()> { + files::ensure_ozy_dirs()?; + let config = config::load_config(None)?; + let app_configs = match config.get("apps") { + Some(serde_yaml::Value::Mapping(app_configs)) => app_configs, + _ => { + return Err(anyhow!("Expected an mapping-type apps section in the YAML",)); + } + }; + + for (name, _) in app_configs { + let name = match name { + serde_yaml::Value::String(name) => name, + _ => { + return Err(anyhow!("Expected name of app config to be a string")); + } + }; + + let app = app::App::new(name, &config); + if app.is_err() { + eprintln!( + "Skipping incompatible app config for {} due to: {}", + name, + app.err().unwrap() + ); + continue; + } + + eprintln!("Installing {}", name); + app?.ensure_installed() + .with_context(|| format!("While ensuring app {} is installed", name))?; + } + + Ok(()) +} + +fn makefile_config_internal(makefile_var: &String, app_names: &[String]) -> Result { + files::ensure_ozy_dirs()?; + let config = config::load_config(None)?; + let ozy_bin_dir = get_ozy_bin_dir()?; + if !check_path(&ozy_bin_dir)? { + return Err(anyhow!("ozy is not on the path")); + } + + for app_name in app_names.iter() { + if app::App::new(app_name, &config).is_err() { + return Err(anyhow!("Missing ozy app '{}'", app_name)); + } + let app_in_bin = ozy_bin_dir.join(path::Path::new(app_name)).canonicalize()?; + if let Ok(os_found) = which(app_name) { + if os_found.canonicalize()? != app_in_bin { + return Err(anyhow!( + "'{}' found in PATH earlier than ozy: results could be inconsistent (found at {})", + app_name, + os_found.display())); + } + } else { + return Err(anyhow!( + "Missing ozy app '{}' - not found on PATH", + app_name + )); + } + } + Ok(format!("{}:={}", &makefile_var, ozy_bin_dir.display())) +} + +fn makefile_config(makefile_var: &String, app_names: &[String]) -> Result<()> { + match makefile_config_internal(makefile_var, app_names) { + Ok(str) => { + println!("{}", str); + } + Err(err) => { + println!("$(error \"{}\")", err); // todo escape + } + } + Ok(()) +} + +fn clean() -> Result<()> { + files::delete_ozy_dirs() +} + +fn run(app_name: &String, version: &Option, args: &[String]) -> Result<()> { + let app = app::find_app(app_name, version)?; + app.ensure_installed() + .with_context(|| format!("While ensuring that app {} is installed", app_name))?; + + // TODO: Maybe restore environment variables if we have to override any + let program_path = app.get_absolute_executable_path()?; + let mut command = exec::Command::new(&program_path); + for arg in args { + command.arg(arg); + } + + // We use exec to replace this process with the child entirely. The only time exec() returns is + // if there was an error. + let err = command.exec(); + Err(anyhow!( + "Failed to execute process {} ({})", + program_path.display(), + err + )) +} + +fn get_apps(config: &serde_yaml::Mapping) -> Result> { + let app_configs = match config.get("apps") { + Some(serde_yaml::Value::Mapping(app_configs)) => app_configs, + _ => { + return Err(anyhow!("Expected an mapping-type apps section in the YAML",)); + } + }; + + let mut result = vec![]; + for (name, _) in app_configs { + let name = match name { + serde_yaml::Value::String(name) => name, + _ => { + return Err(anyhow!("Expected name of app config to be a string")); + } + }; + + let app = app::App::new(name, config); + if app.is_err() { + eprintln!( + "Skipping incompatible app config for {} due to: {}", + name, + app.err().unwrap() + ); + continue; + } + + result.push(app?); + } + + Ok(result) +} + +fn update(path_to_ozy: &std::path::PathBuf, url: &Option) -> Result<()> { + files::ensure_ozy_dirs()?; + let old_base_ozy_conf = files::get_ozy_dir()?.join("ozy.yaml"); + let new_base_ozy_conf = files::get_ozy_dir()?.join("ozy.yaml.tmp"); + + let user_config = config::get_ozy_user_conf()?; + let url = match url { + Some(v) => v, + None => user_config["url"].as_str().unwrap(), + }; + + utils::download_to(&new_base_ozy_conf, url)?; + + let old_config = config::load_config(None)?; + let new_config = config::load_config(Some("ozy.yaml.tmp"))?; + + let new_version = Version::parse(new_config["ozy_version"].as_str().unwrap())?; + if new_version > Version::parse(VERSION)? { + eprintln!( + "Ozy update to {} is mandated by your team config", + new_version + ); + + let mut config_update_slice = serde_yaml::Mapping::new(); + config_update_slice.insert( + serde_yaml::Value::String("ozy_download".to_string()), + new_config["ozy_download"].clone(), + ); + config_update_slice.insert( + serde_yaml::Value::String("version".to_string()), + new_config["ozy_version"].clone(), + ); + config::resolve(&mut config_update_slice); + let download_url = config_update_slice["ozy_download"].as_str().unwrap(); + eprintln!("Downloading from {}", download_url); + + let dest_path = files::get_ozy_bin_dir()?.join("ozy.tmp"); + files::delete_if_exists(&dest_path)?; + utils::download_to(&dest_path, download_url)?; + let mut perms = std::fs::metadata(&dest_path)?.permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&dest_path, perms)?; + + let mut command = exec::Command::new(&dest_path); + command.arg("update"); + + // We use exec to replace this process with the child entirely. The only time exec() returns is + // if there was an error. + let err = command.exec(); + return Err(anyhow!( + "Failed to execute process {} ({})", + dest_path.display(), + err + )); + } + + let new_apps = + std::collections::HashSet::::from_iter(get_apps(&new_config)?.into_iter()); + let old_apps = + std::collections::HashSet::::from_iter(get_apps(&old_config)?.into_iter()); + + let to_delete = old_apps.difference(&new_apps); + + for app in to_delete { + eprintln!("Removing obsolete app {} v.{}", app.name, app.version); + + app.delete()?; + files::delete_if_exists(&files::get_ozy_bin_dir()?.join(&app.name))?; + } + + std::fs::rename(new_base_ozy_conf, old_base_ozy_conf)?; + let config = config::load_config(None)?; + symlink_binaries(path_to_ozy, &config)?; + + let mut user_conf = serde_yaml::Mapping::new(); + user_conf.insert( + serde_yaml::Value::String("url".to_string()), + serde_yaml::Value::String(url.to_string()), + ); + config::save_ozy_user_conf(&user_conf)?; + + Ok(()) +} + +fn sync(path_to_ozy: &std::path::PathBuf) -> Result<()> { + let config = config::load_config(None)?; + symlink_binaries(path_to_ozy, &config)?; + + Ok(()) +} + +fn list() -> Result<()> { + let config = config::load_config(None)?; + let apps = get_apps(&config)?; + for app in apps.iter() { + eprintln!("{}", app.name); + } + + Ok(()) +} + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +struct Args { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + #[clap(about = "Cleans up the ozy-controlled directory")] + Clean, + + #[clap( + trailing_var_arg = true, + about = "Ensures the named applications are installed at their current prevailing versions" + )] + Install { app_names: Vec }, + + #[clap( + trailing_var_arg = true, + about = "Checks apps, and prints a single-line Makefile variable", + long_about = r#" +Use as an argument to $(eval). Errors will are output as $(error) directives +to report in make. +The given variable is defined to be the ozy binary directory, so any app will be +$(VAR)/app_name. If undefined, you know ozy isn't installed. + +Example: + +$ cat Makefile +$(eval $(shell ozy makefile-config OZY_BIN_DIR terraform)) +ifndef OZY_BIN_DIR +$(error please install ozy) +endif + +install: + $(OZY_BIN_DIR)/terraform apply +"# + )] + MakefileConfig { + makefile_var: String, + app_names: Vec, + }, + + #[clap(about = "Ensures all applications are installed at their current prevailing versions")] + InstallAll, + + #[clap(about = "Initialise and install ozy, with configuration from the given URL")] + Init { url: String }, + + #[clap(about = "List all the managed apps")] + List, + + #[clap(trailing_var_arg = true, about = "Runs the given application")] + Run { + app_name: String, + + #[arg(short, long, help = "Version of the app to run")] + app_version: Option, + + app_args: Vec, + }, + + #[clap(about = "Update base configuration from the remote URL")] + Update { + #[arg(short, long)] + url: Option, + }, + + #[clap( + about = "Synchronise any local changes", + long_about = r#" +If you're defining new applications in local user files, you can use this to ensure +the relevant symlinks are created in your ozy bin directory. +"# + )] + Sync, +} + +fn main() -> Result<(), Error> { + let invoked_as = std::env::args() + .next() + .as_ref() + .map(std::path::Path::new) + .and_then(std::path::Path::file_name) + .and_then(std::ffi::OsStr::to_str) + .map(String::from) + .unwrap(); + + if invoked_as.starts_with("ozy") { + let args = Args::parse(); + let exe_path = std::env::current_exe()?; + match &args.command { + Commands::Clean => clean(), + Commands::Init { url } => init(&exe_path, url), + Commands::Install { app_names } => install(app_names), + Commands::InstallAll => install_all(), + Commands::List => list(), + Commands::MakefileConfig { + makefile_var, + app_names, + } => makefile_config(makefile_var, app_names), + Commands::Run { + app_name, + app_version, + app_args, + } => run(app_name, app_version, app_args), + Commands::Update { url } => update(&exe_path, url), + Commands::Sync => sync(&exe_path), + } + } else { + let args = std::env::args().collect::>(); + run(&invoked_as, &None, &args[1..]) + } +} diff --git a/rozy/src/utils.rs b/rozy/src/utils.rs new file mode 100644 index 0000000..c157e84 --- /dev/null +++ b/rozy/src/utils.rs @@ -0,0 +1,11 @@ +use anyhow::Result; + +pub fn download_to(dest_path: &std::path::PathBuf, url: &str) -> Result<()> { + let tmp_dest_path = dest_path.clone().with_extension("tmp"); + let mut dest_file = std::fs::File::create(&tmp_dest_path)?; + let response = reqwest::blocking::get(url)?; + let mut content = std::io::Cursor::new(response.bytes()?); + std::io::copy(&mut content, &mut dest_file)?; + std::fs::rename(tmp_dest_path, dest_path)?; + Ok(()) +} diff --git a/rozy/test_resource/unittest.ozy.yaml b/rozy/test_resource/unittest.ozy.yaml new file mode 100644 index 0000000..933ddf6 --- /dev/null +++ b/rozy/test_resource/unittest.ozy.yaml @@ -0,0 +1,65 @@ +--- +name: Aquatic Team Configuration +ozy_version: 0.0.14 +ozy_download: https://github.com/aquanauts/ozy/releases/download/v{version}/ozy-{ozy_os}-{ozy_machine} +templates: + aquatic_conda: + type: conda + conda_bin: mamba + relocatable: false + channels: + - nodefaults +apps: + single_binary_zip_app: + version: 1.10.1 + type: single_binary_zip + url: some_url_1 + executable_path: bin/single_binary_zip_app + tarball_app: + version: 0.7.0 + type: tarball + url: some_url_2 + executable_path: bin/tarball_app + shell_app: + version: 4.11.0 + type: shell_install + url: some_url_3 + shell_args: + - -u + - -b + - -p + - $INSTALL_DIR + executable_path: bin/shell_app + extra_path_during_install: extra_path + pip_app: + version: 1.20.4 + type: pip + package: pip_package + executable_path: bin/pip_app + conda_app: + version: '45' + type: conda + package: conda-package + executable_path: bin/conda_app + conda_bin: conda_bin + channels: + - channel_a + - channel_b + file_app: + version: 6.6.0 + type: single_file + url: some_url_4 + explicitly_relocatable_app: + version: 1.10.1 + type: single_binary_zip + url: some_url_1 + relocatable: true + explicitly_nonrelocatable_app: + version: 1.10.1 + type: single_binary_zip + url: some_url_1 + relocatable: false + unspecified_relocatability_app: + version: 1.10.1 + type: single_binary_zip + url: some_url_1