From 9c4fb96d81db566db68998a4fed518177a60c30f Mon Sep 17 00:00:00 2001 From: Arthur Beck Date: Wed, 18 Dec 2024 06:47:16 -0600 Subject: [PATCH] Commit to work on chromebook --- .cargo/config.toml | 2 + .gitignore | 2 +- Cargo.toml | 8 + LICENSE | 21 - LICENSE-APACHE | 201 +++++++ LICENSE-MIT | 25 + local-registry/SOURCE | 3 + .../.cargo_vcs_info.json | 6 + .../cargo-local-registry-0.2.7/.gitignore | 1 + .../cargo-local-registry-0.2.7/.travis.yml | 52 ++ .../cargo-local-registry-0.2.7/Cargo.toml | 78 +++ .../Cargo.toml.orig | 32 ++ .../cargo-local-registry-0.2.7/LICENSE-APACHE | 201 +++++++ .../cargo-local-registry-0.2.7/LICENSE-MIT | 25 + .../cargo-local-registry-0.2.7/README.md | 82 +++ .../cargo-local-registry-0.2.7/appveyor.yml | 46 ++ .../cargo-local-registry-0.2.7/src/main.rs | 365 +++++++++++++ .../cargo-local-registry-0.2.7/tests/all.rs | 512 ++++++++++++++++++ local-registry/cargo-local-registry.crate | Bin 0 -> 36188 bytes rust-config.toml | 22 +- src/copied.rs | 11 +- src/crates_README.md | 10 + src/install/install.fish | 0 src/install/install.ps1 | 1 + src/install/install.sh | 3 + src/lib.rs | 107 +++- src/main.rs | 28 +- src/template/build.sh | 4 +- src/tests.rs | 1 + 29 files changed, 1807 insertions(+), 42 deletions(-) create mode 100644 .cargo/config.toml delete mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 local-registry/SOURCE create mode 100644 local-registry/cargo-local-registry-0.2.7/.cargo_vcs_info.json create mode 100644 local-registry/cargo-local-registry-0.2.7/.gitignore create mode 100644 local-registry/cargo-local-registry-0.2.7/.travis.yml create mode 100644 local-registry/cargo-local-registry-0.2.7/Cargo.toml create mode 100644 local-registry/cargo-local-registry-0.2.7/Cargo.toml.orig create mode 100644 local-registry/cargo-local-registry-0.2.7/LICENSE-APACHE create mode 100644 local-registry/cargo-local-registry-0.2.7/LICENSE-MIT create mode 100644 local-registry/cargo-local-registry-0.2.7/README.md create mode 100644 local-registry/cargo-local-registry-0.2.7/appveyor.yml create mode 100644 local-registry/cargo-local-registry-0.2.7/src/main.rs create mode 100644 local-registry/cargo-local-registry-0.2.7/tests/all.rs create mode 100644 local-registry/cargo-local-registry.crate create mode 100644 src/crates_README.md create mode 100644 src/install/install.fish create mode 100644 src/install/install.ps1 create mode 100644 src/install/install.sh diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..dfa84e8 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[unstable] +bindeps = true diff --git a/.gitignore b/.gitignore index 18c116f..c47d414 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,5 @@ Cargo.lock # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# normal data storage place when debugging +# default storage location on debug builds test/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 1f06b26..2f5a8c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,14 @@ name = "rust-pkg-gen" version = "0.1.0" edition = "2021" +license = "MIT/Apache-2.0" +repository = "https://github.com/AverseABFun/rust-pkg-gen" +homepage = "https://github.com/AverseABFun/rust-pkg-gen" +authors = ["Arthur Beck "] [dependencies] anyhow = "1.0.94" +cargo-local-registry = { version = "0.2.7", artifact = "bin" } clap = { version = "4.5.23", features = ["derive"] } clap_complete = "4.5.38" clap_mangen = "0.2.24" @@ -23,3 +28,6 @@ url = "2.5.4" [profile.release] opt-level = "s" debug-assertions = false # required to change the default behavior of --temp-dir + +[dev-dependencies] +proptest = "1.5.0" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 2143b44..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Arthur Beck - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..9b5f627 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2024 Arthur Beck + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..3bed9ec --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2024 Arthur Beck + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/local-registry/SOURCE b/local-registry/SOURCE new file mode 100644 index 0000000..c67e864 --- /dev/null +++ b/local-registry/SOURCE @@ -0,0 +1,3 @@ +Github repo: https://github.com/dhovart/cargo-local-registry +Crates.io: https://crates.io/crates/cargo-local-registry +Build requirements: Have GCC, OpenSSL, and CMake in the path. \ No newline at end of file diff --git a/local-registry/cargo-local-registry-0.2.7/.cargo_vcs_info.json b/local-registry/cargo-local-registry-0.2.7/.cargo_vcs_info.json new file mode 100644 index 0000000..ad4d53b --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "5fba6b7524103624c109de6d7dd10a42f30aa794" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/local-registry/cargo-local-registry-0.2.7/.gitignore b/local-registry/cargo-local-registry-0.2.7/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/.gitignore @@ -0,0 +1 @@ +target diff --git a/local-registry/cargo-local-registry-0.2.7/.travis.yml b/local-registry/cargo-local-registry-0.2.7/.travis.yml new file mode 100644 index 0000000..f7531c7 --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/.travis.yml @@ -0,0 +1,52 @@ +language: rust +sudo: false +rust: stable +dist: trusty + +matrix: + include: + - rust: nightly + + - env: TARGET=x86_64-unknown-linux-musl DEPLOY=1 + before_script: + - rustup target add $TARGET + script: cargo build --release --target $TARGET --locked --features vendored-openssl + addons: + apt: + packages: + - musl-tools + + - os: osx + - os: osx + env: MACOSX_DEPLOYMENT_TARGET=10.7 DEPLOY=1 TARGET=x86_64-apple-darwin OPENSSL_STATIC=1 + script: cargo build --release --target $TARGET --locked + + +script: + - cargo test --locked + +notifications: + email: + on_success: never + +before_deploy: + - name="cargo-local-registry-$TRAVIS_TAG-$TARGET" + - mkdir $name + - cp target/$TARGET/release/cargo-local-registry $name/ + - cp README.md LICENSE-MIT LICENSE-APACHE $name/ + - tar czvf $name.tar.gz $name + +branches: + only: + - master + +deploy: + api_key: + secure: "jwvkQVMOna+agQCnwCWUvTFniBaBxhA/x46PODfZCEPIHxQXnDVeiM3MC4aVTLF0qdUB9T8z8DwNbZYVZdC1CsmSbrITs5Xi9lT4iSSRpUXaI0sH+MuCq2z73sHJRAsHINd4fkFxrlMjH7KOWWHkaw2layxtbyOMc2d8J5tN3d38LjbMZvtoqRBKN6XitCrSGej0eEU/l3reoHOQ4gLHAxIJjF4ARxmyGfV+uin31Qyk/4OM1acHnqg18geLunwjYwV6DTXDflrLiVXMKYOizi+jciDAXPLe6M0vTPeTqc793Hy0MnijQ2bvrQzkwcd79+sXlpS4LXJ65O96DipeUJ0+zqq4VhuUpAZjvsKC8IhOFKvMynBvoaf5nV0txFG5595WkpEm0n63PB/ZpAxLrRtoT13oYajGT5s30CtF4hm+W9KDJiHAl4J0TVlI+hRm+e0roiSL/8XjzfdhYccfOp46jbOABc1PP6l+ekA+rkQk1PeANGmrSOTJxB1Ulz71t+6JMqR4ZZ4fI9yXf9Xh40VB+FtfT+H2L20L4xZG49L5jtnzDer3GwKQ3CGp4tPklkNrFNhwYQwVfeQk5BYnqHtYCtiLcixDlG9jBvl1kaBSnKxg9lqvvaoffX3IbZeoLsbm5cjR25zlYJCSJO8S8FEhAAnu3+A1D2ovFPcPP0I=" + file_glob: true + file: cargo-local-registry-$TRAVIS_TAG-$TARGET.tar.gz + on: + condition: $DEPLOY = 1 + tags: true + provider: releases + skip_cleanup: true diff --git a/local-registry/cargo-local-registry-0.2.7/Cargo.toml b/local-registry/cargo-local-registry-0.2.7/Cargo.toml new file mode 100644 index 0000000..2a82503 --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/Cargo.toml @@ -0,0 +1,78 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "cargo-local-registry" +version = "0.2.7" +authors = ["Alex Crichton "] +build = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = """ +A Cargo subcommand for managing local registries. +""" +homepage = "https://github.com/dhovart/cargo-local-registry" +readme = "README.md" +keywords = ["cargo-subcommand"] +license = "MIT/Apache-2.0" +repository = "https://github.com/dhovart/cargo-local-registry" + +[[bin]] +name = "cargo-local-registry" +path = "src/main.rs" + +[[test]] +name = "all" +path = "tests/all.rs" + +[dependencies.anyhow] +version = "1.0.47" + +[dependencies.cargo] +version = "0.81.0" + +[dependencies.cargo-platform] +version = "0.1.0" + +[dependencies.docopt] +version = "1.1.0" + +[dependencies.env_logger] +version = "0.11.0" + +[dependencies.flate2] +version = "1.0.22" + +[dependencies.openssl] +version = "0.10.41" +optional = true + +[dependencies.serde] +version = "1.0.104" +features = ["derive"] + +[dependencies.serde_json] +version = "1.0.46" + +[dependencies.tar] +version = "0.4.26" + +[dependencies.url] +version = "2.1.1" + +[dev-dependencies.tempfile] +version = "3.1.0" + +[features] +vendored-openssl = ["openssl/vendored"] diff --git a/local-registry/cargo-local-registry-0.2.7/Cargo.toml.orig b/local-registry/cargo-local-registry-0.2.7/Cargo.toml.orig new file mode 100644 index 0000000..a1a95cf --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/Cargo.toml.orig @@ -0,0 +1,32 @@ +[package] +name = "cargo-local-registry" +version = "0.2.7" +authors = ["Alex Crichton "] +license = "MIT/Apache-2.0" +readme = "README.md" +keywords = ["cargo-subcommand"] +repository = "https://github.com/dhovart/cargo-local-registry" +homepage = "https://github.com/dhovart/cargo-local-registry" +description = """ +A Cargo subcommand for managing local registries. +""" +edition = "2021" + +[dependencies] +anyhow = "1.0.47" +cargo = "0.81.0" +cargo-platform = "0.1.0" +docopt = "1.1.0" +env_logger = "0.11.0" +flate2 = "1.0.22" +openssl = { version = '0.10.41', optional = true } +serde = { version = "1.0.104", features = ['derive'] } +serde_json = "1.0.46" +tar = "0.4.26" +url = "2.1.1" + +[dev-dependencies] +tempfile = "3.1.0" + +[features] +vendored-openssl = ['openssl/vendored'] diff --git a/local-registry/cargo-local-registry-0.2.7/LICENSE-APACHE b/local-registry/cargo-local-registry-0.2.7/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/local-registry/cargo-local-registry-0.2.7/LICENSE-MIT b/local-registry/cargo-local-registry-0.2.7/LICENSE-MIT new file mode 100644 index 0000000..39e0ed6 --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/local-registry/cargo-local-registry-0.2.7/README.md b/local-registry/cargo-local-registry-0.2.7/README.md new file mode 100644 index 0000000..c800086 --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/README.md @@ -0,0 +1,82 @@ +# `cargo local-registry` + +[![Build Status](https://travis-ci.org/alexcrichton/cargo-local-registry.svg?branch=master)](https://travis-ci.org/alexcrichton/cargo-local-registry) +[![Build status](https://ci.appveyor.com/api/projects/status/x867la68pp0s94an/branch/master?svg=true)](https://ci.appveyor.com/project/alexcrichton/cargo-local-registry/branch/master) + +This is a Cargo subcommand to ease maintenance of local registries. Support for +a local registry is being added in +[rust-lang/cargo#2361](https://github.com/rust-lang/cargo/pull/2361) and will be +able to redirect all Cargo downloads/requests to a registry stored locally. + +This support is often useful for "offline builds" by preparing the list of all +Rust dependencies ahead of time and shipping them to a build machine in a +pre-ordained format. A local registry is an index and a collection of tarballs, +all of which currently originate from crates.io. + +The purpose of this subcommand will be to manage these registries and allow +adding/deleting packages with ease. + +## Installation + +To install from source you can execute: + +``` +cargo install cargo-local-registry +``` + +Note that you'll need the build tools listed below for this to succeed. If you'd +prefer to download precompiled binaries assembled on the CI for this repository, +you may also use the [GitHub releases][releases] + +[releases]: https://github.com/alexcrichton/cargo-local-registry/releases + +## Building + +As part of the build process you will need [gcc], [openssl] and [cmake] in your +`PATH`. + +[gcc]: https://gcc.gnu.org/install/download.html +[openssl]: https://www.openssl.org/source/ +[cmake]: https://cmake.org/download/ + +Afterwards you can build this repository via: + +``` +cargo build +``` + +And the resulting binary will be inside `target/debug` + +## Usage + +One of the primary operations will be to create a local registry from a lock +file itself. This can be done via + +``` +cargo local-registry --sync path/to/Cargo.lock path/to/registry +``` + +This command will: + +* Download all dependencies from the crates.io registry +* Verify all checksums of what's downloaded +* Place all downloads in `path/to/registry` +* Prepare the index of `path/to/registry` to reflect all this information + +# License + +This project is licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in cargo-local-registry by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/local-registry/cargo-local-registry-0.2.7/appveyor.yml b/local-registry/cargo-local-registry-0.2.7/appveyor.yml new file mode 100644 index 0000000..b4288f1 --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/appveyor.yml @@ -0,0 +1,46 @@ +environment: + global: + RUSTFLAGS: -Zunstable-options -Ctarget-feature=+crt-static + matrix: + - TARGET: x86_64-pc-windows-msvc + DEPLOY: 1 + +install: + # Install rust, x86_64-pc-windows-msvc host + - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -V + - cargo -V + +branches: + only: + - master + +build: false + +test_script: + - cargo test --locked + - cargo build --release + +before_deploy: + - ps: | + $NAME = "cargo-local-registry-${env:APPVEYOR_REPO_TAG_NAME}-${env:TARGET}" + New-Item -Path $NAME -ItemType directory + Copy-Item target/release/cargo-local-registry.exe "${NAME}/" + Copy-Item LICENSE-MIT "${NAME}/" + Copy-Item LICENSE-APACHE "${NAME}/" + Copy-Item README.md "${NAME}/" + 7z a -ttar "${NAME}.tar" "${NAME}" + 7z a "${NAME}.tar.gz" "${NAME}.tar" + Push-AppveyorArtifact "${NAME}.tar.gz" + +deploy: + artifact: /.*\.tar.gz/ + auth_token: + secure: nHB4fVo+y/Aak+L0nYfrT8Rcs8OfUNm0F2xcIVFVYJ9ehf0CzvCmSMUvWguM0kKp + description: '' + on: + appveyor_repo_tag: true + provider: GitHub + diff --git a/local-registry/cargo-local-registry-0.2.7/src/main.rs b/local-registry/cargo-local-registry-0.2.7/src/main.rs new file mode 100644 index 0000000..7c7c224 --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/src/main.rs @@ -0,0 +1,365 @@ +use anyhow::Context as _; +use cargo::core::dependency::DepKind; +use cargo::core::resolver::Resolve; +use cargo::core::{Package, SourceId, Workspace}; +use cargo::sources::PathSource; +use cargo::util::errors::*; +use cargo::util::GlobalContext; +use cargo_platform::Platform; +use docopt::Docopt; +use flate2::write::GzEncoder; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashSet}; +use std::env; +use std::fs::{self, File}; +use std::io; +use std::io::prelude::*; +use std::path::{self, Path, PathBuf}; +use tar::{Builder, Header}; +use url::Url; + +#[derive(Deserialize)] +struct Options { + arg_path: String, + flag_no_delete: Option, + flag_sync: Option, + flag_host: Option, + flag_verbose: u32, + flag_quiet: bool, + flag_color: Option, + flag_git: bool, +} + +#[derive(Deserialize, Serialize)] +struct RegistryPackage { + name: String, + vers: String, + deps: Vec, + cksum: String, + features: BTreeMap>, + yanked: Option, +} + +#[derive(Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] +struct RegistryDependency { + name: String, + req: String, + features: Vec, + optional: bool, + default_features: bool, + target: Option, + kind: Option, + package: Option, +} + +fn main() { + env_logger::init(); + + // We're doing the vendoring operation outselves, so we don't actually want + // to respect any of the `source` configuration in Cargo itself. That's + // intended for other consumers of Cargo, but we want to go straight to the + // source, e.g. crates.io, to fetch crates. + let mut config = { + let config_orig = GlobalContext::default().unwrap(); + let mut values = config_orig.values().unwrap().clone(); + values.remove("source"); + let config = GlobalContext::default().unwrap(); + config.set_values(values).unwrap(); + config + }; + + let usage = r#" +Vendor all dependencies for a project locally + +Usage: + cargo local-registry [options] [] + +Options: + -h, --help Print this message + -s, --sync LOCK Sync the registry with LOCK + --host HOST Registry index to sync with + --git Vendor git dependencies as well + -v, --verbose Use verbose output + -q, --quiet No output printed to stdout + --color WHEN Coloring: auto, always, never + --no-delete Don't delete older crates in the local registry directory +"#; + + let options = Docopt::new(usage) + .and_then(|d| d.deserialize()) + .unwrap_or_else(|e| e.exit()); + let result = real_main(options, &mut config); + if let Err(e) = result { + cargo::exit_with_error(e.into(), &mut config.shell()); + } +} + +fn real_main(options: Options, config: &mut GlobalContext) -> CargoResult<()> { + config.configure( + options.flag_verbose, + options.flag_quiet, + options.flag_color.as_deref(), + /* frozen = */ false, + /* locked = */ false, + /* offline = */ false, + /* target dir = */ &None, + /* unstable flags = */ &[], + /* cli_config = */ &[], + )?; + + let path = Path::new(&options.arg_path); + let index = path.join("index"); + + fs::create_dir_all(&index) + .with_context(|| format!("failed to create index: `{}`", index.display()))?; + let id = match options.flag_host { + Some(ref s) => SourceId::for_registry(&Url::parse(s)?)?, + None => SourceId::crates_io_maybe_sparse_http(config)?, + }; + + let lockfile = match options.flag_sync { + Some(ref file) => file, + None => return Ok(()), + }; + + sync(Path::new(lockfile), path, &id, &options, config).with_context(|| "failed to sync")?; + + println!( + "add this to your .cargo/config somewhere: + + [source.crates-io] + registry = '{}' + replace-with = 'local-registry' + + [source.local-registry] + local-registry = '{}' + +", + id.url(), + config.cwd().join(path).display() + ); + + Ok(()) +} + +fn sync( + lockfile: &Path, + local_dst: &Path, + registry_id: &SourceId, + options: &Options, + config: &GlobalContext, +) -> CargoResult<()> { + let no_delete = options.flag_no_delete.unwrap_or(false); + let canonical_local_dst = local_dst.canonicalize().unwrap_or(local_dst.to_path_buf()); + let manifest = lockfile.parent().unwrap().join("Cargo.toml"); + let manifest = env::current_dir().unwrap().join(&manifest); + let ws = Workspace::new(&manifest, config)?; + let (packages, resolve) = + cargo::ops::resolve_ws(&ws).with_context(|| "failed to load pkg lockfile")?; + packages.get_many(resolve.iter())?; + + let hash = cargo::util::hex::short_hash(registry_id); + let ident = registry_id.url().host().unwrap().to_string(); + let part = format!("{}-{}", ident, hash); + + let cache = config.registry_cache_path().join(&part); + + let mut added_crates = HashSet::new(); + let mut added_index = HashSet::new(); + for id in resolve.iter() { + if id.source_id().is_git() { + if !options.flag_git { + continue; + } + } else if !id.source_id().is_registry() { + continue; + } + + let pkg = packages + .get_one(id) + .with_context(|| "failed to fetch package")?; + let filename = format!("{}-{}.crate", id.name(), id.version()); + let dst = canonical_local_dst.join(&filename); + if id.source_id().is_registry() { + let src = cache.join(&filename).into_path_unlocked(); + fs::copy(&src, &dst).with_context(|| { + format!("failed to copy `{}` to `{}`", src.display(), dst.display()) + })?; + } else { + let file = File::create(&dst).unwrap(); + let gz = GzEncoder::new(file, flate2::Compression::best()); + let mut ar = Builder::new(gz); + ar.mode(tar::HeaderMode::Deterministic); + build_ar(&mut ar, pkg, config); + } + added_crates.insert(dst); + + let name = id.name().to_lowercase(); + let index_dir = canonical_local_dst.join("index"); + let dst = match name.len() { + 1 => index_dir.join("1").join(name), + 2 => index_dir.join("2").join(name), + 3 => index_dir.join("3").join(&name[..1]).join(name), + _ => index_dir.join(&name[..2]).join(&name[2..4]).join(name), + }; + fs::create_dir_all(dst.parent().unwrap())?; + let line = serde_json::to_string(®istry_pkg(pkg, &resolve)).unwrap(); + + let prev = if no_delete || added_index.contains(&dst) { + read(&dst).unwrap_or_default() + } else { + // If cleaning old entries (no_delete is not set), don't read the file unless we wrote + // it in one of the previous iterations. + String::new() + }; + let mut prev_entries = prev + .lines() + .filter(|line| { + let pkg: RegistryPackage = serde_json::from_str(line).unwrap(); + pkg.vers != id.version().to_string() + }) + .collect::>(); + prev_entries.push(&line); + prev_entries.sort(); + let new_contents = prev_entries.join("\n"); + + File::create(&dst).and_then(|mut f| f.write_all(new_contents.as_bytes()))?; + added_index.insert(dst); + } + + if !no_delete { + let existing_crates: Vec = canonical_local_dst + .read_dir() + .map(|iter| { + iter.filter_map(|e| e.ok()) + .filter(|e| { + e.file_name() + .to_str() + .map_or(false, |name| name.ends_with(".crate")) + }) + .map(|e| e.path()) + .collect::>() + }) + .unwrap_or_else(|_| Vec::new()); + + for path in existing_crates { + if !added_crates.contains(&path) { + fs::remove_file(&path)?; + } + } + + scan_delete(&canonical_local_dst.join("index"), 3, &added_index)?; + } + Ok(()) +} + +fn scan_delete(path: &Path, depth: usize, keep: &HashSet) -> CargoResult<()> { + if path.is_file() && !keep.contains(path) { + fs::remove_file(path)?; + } else if path.is_dir() && depth > 0 { + for entry in (path.read_dir()?).flatten() { + scan_delete(&entry.path(), depth - 1, keep)?; + } + + let is_empty = path.read_dir()?.next().is_none(); + // Don't delete "index" itself + if is_empty && depth != 3 { + fs::remove_dir(path)?; + } + } + Ok(()) +} + +fn build_ar(ar: &mut Builder>, pkg: &Package, config: &GlobalContext) { + let root = pkg.root(); + let src = PathSource::new(pkg.root(), pkg.package_id().source_id(), config); + for file in src.list_files(pkg).unwrap().iter() { + let relative = file.strip_prefix(root).unwrap(); + let relative = relative.to_str().unwrap(); + let mut file = File::open(file).unwrap(); + let path = format!( + "{}-{}{}{}", + pkg.name(), + pkg.version(), + path::MAIN_SEPARATOR, + relative + ); + + let mut header = Header::new_ustar(); + let metadata = file.metadata().unwrap(); + header.set_path(&path).unwrap(); + header.set_metadata(&metadata); + header.set_cksum(); + + ar.append(&header, &mut file).unwrap(); + } +} + +fn registry_pkg(pkg: &Package, resolve: &Resolve) -> RegistryPackage { + let id = pkg.package_id(); + let mut deps = pkg + .dependencies() + .iter() + .map(|dep| { + let (name, package) = match &dep.explicit_name_in_toml() { + Some(explicit) => (explicit.to_string(), Some(dep.package_name().to_string())), + None => (dep.package_name().to_string(), None), + }; + + RegistryDependency { + name, + req: dep.version_req().to_string(), + features: dep.features().iter().map(|s| s.to_string()).collect(), + optional: dep.is_optional(), + default_features: dep.uses_default_features(), + target: dep.platform().map(|platform| match *platform { + Platform::Name(ref s) => s.to_string(), + Platform::Cfg(ref s) => format!("cfg({})", s), + }), + kind: match dep.kind() { + DepKind::Normal => None, + DepKind::Development => Some("dev".to_string()), + DepKind::Build => Some("build".to_string()), + }, + package, + } + }) + .collect::>(); + deps.sort(); + + let features = pkg + .summary() + .features() + .iter() + .map(|(k, v)| { + let mut v = v.iter().map(|fv| fv.to_string()).collect::>(); + v.sort(); + (k.to_string(), v) + }) + .collect(); + + RegistryPackage { + name: id.name().to_string(), + vers: id.version().to_string(), + deps, + features, + cksum: resolve + .checksums() + .get(&id) + .cloned() + .unwrap_or_default() + .unwrap_or_default(), + yanked: Some(false), + } +} + +fn read(path: &Path) -> CargoResult { + let s = (|| -> io::Result<_> { + let mut contents = String::new(); + let mut f = File::open(path)?; + f.read_to_string(&mut contents)?; + Ok(contents) + })() + .with_context(|| format!("failed to read: {}", path.display()))?; + Ok(s) +} diff --git a/local-registry/cargo-local-registry-0.2.7/tests/all.rs b/local-registry/cargo-local-registry-0.2.7/tests/all.rs new file mode 100644 index 0000000..8f0d3d4 --- /dev/null +++ b/local-registry/cargo-local-registry-0.2.7/tests/all.rs @@ -0,0 +1,512 @@ +extern crate tempfile; + +use std::env; +use std::fs::{self, File}; +use std::io::prelude::*; +use std::process::Command; +use std::sync::{Once, Mutex, MutexGuard}; + +use tempfile::TempDir; + +fn cmd() -> Command { + let mut me = env::current_exe().unwrap(); + me.pop(); + if me.ends_with("deps") { + me.pop(); + } + me.push("cargo-local-registry"); + let mut cmd = Command::new(me); + cmd.arg("local-registry"); + return cmd +} + +static INIT: Once = Once::new(); +static mut LOCK: *mut Mutex<()> = 0 as *mut _; + +fn lock() -> MutexGuard<'static, ()> { + unsafe { + INIT.call_once(|| { + LOCK = Box::into_raw(Box::new(Mutex::new(()))); + }); + (*LOCK).lock().unwrap() + } +} + +#[test] +fn help() { + run(cmd().arg("--help")); + run(cmd().arg("-h")); +} + +#[test] +fn no_sync() { + let _l = lock(); + let td = TempDir::new().unwrap(); + let output = run(cmd().arg(td.path())); + assert!(td.path().join("index").exists()); + assert_eq!(output, ""); +} + +#[test] +fn dst_no_exists() { + let _l = lock(); + let td = TempDir::new().unwrap(); + let output = run(cmd().arg(td.path().join("foo"))); + assert!(td.path().join("foo/index").exists()); + assert_eq!(output, ""); +} + +#[test] +fn empty_cargo_lock() { + let _l = lock(); + let td = TempDir::new().unwrap(); + let lock = td.path().join("Cargo.lock"); + let registry = td.path().join("registry"); + fs::create_dir(td.path().join("src")).unwrap(); + File::create(&td.path().join("Cargo.toml")).unwrap().write_all(br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + "#).unwrap(); + File::create(&td.path().join("src/lib.rs")).unwrap().write_all(b"").unwrap(); + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [] +"#).unwrap(); + run(cmd().arg(®istry).arg("--sync").arg(&lock).arg("-v")); + + assert!(registry.join("index").is_dir()); + assert_eq!(registry.join("index").read_dir().unwrap().count(), 0); +} + +#[test] +fn libc_dependency() { + let _l = lock(); + let td = TempDir::new().unwrap(); + let lock = td.path().join("Cargo.lock"); + let registry = td.path().join("registry"); + fs::create_dir(td.path().join("src")).unwrap(); + File::create(&td.path().join("Cargo.toml")).unwrap().write_all(br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + libc = "0.2.6" + "#).unwrap(); + File::create(&td.path().join("src/lib.rs")).unwrap().write_all(b"").unwrap(); + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +"#).unwrap(); + println!("one: {}", run(cmd().arg(®istry).arg("--sync").arg(&lock))); + + assert!(registry.join("index").is_dir()); + assert!(registry.join("index/li/bc/libc").is_file()); + assert!(registry.join("libc-0.2.7.crate").is_file()); + + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "libc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +"#).unwrap(); + println!("two: {}", run(cmd().arg(®istry).arg("--sync").arg(&lock))); + + assert!(registry.join("index").is_dir()); + assert!(registry.join("index/li/bc/libc").is_file()); + assert!(registry.join("libc-0.2.6.crate").is_file()); + assert!(!registry.join("libc-0.2.7.crate").exists()); + + let mut contents = String::new(); + File::open(registry.join("index/li/bc/libc")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents.lines().count(), 1); + assert!(contents.contains("0.2.6")); +} + +#[test] +fn git_dependency() { + let _l = lock(); + let td = TempDir::new().unwrap(); + let lock = td.path().join("Cargo.lock"); + let registry = td.path().join("registry"); + fs::create_dir(td.path().join("src")).unwrap(); + File::create(&td.path().join("Cargo.toml")).unwrap().write_all(br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + libc = { git = "https://github.com/rust-lang/libc" } + "#).unwrap(); + File::create(&td.path().join("src/lib.rs")).unwrap().write_all(b"").unwrap(); + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "libc 0.2.16 (git+https://github.com/rust-lang/libc)", +] + +[[package]] +name = "libc" +version = "0.2.16" +source = "git+https://github.com/rust-lang/libc#36bec35aeb600bb1b8b47f4985a84a8d4a261747" +"#).unwrap(); + run(cmd().arg(®istry).arg("--sync").arg(&lock).arg("--git")); + + assert!(registry.join("index").is_dir()); + assert!(registry.join("index/li/bc/libc").is_file()); + assert!(registry.join("libc-0.2.16.crate").is_file()); +} + +#[test] +fn deterministic() { + let td = TempDir::new().unwrap(); + let lock = td.path().join("Cargo.lock"); + let registry = td.path().join("registry"); + fs::create_dir(td.path().join("src")).unwrap(); + File::create(&td.path().join("Cargo.toml")).unwrap().write_all(br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + libc = "0.1.4" + filetime = "0.1.10" + "#).unwrap(); + File::create(&td.path().join("src/lib.rs")).unwrap().write_all(b"").unwrap(); + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "libc 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "filetime" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] +"#).unwrap(); + run(cmd().arg(®istry).arg("--sync").arg(&lock)); + + let mut contents = String::new(); + File::open(registry.join("index/li/bc/libc")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"libc","vers":"0.1.4","deps":[],"cksum":"93a57b3496432ca744a67300dae196f8d4bbe33dfa7dc27adabfb6faa4643bb2","features":{"cargo-build":[],"default":["cargo-build"]},"yanked":false} +{"name":"libc","vers":"0.2.7","deps":[],"cksum":"4870ef6725dde13394134e587e4ab4eca13cb92e916209a31c851b49131d3c75","features":{"default":[]},"yanked":false}"#); + + // A lot of this doesn't look particularity ordered. It's not! This line is + // exactly as it appears in the crates.io index. As long as crates.io + // doesn't rewrite all the crates we should be fine. + contents.clear(); + File::open(registry.join("index/fi/le/filetime")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"filetime","vers":"0.1.10","deps":[{"name":"libc","req":"^0.2","features":[],"optional":false,"default_features":true,"target":null,"kind":null,"package":null},{"name":"tempdir","req":"^0.3","features":[],"optional":false,"default_features":true,"target":null,"kind":"dev","package":null}],"cksum":"5363ab8e4139b8568a6237db5248646e5a8a2f89bd5ccb02092182b11fd3e922","features":{},"yanked":false}"#); + + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "libc 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "filetime" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] +"#).unwrap(); + run(cmd().arg(®istry).arg("--sync").arg(&lock)); + + contents.clear(); + File::open(registry.join("index/li/bc/libc")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"libc","vers":"0.1.4","deps":[],"cksum":"93a57b3496432ca744a67300dae196f8d4bbe33dfa7dc27adabfb6faa4643bb2","features":{"cargo-build":[],"default":["cargo-build"]},"yanked":false} +{"name":"libc","vers":"0.2.6","deps":[],"cksum":"b608bf5e09bb38b075938d5d261682511bae283ef4549cc24fa66b1b8050de7b","features":{"default":[]},"yanked":false}"#); +} + +#[test] +fn lowercased() { + let td = TempDir::new().unwrap(); + let lock = td.path().join("Cargo.lock"); + let registry = td.path().join("registry"); + fs::create_dir(td.path().join("src")).unwrap(); + File::create(&td.path().join("Cargo.toml")).unwrap().write_all(br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + Inflector = "0.11.3" + "#).unwrap(); + File::create(&td.path().join("src/lib.rs")).unwrap().write_all(b"").unwrap(); + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "Inflector 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "Inflector" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +"#).unwrap(); + run(cmd().arg(®istry).arg("--sync").arg(&lock)); + + let mut contents = String::new(); + let path = registry.join("index/in/fl/inflector"); + let path = fs::canonicalize(path).unwrap(); + + assert_eq!(path.file_name().unwrap(), "inflector"); + + File::open(registry.join("index/in/fl/inflector")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"Inflector","vers":"0.11.3","deps":[{"name":"lazy_static","req":"^1.0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null},{"name":"regex","req":"^1.0","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null}],"cksum":"4467f98bb61f615f8273359bf1c989453dfc1ea4a45ae9298f1dcd0672febe5d","features":{"default":["heavyweight"],"heavyweight":["lazy_static","regex"],"lazy_static":["dep:lazy_static"],"regex":["dep:regex"],"unstable":[]},"yanked":false}"#); +} + +#[test] +fn renamed() { + let td = TempDir::new().unwrap(); + let lock = td.path().join("Cargo.lock"); + let registry = td.path().join("registry"); + fs::create_dir(td.path().join("src")).unwrap(); + File::create(&td.path().join("Cargo.toml")).unwrap().write_all(br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + rustc-demangle = "0.1.14" + "#).unwrap(); + File::create(&td.path().join("src/lib.rs")).unwrap().write_all(b"").unwrap(); + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +"#).unwrap(); + run(cmd().arg(®istry).arg("--sync").arg(&lock)); + + let mut contents = String::new(); + let path = registry.join("index/ru/st/rustc-demangle"); + let path = fs::canonicalize(path).unwrap(); + + assert_eq!(path.file_name().unwrap(), "rustc-demangle"); + + File::open(registry.join("index/ru/st/rustc-demangle")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"rustc-demangle","vers":"0.1.14","deps":[{"name":"compiler_builtins","req":"^0.1.2","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null},{"name":"core","req":"^1.0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":"rustc-std-workspace-core"}],"cksum":"ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288","features":{"compiler_builtins":["dep:compiler_builtins"],"core":["dep:core"],"rustc-dep-of-std":["compiler_builtins","core"]},"yanked":false}"#); +} + +#[test] +fn clean_mode() { + let td = TempDir::new().unwrap(); + let lock = td.path().join("Cargo.lock"); + let registry = td.path().join("registry"); + fs::create_dir(td.path().join("src")).unwrap(); + File::create(&td.path().join("Cargo.toml")).unwrap().write_all(br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + lazy_static = "0.2.11" + language-tags = "0.2.2" + "#).unwrap(); + File::create(&td.path().join("src/lib.rs")).unwrap().write_all(b"").unwrap(); + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"#).unwrap(); + run(cmd().arg(®istry).arg("--sync").arg(&lock)); + + assert!(registry.join("language-tags-0.2.2.crate").exists()); + assert!(registry.join("lazy_static-0.2.11.crate").exists()); + + let mut contents = String::new(); + File::open(registry.join("index/la/zy/lazy_static")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"lazy_static","vers":"0.2.11","deps":[{"name":"compiletest_rs","req":"^0.3","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null},{"name":"spin","req":"^0.4.6","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null}],"cksum":"76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73","features":{"compiletest":["compiletest_rs"],"compiletest_rs":["dep:compiletest_rs"],"nightly":[],"spin":["dep:spin"],"spin_no_std":["nightly","spin"]},"yanked":false}"#); + + contents.clear(); + File::open(registry.join("index/la/ng/language-tags")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"language-tags","vers":"0.2.2","deps":[{"name":"heapsize","req":">=0.2.2, <0.4","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null},{"name":"heapsize_plugin","req":"^0.1.2","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null}],"cksum":"a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a","features":{"heap_size":["heapsize","heapsize_plugin"],"heapsize":["dep:heapsize"],"heapsize_plugin":["dep:heapsize_plugin"]},"yanked":false}"#); + + // Modify the Cargo.toml to swap an existing library, add a new one and delete another + File::create(&td.path().join("Cargo.toml")).unwrap().write_all(br#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + lazy_static = "1.2.0" + lazycell = "1.2.1" + "#).unwrap(); + + File::create(&lock).unwrap().write_all(br#" +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazycell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +"#).unwrap(); + + // Run again -- no delete unused + run(cmd().arg(®istry).arg("--no-delete").arg("--sync").arg(&lock)); + + assert!(registry.join("language-tags-0.2.2.crate").exists()); + assert!(registry.join("lazy_static-0.2.11.crate").exists()); + assert!(registry.join("lazy_static-1.2.0.crate").exists()); + assert!(registry.join("lazycell-1.2.1.crate").exists()); + + contents.clear(); + File::open(registry.join("index/la/zy/lazy_static")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"lazy_static","vers":"0.2.11","deps":[{"name":"compiletest_rs","req":"^0.3","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null},{"name":"spin","req":"^0.4.6","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null}],"cksum":"76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73","features":{"compiletest":["compiletest_rs"],"compiletest_rs":["dep:compiletest_rs"],"nightly":[],"spin":["dep:spin"],"spin_no_std":["nightly","spin"]},"yanked":false} +{"name":"lazy_static","vers":"1.2.0","deps":[{"name":"spin","req":"^0.4.10","features":["once"],"optional":true,"default_features":false,"target":null,"kind":null,"package":null}],"cksum":"a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1","features":{"nightly":[],"spin":["dep:spin"],"spin_no_std":["spin"]},"yanked":false}"#); + + contents.clear(); + File::open(registry.join("index/la/zy/lazycell")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"lazycell","vers":"1.2.1","deps":[{"name":"clippy","req":"^0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null}],"cksum":"b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f","features":{"clippy":["dep:clippy"],"nightly":[],"nightly-testing":["clippy","nightly"]},"yanked":false}"#); + + contents.clear(); + File::open(registry.join("index/la/ng/language-tags")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"language-tags","vers":"0.2.2","deps":[{"name":"heapsize","req":">=0.2.2, <0.4","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null},{"name":"heapsize_plugin","req":"^0.1.2","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null}],"cksum":"a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a","features":{"heap_size":["heapsize","heapsize_plugin"],"heapsize":["dep:heapsize"],"heapsize_plugin":["dep:heapsize_plugin"]},"yanked":false}"#); + + // Run for the third time -- delete unused (default) + run(cmd().arg(®istry).arg("--sync").arg(&lock)); + + // should be deleted + assert!(!registry.join("language-tags-0.2.2.crate").exists()); + // should be deleted + assert!(!registry.join("lazy_static-0.2.11.crate").exists()); + assert!(registry.join("lazy_static-1.2.0.crate").exists()); + assert!(registry.join("lazycell-1.2.1.crate").exists()); + + // index and its parent directory should be cleaned, too + assert!(!registry.join("index").join("la").join("ng").exists()); + + contents.clear(); + File::open(registry.join("index/la/zy/lazy_static")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"lazy_static","vers":"1.2.0","deps":[{"name":"spin","req":"^0.4.10","features":["once"],"optional":true,"default_features":false,"target":null,"kind":null,"package":null}],"cksum":"a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1","features":{"nightly":[],"spin":["dep:spin"],"spin_no_std":["spin"]},"yanked":false}"#); + + contents.clear(); + File::open(registry.join("index/la/zy/lazycell")).unwrap() + .read_to_string(&mut contents).unwrap(); + assert_eq!(contents, r#"{"name":"lazycell","vers":"1.2.1","deps":[{"name":"clippy","req":"^0.0","features":[],"optional":true,"default_features":true,"target":null,"kind":null,"package":null}],"cksum":"b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f","features":{"clippy":["dep:clippy"],"nightly":[],"nightly-testing":["clippy","nightly"]},"yanked":false}"#); +} + +fn run(cmd: &mut Command) -> String { + let output = cmd.env("RUST_BACKTRACE", "1").output().unwrap(); + if !output.status.success() { + panic!("failed to run {:?}\n--- stdout\n{}\n--- stderr\n{}", cmd, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr)); + } + String::from_utf8_lossy(&output.stdout).into_owned() +} diff --git a/local-registry/cargo-local-registry.crate b/local-registry/cargo-local-registry.crate new file mode 100644 index 0000000000000000000000000000000000000000..1ead6a4e703f7041bbbe95a1ffb2020dc2b286e1 GIT binary patch literal 36188 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

DUQfZS81Ag2h6Ekp4Z ztcgppwctu1;R$3lap4-#pgUlTa!x>fDskp zj4dn9IQEsR$ZFM~MPpFl1F#|mtjP;bx~-)I4TB_ zfCoWt!{p#KlUJ4Fpro+zS#3um`AZVgo@-&{W6Wke63< zqIp>N?!mwYm$+=W9}qN3mL3+PL=7;;{b&JaHAN@6uxEtbb}5$(l3Bgx?1nCWxhD#D zQ?j^kXw$31P}{+)j_V>dBn`;hD{iD=NLcT(;yAQoYLQ&zvAF;(q7wTVM8()tthF`B z=+Tiq#KXjo&c@~P(LDmr07fQ^lmM>+M~f84wR5nX+JJOam{QbtBjU>;Tp-1 z+^p>Jp>C2|`-ZovsQc)(B1x0w7D3TQEi2?wJ8VUt3=g7E%@|#Zp2Np6U>%JUl6zfw za4}m=kLDPa2|a#Vn>GakW}Kv^xdU4XMgyX;``Fs(q3NdFBO3LoVdAm*O_d3o46LbXm#5`V|&-OZ{+eeUp%tW-7 zylo_c$j(E`BkT~jLNg%JfX3XxFv;99^qch*4XCDawxnuXZzA>;UC)o;zwYYobbdMN z8Ul0Ugg$SIesP>E3j@c*p$p#$ z;M!49W+<(rj5TNpDpb32{YNKjt0efJ?}w;H^B0>o-q+LdI|$i$udbjU-dhf=sB>^G zXPb*NnYDXTRY-QF2;Zo~Fp?BR2G%Y}=7V0C3J4i$?rST?b(t5_>7lD=cs!CwMJbdj zTvaFr0z;+Tq;gqSZLklF@R3_cWKOZiB0D3s(;$se0e2k_uv&Gw=wB?3&poL1K75aP zE^BOp*IIMqW-COo09afay%{n|G#?zGN5(B~V?{nxlN*NRxb$w%;=E+lH+y}gQGp)3 zQvnD^NjaqoM&et|UFcFUf`A+Y)VF2>gW}|?9^8p|ap8^Y!Npbg! zh81z#%O&?e{iY}1{vZbL(CWBR5s0C;0+v*$7?Y12qDkCjnH2-y*kyLai_=Pzk0{Isa<-In5;eodKmtIuo}so1Cy!MLg&TGWxGlV-sVU|QQin>r3BcP!5?&WGI8>4)k+ggi_hGV(K`Wf%K7Y4NbfNV?cPe@ zw$bW|k*~MSqxX9$IY6>S^1*X;x=4TkOhF|Fr*p+R#Vphb%m9ZOHkeN7B5i2KdVLkB;(d3!#iH%HqS4}MVR0SF00=gxdwy*yXU30Of>-uha7`%kAR> znYBWM$rPb@*mszdKFdDMuU>F}-A4~_=OeO^BKI5&5*A(BX1Fhz2mFXNah{{)DwItC z7GdvmS4!EM#7Ie|o@8Y9EJ2U2`cXrgFK%ddqIUwjs2<($UOO#^K_Sz4j(h+Lnd}y5(G6R zS$(lC+R6z$UMF6 z`h#moZK3_Vx#Sd8 zWJeR}46CP!u#gpW(s{|ftpqpiDt+jgpMNo<(c;i8Cgb4vSr+ahrd$-2v&po{&_CFu zxq3x-(ngV+&U-ANp=%#43d{!<7@syqLWE;6iQBB*H=AS2J-OTB@I>;|+MTo2 zQ)t)2b9(mr>XHrKWdx?gaccqXNi@UtjIA@+{gpc#mr{i&Uf~rV6y*qdtfOdIgMNUh z#5^S5+HFbp*cI6tH*6*?KqjN(-mc!k*lO7_pKuBpr8R^L9}+cR0t^*&pD~bpc&}^(63tS`2t`WrX9bR%e1=pD|iJh__mxE;$ST*3H z%Rm&x-OJ;+y>1WpKu%|+&T2Vtz*~#`SU~XzgtSQv4C5!|qs`G& qF<}A73c1^0P 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 Q?+*X|KRh~JO8`Ow0KewWLjV8( literal 0 HcmV?d00001 diff --git a/rust-config.toml b/rust-config.toml index 39294b3..86531a9 100644 --- a/rust-config.toml +++ b/rust-config.toml @@ -4,11 +4,27 @@ toolchains = [ "rustfmt", "rustc", "cargo", - ], crate_id = "2021-nightly-complete", platforms = ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-gnu"], targets = ["x86_64-unknown-linux-gnu"]}, + ], crate-id = "2021-nightly-complete", platforms = [ + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-gnu", + ], targets = [ + "x86_64-unknown-linux-gnu", + ], format-map = { "x86_64-pc-windows-gnu" = "x86_64-pc-windows-gnu", "x86_64-unknown-linux-gnu" = "x86_64-unknown-linux-gnu" } }, ] -[x64_package_linux_rust_pkg_gen.meta] -offline = true +[x64_package_linux_rust_pkg_gen.formats] +x86_64-pc-windows-gnu = [ + "msi", # no suffix: assume "-if-available". "-if-available" will provide the requested format if it's available, + # "-only" will error out if the requested format isn't available with the provided settings. + # Excluding the suffix, should be one of "msi" "pkg" "gz" "xz". + # msi and pkg should only be provided for windows and darwin, respectively + + "gz", + # elements past the first are only available if the suffix(or assumed suffix) is "-if-available". They should be one of "gz" "xz". Cannot include suffixes. + "xz", +] + +x86_64-unknown-linux-gnu = ["gz-only"] [x64_package_linux_rust_pkg_gen.crates] "2021-nightly-complete" = { "syn" = "2.0.90" } diff --git a/src/copied.rs b/src/copied.rs index d5c6b24..c536034 100644 --- a/src/copied.rs +++ b/src/copied.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Error}; use filebuffer::FileBuffer; use sha2::{Digest, Sha256}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::fs::{copy, create_dir_all, File}; use std::io::{Read, Write}; use std::path::{Component, Path, PathBuf}; @@ -25,6 +25,7 @@ fn file_sha256(file_path: &Path) -> Option { } fn download(upstream_url: &str, dir: &str, path: &str) -> Result { + println!("Downloading file {}...", path); let manifest = format!("{}{}", upstream_url, path); let mut response = reqwest::blocking::get(&manifest)?; let mirror = Path::new(dir); @@ -85,6 +86,7 @@ pub fn download_all( components: Vec<&str>, for_targets: Vec<&str>, quiet: bool, + format_map: HashMap<&str, Vec>, ) { for channel in channels.clone() { if !crate::targets::RELEASE_CHANNELS.contains(&channel) { @@ -127,13 +129,6 @@ pub fn download_all( let mut value = data.parse::().unwrap(); assert_eq!(value["manifest-version"].as_str(), Some("2")); - if !quiet { - println!( - "Channel {} date {}", - channel, - value["date"].as_str().unwrap() - ); - } for ele in for_targets.clone() { if ele.contains("windows") { diff --git a/src/crates_README.md b/src/crates_README.md new file mode 100644 index 0000000..ce06152 --- /dev/null +++ b/src/crates_README.md @@ -0,0 +1,10 @@ +To properly use this source, add this to your `.cargo/config[.toml]` somewhere: + +```toml +[source.crates-io] +registry = 'sparse+' +replace-with = 'local-registry' + +[source.local-registry] +local-registry = '{?TOOLCHAIN.CRATES_DIR}' # double-check this path is correct, especially if you are creating the package on a different machine than the machine you are creating the package for +``` diff --git a/src/install/install.fish b/src/install/install.fish new file mode 100644 index 0000000..e69de29 diff --git a/src/install/install.ps1 b/src/install/install.ps1 new file mode 100644 index 0000000..e7c8d4f --- /dev/null +++ b/src/install/install.ps1 @@ -0,0 +1 @@ +Start-Process \ No newline at end of file diff --git a/src/install/install.sh b/src/install/install.sh new file mode 100644 index 0000000..480e954 --- /dev/null +++ b/src/install/install.sh @@ -0,0 +1,3 @@ +TARGET="{?TOOLCHAIN.TARGET}" +TOOLCHAIN_DIR="{?TOOLCHAIN.DIST_DIR}" +tar \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index bbeec1d..de9d81f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,10 @@ use std::{collections::HashMap, fs, path::Path}; pub mod copied; pub mod resources; pub mod targets; -mod tests; +pub mod tests; #[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] pub struct Toolchain { pub edition: String, pub channel: String, @@ -15,22 +16,116 @@ pub struct Toolchain { pub crate_id: String, pub platforms: Vec, pub targets: Vec, + pub format_map: HashMap, +} + +#[derive(Debug, Clone)] +pub enum Suffix { + IfAvailable, + Only, +} + +struct StringVisitor; + +impl<'de> serde::de::Visitor<'de> for StringVisitor { + type Value = String; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("any UTF-8 encoded string") + } + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + Ok(v) + } + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(v.to_string()) + } +} + +#[derive(Debug, Clone)] +pub struct Format { + pub format: String, + pub suffix: Suffix, +} + +impl<'de> Deserialize<'de> for Format { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let base_string = deserializer.deserialize_str(StringVisitor)?; + let split = base_string.split_once("-").unwrap_or((&base_string, "")); + let suffix = split.1.to_string(); + let real_suffix = match suffix.as_str() { + "-only" => Suffix::Only, + _ => Suffix::IfAvailable, + }; + Ok(Format { + format: split.0.to_string(), + suffix: real_suffix, + }) + } } #[derive(Deserialize, Debug, Clone)] -pub struct Meta { - pub offline: bool, +#[serde(untagged)] +pub enum Crate { + Version(String), + Detailed { + version: Option, + features: Option>, + path: Option, + git: Option, + }, } -pub type Crate = String; +impl Crate { + pub fn serialize(self) -> String { + if let Crate::Version(str) = self { + return str; + } else { + let Crate::Detailed { + version, + features, + path, + git, + } = self + else { + unreachable!(); + }; + let mut out = String::new(); + if let Some(version) = version { + out += &format!("version = {},", version); + } + if let Some(mut features) = features { + for ele in &mut features { + *ele = format!("\"{}\"", ele); + } + out += &format!("features = [{}],", features.join(", ")); + } + if let Some(path) = path { + out += &format!("path = {},", path); + } + if let Some(git) = git { + out += &format!("git = {},", git); + } + out = format!("{{ {} }}", out); + return out; + } + } +} -pub type Crates = HashMap>; +pub type Crates = HashMap>; #[derive(Deserialize, Debug, Clone)] pub struct RustConfigInner { pub toolchains: Vec, - pub meta: Meta, pub crates: Crates, + pub formats: HashMap>, } pub type RustConfig = HashMap; diff --git a/src/main.rs b/src/main.rs index 9a6d767..41c4526 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,11 @@ use clap::Parser; use rand::{Rng, SeedableRng}; use rust_pkg_gen::resources::TemplateAssets; -use std::{fs, path::PathBuf, process::Stdio}; +use std::{ + fs::{self, write}, + path::PathBuf, + process::Stdio, +}; fn gen_char() -> u8 { rand::rngs::StdRng::from_entropy().gen_range(65..90) @@ -63,7 +67,7 @@ fn generate_crates( ) -> String { let mut out: String = String::new(); for (crte, version) in cfg.crates.get(&toolchain.crate_id).unwrap() { - out = format!("{}{} = \"{}\"\n", out, crte, version).to_string(); + out = format!("{}{} = \"{}\"\n", out, crte, version.clone().serialize()).to_string(); } out } @@ -155,6 +159,10 @@ fn main() { dir.join(PathBuf::from("crates")) .join(PathBuf::from("build.sh")), ); + build = build.env( + "CARGO_BIN_FILE_CARGO_LOCAL_REGISTRY", + env!("CARGO_BIN_FILE_CARGO_LOCAL_REGISTRY"), + ); if args.quiet { build = build .stdout(Stdio::null()) @@ -165,6 +173,18 @@ fn main() { build = build.arg("save") } build.spawn().unwrap().wait().unwrap(); + write( + dir.join(PathBuf::from("crates")) + .join(PathBuf::from("README.md")), + include_str!("crates_README.md").replace( + "{?TOOLCHAIN.CRATES_DIR}", + fs::canonicalize(dir.join(PathBuf::from("crates"))) + .unwrap() + .to_str() + .unwrap(), + ), + ) + .unwrap(); rust_pkg_gen::copied::download_all( vec![&toolchain.channel], @@ -175,6 +195,10 @@ fn main() { toolchain.components.iter().map(|s| &**s).collect(), toolchain.platforms.iter().map(|s| &**s).collect(), args.quiet, + toolchain + .format_map + .iter() + .map(|(k, v)| (k, cfg.formats[v])), ); if !args.save_temp { diff --git a/src/template/build.sh b/src/template/build.sh index 82e0a36..c55ec38 100644 --- a/src/template/build.sh +++ b/src/template/build.sh @@ -2,7 +2,9 @@ CI=1 cd "$(dirname "$0")" cargo generate-lockfile --verbose -cargo vendor --verbose --locked ./crates +"$CARGO_BIN_FILE_CARGO_LOCAL_REGISTRY" local-registry --sync Cargo.lock crates > /dev/null 2>&1 + # ^^^ is super jank, but it expects that the first argument is the provided subcommand of cargo. + mv crates/* . if [[ "$1" != "save" ]]; then rm Cargo.lock Cargo.toml rust-toolchain.toml src/main.rs .cargo/config.toml build.sh diff --git a/src/tests.rs b/src/tests.rs index e69de29..8b13789 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -0,0 +1 @@ +