├── .github └── workflows │ ├── linux-builds-on-master.yaml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── docs ├── erlang.png ├── erlup.css ├── erlup.js ├── fonts │ ├── FiraSans-Light.woff │ ├── FiraSans-Medium.woff │ ├── FiraSans-Regular.woff │ ├── Inconsolata-Regular.ttf │ ├── OFL.txt │ └── WorkSans-Medium.ttf ├── index.html ├── normalize.css └── website_config.json ├── image.png └── src ├── build.rs ├── cli.yml ├── config.rs ├── erl.rs └── main.rs /.github/workflows/linux-builds-on-master.yaml: -------------------------------------------------------------------------------- 1 | name: Linux (master) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | target: 15 | - x86_64-unknown-linux-gnu 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Cache Rust dependencies 19 | uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.cargo/registry 23 | ~/.cargo/git 24 | target 25 | key: ${{ runner.OS }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }} 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: stable 29 | target: ${{ matrix.target }} 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | use-cross: true 33 | command: build 34 | args: --release --target ${{ matrix.target }} 35 | 36 | - name: Configure AWS credentials 37 | uses: aws-actions/configure-aws-credentials@v1 38 | with: 39 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 40 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 41 | aws-region: us-east-1 42 | 43 | - name: Copy erlup binary to S3 bucket 44 | run: | 45 | aws s3 cp ./target/${{ matrix.target }}/release/erlup s3://erlup.rs/master/${{ matrix.target }}/erlup 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2024, axodotdev 2 | # SPDX-License-Identifier: MIT or Apache-2.0 3 | # 4 | # CI that: 5 | # 6 | # * checks for a Git Tag that looks like a release 7 | # * builds artifacts with cargo-dist (archives, installers, hashes) 8 | # * uploads those artifacts to temporary workflow zip 9 | # * on success, uploads the artifacts to a GitHub Release 10 | # 11 | # Note that the GitHub Release will be created with a generated 12 | # title/body based on your changelogs. 13 | 14 | name: Release 15 | 16 | permissions: 17 | contents: write 18 | 19 | # This task will run whenever you push a git tag that looks like a version 20 | # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. 21 | # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where 22 | # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION 23 | # must be a Cargo-style SemVer Version (must have at least major.minor.patch). 24 | # 25 | # If PACKAGE_NAME is specified, then the announcement will be for that 26 | # package (erroring out if it doesn't have the given version or isn't cargo-dist-able). 27 | # 28 | # If PACKAGE_NAME isn't specified, then the announcement will be for all 29 | # (cargo-dist-able) packages in the workspace with that version (this mode is 30 | # intended for workspaces with only one dist-able package, or with all dist-able 31 | # packages versioned/released in lockstep). 32 | # 33 | # If you push multiple tags at once, separate instances of this workflow will 34 | # spin up, creating an independent announcement for each one. However, GitHub 35 | # will hard limit this to 3 tags per commit, as it will assume more tags is a 36 | # mistake. 37 | # 38 | # If there's a prerelease-style suffix to the version, then the release(s) 39 | # will be marked as a prerelease. 40 | on: 41 | push: 42 | tags: 43 | - '**[0-9]+.[0-9]+.[0-9]+*' 44 | pull_request: 45 | 46 | jobs: 47 | # Run 'cargo dist plan' (or host) to determine what tasks we need to do 48 | plan: 49 | runs-on: ubuntu-latest 50 | outputs: 51 | val: ${{ steps.plan.outputs.manifest }} 52 | tag: ${{ !github.event.pull_request && github.ref_name || '' }} 53 | tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} 54 | publishing: ${{ !github.event.pull_request }} 55 | env: 56 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | steps: 58 | - uses: actions/checkout@v4 59 | with: 60 | submodules: recursive 61 | - name: Install cargo-dist 62 | # we specify bash to get pipefail; it guards against the `curl` command 63 | # failing. otherwise `sh` won't catch that `curl` returned non-0 64 | shell: bash 65 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.14.1/cargo-dist-installer.sh | sh" 66 | # sure would be cool if github gave us proper conditionals... 67 | # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible 68 | # functionality based on whether this is a pull_request, and whether it's from a fork. 69 | # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* 70 | # but also really annoying to build CI around when it needs secrets to work right.) 71 | - id: plan 72 | run: | 73 | cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json 74 | echo "cargo dist ran successfully" 75 | cat plan-dist-manifest.json 76 | echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" 77 | - name: "Upload dist-manifest.json" 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: artifacts-plan-dist-manifest 81 | path: plan-dist-manifest.json 82 | 83 | # Build and packages all the platform-specific things 84 | build-local-artifacts: 85 | name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) 86 | # Let the initial task tell us to not run (currently very blunt) 87 | needs: 88 | - plan 89 | if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} 90 | strategy: 91 | fail-fast: false 92 | # Target platforms/runners are computed by cargo-dist in create-release. 93 | # Each member of the matrix has the following arguments: 94 | # 95 | # - runner: the github runner 96 | # - dist-args: cli flags to pass to cargo dist 97 | # - install-dist: expression to run to install cargo-dist on the runner 98 | # 99 | # Typically there will be: 100 | # - 1 "global" task that builds universal installers 101 | # - N "local" tasks that build each platform's binaries and platform-specific installers 102 | matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} 103 | runs-on: ${{ matrix.runner }} 104 | env: 105 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 | BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json 107 | steps: 108 | - name: enable windows longpaths 109 | run: | 110 | git config --global core.longpaths true 111 | - uses: actions/checkout@v4 112 | with: 113 | submodules: recursive 114 | - uses: swatinem/rust-cache@v2 115 | with: 116 | key: ${{ join(matrix.targets, '-') }} 117 | - name: Install cargo-dist 118 | run: ${{ matrix.install_dist }} 119 | # Get the dist-manifest 120 | - name: Fetch local artifacts 121 | uses: actions/download-artifact@v4 122 | with: 123 | pattern: artifacts-* 124 | path: target/distrib/ 125 | merge-multiple: true 126 | - name: Install dependencies 127 | run: | 128 | ${{ matrix.packages_install }} 129 | - name: Build artifacts 130 | run: | 131 | # Actually do builds and make zips and whatnot 132 | cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json 133 | echo "cargo dist ran successfully" 134 | - id: cargo-dist 135 | name: Post-build 136 | # We force bash here just because github makes it really hard to get values up 137 | # to "real" actions without writing to env-vars, and writing to env-vars has 138 | # inconsistent syntax between shell and powershell. 139 | shell: bash 140 | run: | 141 | # Parse out what we just built and upload it to scratch storage 142 | echo "paths<> "$GITHUB_OUTPUT" 143 | jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" 144 | echo "EOF" >> "$GITHUB_OUTPUT" 145 | 146 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 147 | - name: "Upload artifacts" 148 | uses: actions/upload-artifact@v4 149 | with: 150 | name: artifacts-build-local-${{ join(matrix.targets, '_') }} 151 | path: | 152 | ${{ steps.cargo-dist.outputs.paths }} 153 | ${{ env.BUILD_MANIFEST_NAME }} 154 | 155 | # Build and package all the platform-agnostic(ish) things 156 | build-global-artifacts: 157 | needs: 158 | - plan 159 | - build-local-artifacts 160 | runs-on: "ubuntu-20.04" 161 | env: 162 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 163 | BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json 164 | steps: 165 | - uses: actions/checkout@v4 166 | with: 167 | submodules: recursive 168 | - name: Install cargo-dist 169 | shell: bash 170 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.14.1/cargo-dist-installer.sh | sh" 171 | # Get all the local artifacts for the global tasks to use (for e.g. checksums) 172 | - name: Fetch local artifacts 173 | uses: actions/download-artifact@v4 174 | with: 175 | pattern: artifacts-* 176 | path: target/distrib/ 177 | merge-multiple: true 178 | - id: cargo-dist 179 | shell: bash 180 | run: | 181 | cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json 182 | echo "cargo dist ran successfully" 183 | 184 | # Parse out what we just built and upload it to scratch storage 185 | echo "paths<> "$GITHUB_OUTPUT" 186 | jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" 187 | echo "EOF" >> "$GITHUB_OUTPUT" 188 | 189 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 190 | - name: "Upload artifacts" 191 | uses: actions/upload-artifact@v4 192 | with: 193 | name: artifacts-build-global 194 | path: | 195 | ${{ steps.cargo-dist.outputs.paths }} 196 | ${{ env.BUILD_MANIFEST_NAME }} 197 | # Determines if we should publish/announce 198 | host: 199 | needs: 200 | - plan 201 | - build-local-artifacts 202 | - build-global-artifacts 203 | # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) 204 | if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} 205 | env: 206 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 207 | runs-on: "ubuntu-20.04" 208 | outputs: 209 | val: ${{ steps.host.outputs.manifest }} 210 | steps: 211 | - uses: actions/checkout@v4 212 | with: 213 | submodules: recursive 214 | - name: Install cargo-dist 215 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.14.1/cargo-dist-installer.sh | sh" 216 | # Fetch artifacts from scratch-storage 217 | - name: Fetch artifacts 218 | uses: actions/download-artifact@v4 219 | with: 220 | pattern: artifacts-* 221 | path: target/distrib/ 222 | merge-multiple: true 223 | # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce" 224 | - id: host 225 | shell: bash 226 | run: | 227 | cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json 228 | echo "artifacts uploaded and released successfully" 229 | cat dist-manifest.json 230 | echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" 231 | - name: "Upload dist-manifest.json" 232 | uses: actions/upload-artifact@v4 233 | with: 234 | # Overwrite the previous copy 235 | name: artifacts-dist-manifest 236 | path: dist-manifest.json 237 | 238 | # Create a GitHub Release while uploading all files to it 239 | announce: 240 | needs: 241 | - plan 242 | - host 243 | # use "always() && ..." to allow us to wait for all publish jobs while 244 | # still allowing individual publish jobs to skip themselves (for prereleases). 245 | # "host" however must run to completion, no skipping allowed! 246 | if: ${{ always() && needs.host.result == 'success' }} 247 | runs-on: "ubuntu-20.04" 248 | env: 249 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 250 | steps: 251 | - uses: actions/checkout@v4 252 | with: 253 | submodules: recursive 254 | - name: "Download GitHub Artifacts" 255 | uses: actions/download-artifact@v4 256 | with: 257 | pattern: artifacts-* 258 | path: artifacts 259 | merge-multiple: true 260 | - name: Cleanup 261 | run: | 262 | # Remove the granular manifests 263 | rm -f artifacts/*-dist-manifest.json 264 | - name: Create GitHub Release 265 | uses: ncipollo/release-action@v1 266 | with: 267 | tag: ${{ needs.plan.outputs.tag }} 268 | name: ${{ fromJson(needs.host.outputs.val).announcement_title }} 269 | body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }} 270 | prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }} 271 | artifacts: "artifacts/*" 272 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.7" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.4" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 49 | dependencies = [ 50 | "windows-sys 0.52.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.52.0", 61 | ] 62 | 63 | [[package]] 64 | name = "bitflags" 65 | version = "1.3.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 68 | 69 | [[package]] 70 | name = "bitflags" 71 | version = "2.6.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 74 | 75 | [[package]] 76 | name = "cfg-if" 77 | version = "1.0.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 80 | 81 | [[package]] 82 | name = "clap" 83 | version = "4.5.8" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" 86 | dependencies = [ 87 | "clap_builder", 88 | "clap_derive", 89 | ] 90 | 91 | [[package]] 92 | name = "clap_builder" 93 | version = "4.5.8" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" 96 | dependencies = [ 97 | "anstream", 98 | "anstyle", 99 | "clap_lex", 100 | "strsim", 101 | ] 102 | 103 | [[package]] 104 | name = "clap_derive" 105 | version = "4.5.8" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" 108 | dependencies = [ 109 | "heck", 110 | "proc-macro2", 111 | "quote", 112 | "syn", 113 | ] 114 | 115 | [[package]] 116 | name = "clap_lex" 117 | version = "0.7.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" 120 | 121 | [[package]] 122 | name = "colorchoice" 123 | version = "1.0.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 126 | 127 | [[package]] 128 | name = "console" 129 | version = "0.15.8" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 132 | dependencies = [ 133 | "encode_unicode", 134 | "lazy_static", 135 | "libc", 136 | "unicode-width", 137 | "windows-sys 0.52.0", 138 | ] 139 | 140 | [[package]] 141 | name = "const-random" 142 | version = "0.1.18" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" 145 | dependencies = [ 146 | "const-random-macro", 147 | ] 148 | 149 | [[package]] 150 | name = "const-random-macro" 151 | version = "0.1.16" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" 154 | dependencies = [ 155 | "getrandom", 156 | "once_cell", 157 | "tiny-keccak", 158 | ] 159 | 160 | [[package]] 161 | name = "crunchy" 162 | version = "0.2.2" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 165 | 166 | [[package]] 167 | name = "dirs" 168 | version = "5.0.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 171 | dependencies = [ 172 | "dirs-sys", 173 | ] 174 | 175 | [[package]] 176 | name = "dirs-sys" 177 | version = "0.4.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 180 | dependencies = [ 181 | "libc", 182 | "option-ext", 183 | "redox_users", 184 | "windows-sys 0.48.0", 185 | ] 186 | 187 | [[package]] 188 | name = "dlv-list" 189 | version = "0.5.2" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" 192 | dependencies = [ 193 | "const-random", 194 | ] 195 | 196 | [[package]] 197 | name = "encode_unicode" 198 | version = "0.3.6" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 201 | 202 | [[package]] 203 | name = "env_filter" 204 | version = "0.1.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" 207 | dependencies = [ 208 | "log", 209 | "regex", 210 | ] 211 | 212 | [[package]] 213 | name = "env_logger" 214 | version = "0.11.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" 217 | dependencies = [ 218 | "anstream", 219 | "anstyle", 220 | "env_filter", 221 | "humantime", 222 | "log", 223 | ] 224 | 225 | [[package]] 226 | name = "erlup" 227 | version = "0.2.0" 228 | dependencies = [ 229 | "clap", 230 | "console", 231 | "dirs", 232 | "env_logger", 233 | "glob", 234 | "indicatif", 235 | "log", 236 | "num_cpus", 237 | "rust-ini", 238 | "shell-words", 239 | "tar", 240 | "tempdir", 241 | ] 242 | 243 | [[package]] 244 | name = "errno" 245 | version = "0.3.9" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 248 | dependencies = [ 249 | "libc", 250 | "windows-sys 0.52.0", 251 | ] 252 | 253 | [[package]] 254 | name = "filetime" 255 | version = "0.2.23" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" 258 | dependencies = [ 259 | "cfg-if", 260 | "libc", 261 | "redox_syscall", 262 | "windows-sys 0.52.0", 263 | ] 264 | 265 | [[package]] 266 | name = "fuchsia-cprng" 267 | version = "0.1.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 270 | 271 | [[package]] 272 | name = "getrandom" 273 | version = "0.2.15" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 276 | dependencies = [ 277 | "cfg-if", 278 | "libc", 279 | "wasi", 280 | ] 281 | 282 | [[package]] 283 | name = "glob" 284 | version = "0.3.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 287 | 288 | [[package]] 289 | name = "hashbrown" 290 | version = "0.14.5" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 293 | 294 | [[package]] 295 | name = "heck" 296 | version = "0.5.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 299 | 300 | [[package]] 301 | name = "hermit-abi" 302 | version = "0.3.9" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 305 | 306 | [[package]] 307 | name = "humantime" 308 | version = "2.1.0" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 311 | 312 | [[package]] 313 | name = "indicatif" 314 | version = "0.17.8" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" 317 | dependencies = [ 318 | "console", 319 | "instant", 320 | "number_prefix", 321 | "portable-atomic", 322 | "unicode-width", 323 | ] 324 | 325 | [[package]] 326 | name = "instant" 327 | version = "0.1.13" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 330 | dependencies = [ 331 | "cfg-if", 332 | ] 333 | 334 | [[package]] 335 | name = "is_terminal_polyfill" 336 | version = "1.70.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 339 | 340 | [[package]] 341 | name = "lazy_static" 342 | version = "1.5.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 345 | 346 | [[package]] 347 | name = "libc" 348 | version = "0.2.155" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 351 | 352 | [[package]] 353 | name = "libredox" 354 | version = "0.1.3" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 357 | dependencies = [ 358 | "bitflags 2.6.0", 359 | "libc", 360 | ] 361 | 362 | [[package]] 363 | name = "linux-raw-sys" 364 | version = "0.4.14" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 367 | 368 | [[package]] 369 | name = "log" 370 | version = "0.4.22" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 373 | 374 | [[package]] 375 | name = "memchr" 376 | version = "2.7.4" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 379 | 380 | [[package]] 381 | name = "num_cpus" 382 | version = "1.16.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 385 | dependencies = [ 386 | "hermit-abi", 387 | "libc", 388 | ] 389 | 390 | [[package]] 391 | name = "number_prefix" 392 | version = "0.4.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 395 | 396 | [[package]] 397 | name = "once_cell" 398 | version = "1.19.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 401 | 402 | [[package]] 403 | name = "option-ext" 404 | version = "0.2.0" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 407 | 408 | [[package]] 409 | name = "ordered-multimap" 410 | version = "0.7.3" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" 413 | dependencies = [ 414 | "dlv-list", 415 | "hashbrown", 416 | ] 417 | 418 | [[package]] 419 | name = "portable-atomic" 420 | version = "1.6.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 423 | 424 | [[package]] 425 | name = "proc-macro2" 426 | version = "1.0.86" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 429 | dependencies = [ 430 | "unicode-ident", 431 | ] 432 | 433 | [[package]] 434 | name = "quote" 435 | version = "1.0.36" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 438 | dependencies = [ 439 | "proc-macro2", 440 | ] 441 | 442 | [[package]] 443 | name = "rand" 444 | version = "0.4.6" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 447 | dependencies = [ 448 | "fuchsia-cprng", 449 | "libc", 450 | "rand_core 0.3.1", 451 | "rdrand", 452 | "winapi", 453 | ] 454 | 455 | [[package]] 456 | name = "rand_core" 457 | version = "0.3.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 460 | dependencies = [ 461 | "rand_core 0.4.2", 462 | ] 463 | 464 | [[package]] 465 | name = "rand_core" 466 | version = "0.4.2" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 469 | 470 | [[package]] 471 | name = "rdrand" 472 | version = "0.4.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 475 | dependencies = [ 476 | "rand_core 0.3.1", 477 | ] 478 | 479 | [[package]] 480 | name = "redox_syscall" 481 | version = "0.4.1" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 484 | dependencies = [ 485 | "bitflags 1.3.2", 486 | ] 487 | 488 | [[package]] 489 | name = "redox_users" 490 | version = "0.4.5" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" 493 | dependencies = [ 494 | "getrandom", 495 | "libredox", 496 | "thiserror", 497 | ] 498 | 499 | [[package]] 500 | name = "regex" 501 | version = "1.10.5" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 504 | dependencies = [ 505 | "aho-corasick", 506 | "memchr", 507 | "regex-automata", 508 | "regex-syntax", 509 | ] 510 | 511 | [[package]] 512 | name = "regex-automata" 513 | version = "0.4.7" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 516 | dependencies = [ 517 | "aho-corasick", 518 | "memchr", 519 | "regex-syntax", 520 | ] 521 | 522 | [[package]] 523 | name = "regex-syntax" 524 | version = "0.8.4" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 527 | 528 | [[package]] 529 | name = "remove_dir_all" 530 | version = "0.5.3" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 533 | dependencies = [ 534 | "winapi", 535 | ] 536 | 537 | [[package]] 538 | name = "rust-ini" 539 | version = "0.21.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "0d625ed57d8f49af6cfa514c42e1a71fadcff60eb0b1c517ff82fe41aa025b41" 542 | dependencies = [ 543 | "cfg-if", 544 | "ordered-multimap", 545 | "trim-in-place", 546 | ] 547 | 548 | [[package]] 549 | name = "rustix" 550 | version = "0.38.34" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 553 | dependencies = [ 554 | "bitflags 2.6.0", 555 | "errno", 556 | "libc", 557 | "linux-raw-sys", 558 | "windows-sys 0.52.0", 559 | ] 560 | 561 | [[package]] 562 | name = "shell-words" 563 | version = "1.1.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 566 | 567 | [[package]] 568 | name = "strsim" 569 | version = "0.11.1" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 572 | 573 | [[package]] 574 | name = "syn" 575 | version = "2.0.68" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" 578 | dependencies = [ 579 | "proc-macro2", 580 | "quote", 581 | "unicode-ident", 582 | ] 583 | 584 | [[package]] 585 | name = "tar" 586 | version = "0.4.41" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" 589 | dependencies = [ 590 | "filetime", 591 | "libc", 592 | "xattr", 593 | ] 594 | 595 | [[package]] 596 | name = "tempdir" 597 | version = "0.3.7" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 600 | dependencies = [ 601 | "rand", 602 | "remove_dir_all", 603 | ] 604 | 605 | [[package]] 606 | name = "thiserror" 607 | version = "1.0.61" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 610 | dependencies = [ 611 | "thiserror-impl", 612 | ] 613 | 614 | [[package]] 615 | name = "thiserror-impl" 616 | version = "1.0.61" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 619 | dependencies = [ 620 | "proc-macro2", 621 | "quote", 622 | "syn", 623 | ] 624 | 625 | [[package]] 626 | name = "tiny-keccak" 627 | version = "2.0.2" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 630 | dependencies = [ 631 | "crunchy", 632 | ] 633 | 634 | [[package]] 635 | name = "trim-in-place" 636 | version = "0.1.7" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" 639 | 640 | [[package]] 641 | name = "unicode-ident" 642 | version = "1.0.12" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 645 | 646 | [[package]] 647 | name = "unicode-width" 648 | version = "0.1.13" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" 651 | 652 | [[package]] 653 | name = "utf8parse" 654 | version = "0.2.2" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 657 | 658 | [[package]] 659 | name = "wasi" 660 | version = "0.11.0+wasi-snapshot-preview1" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 663 | 664 | [[package]] 665 | name = "winapi" 666 | version = "0.3.9" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 669 | dependencies = [ 670 | "winapi-i686-pc-windows-gnu", 671 | "winapi-x86_64-pc-windows-gnu", 672 | ] 673 | 674 | [[package]] 675 | name = "winapi-i686-pc-windows-gnu" 676 | version = "0.4.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 679 | 680 | [[package]] 681 | name = "winapi-x86_64-pc-windows-gnu" 682 | version = "0.4.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 685 | 686 | [[package]] 687 | name = "windows-sys" 688 | version = "0.48.0" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 691 | dependencies = [ 692 | "windows-targets 0.48.5", 693 | ] 694 | 695 | [[package]] 696 | name = "windows-sys" 697 | version = "0.52.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 700 | dependencies = [ 701 | "windows-targets 0.52.5", 702 | ] 703 | 704 | [[package]] 705 | name = "windows-targets" 706 | version = "0.48.5" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 709 | dependencies = [ 710 | "windows_aarch64_gnullvm 0.48.5", 711 | "windows_aarch64_msvc 0.48.5", 712 | "windows_i686_gnu 0.48.5", 713 | "windows_i686_msvc 0.48.5", 714 | "windows_x86_64_gnu 0.48.5", 715 | "windows_x86_64_gnullvm 0.48.5", 716 | "windows_x86_64_msvc 0.48.5", 717 | ] 718 | 719 | [[package]] 720 | name = "windows-targets" 721 | version = "0.52.5" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 724 | dependencies = [ 725 | "windows_aarch64_gnullvm 0.52.5", 726 | "windows_aarch64_msvc 0.52.5", 727 | "windows_i686_gnu 0.52.5", 728 | "windows_i686_gnullvm", 729 | "windows_i686_msvc 0.52.5", 730 | "windows_x86_64_gnu 0.52.5", 731 | "windows_x86_64_gnullvm 0.52.5", 732 | "windows_x86_64_msvc 0.52.5", 733 | ] 734 | 735 | [[package]] 736 | name = "windows_aarch64_gnullvm" 737 | version = "0.48.5" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 740 | 741 | [[package]] 742 | name = "windows_aarch64_gnullvm" 743 | version = "0.52.5" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 746 | 747 | [[package]] 748 | name = "windows_aarch64_msvc" 749 | version = "0.48.5" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 752 | 753 | [[package]] 754 | name = "windows_aarch64_msvc" 755 | version = "0.52.5" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 758 | 759 | [[package]] 760 | name = "windows_i686_gnu" 761 | version = "0.48.5" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 764 | 765 | [[package]] 766 | name = "windows_i686_gnu" 767 | version = "0.52.5" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 770 | 771 | [[package]] 772 | name = "windows_i686_gnullvm" 773 | version = "0.52.5" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 776 | 777 | [[package]] 778 | name = "windows_i686_msvc" 779 | version = "0.48.5" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 782 | 783 | [[package]] 784 | name = "windows_i686_msvc" 785 | version = "0.52.5" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 788 | 789 | [[package]] 790 | name = "windows_x86_64_gnu" 791 | version = "0.48.5" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 794 | 795 | [[package]] 796 | name = "windows_x86_64_gnu" 797 | version = "0.52.5" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 800 | 801 | [[package]] 802 | name = "windows_x86_64_gnullvm" 803 | version = "0.48.5" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 806 | 807 | [[package]] 808 | name = "windows_x86_64_gnullvm" 809 | version = "0.52.5" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 812 | 813 | [[package]] 814 | name = "windows_x86_64_msvc" 815 | version = "0.48.5" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 818 | 819 | [[package]] 820 | name = "windows_x86_64_msvc" 821 | version = "0.52.5" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 824 | 825 | [[package]] 826 | name = "xattr" 827 | version = "1.3.1" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" 830 | dependencies = [ 831 | "libc", 832 | "linux-raw-sys", 833 | "rustix", 834 | ] 835 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erlup" 3 | version = "0.2.0" 4 | authors = ["Tristan Sloughter "] 5 | edition = "2021" 6 | repository = "https://github.com/tsloughter/erlup" 7 | 8 | [dependencies] 9 | clap = {version = "4.5.8", features = ["derive", "color"]} 10 | log = "0.4.21" 11 | env_logger = "0.11.3" 12 | tempdir = "0.3.4" 13 | tar = "0.4.5" 14 | glob = "0.3.1" 15 | rust-ini = "0.21" 16 | dirs = "5.0.1" 17 | indicatif = "0.17.8" 18 | console = "0.15.8" 19 | num_cpus = "1.8.0" 20 | shell-words = "1.0.0" 21 | 22 | # The profile that 'cargo dist' will build with 23 | [profile.dist] 24 | inherits = "release" 25 | lto = "thin" 26 | 27 | # Config for 'cargo dist' 28 | [workspace.metadata.dist] 29 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 30 | cargo-dist-version = "0.14.1" 31 | # CI backends to support 32 | ci = "github" 33 | # The installers to generate for each app 34 | installers = ["shell", "powershell"] 35 | # Target platforms to build apps for (Rust target-triple syntax) 36 | targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"# , "x86_64-pc-windows-msvc" 37 | ] 38 | # Publish jobs to run in CI 39 | pr-run-mode = "plan" 40 | # Whether to install an updater program 41 | install-updater = false 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ERLUP 2 | ===== 3 | 4 | Manage multiple Erlang installs with per directory configuration. 5 | 6 | ![screenshot](image.png) 7 | 8 | ## Build 9 | 10 | ``` 11 | $ cargo build --release 12 | ``` 13 | 14 | ## Setup 15 | 16 | If you download a binary from the github releases you must rename it to `erlup` for it to work. 17 | 18 | Because `erlup` creates symlinks from commands like `erl` to the `erlup` binary you must be sure the directory the symlinks are created, `~/.cache/erlup/bin`, is in your `PATH`: 19 | 20 | ``` 21 | $ mkdir -p ~/.cache/erlup/bin 22 | $ export PATH=~/.cache/erlup/bin:$PATH 23 | ``` 24 | 25 | ## Build Erlang 26 | 27 | `erlup` will create a default config under `~/.config/erlup/config` if you don't create it yourself and it'll contain: 28 | 29 | ``` 30 | [erlup] 31 | dir=/.cache/erlup 32 | 33 | [repos] 34 | default=https://github.com/erlang/otp 35 | ``` 36 | 37 | To list tags available to build one: 38 | 39 | ``` 40 | $ erlup tags 41 | ... 42 | $ erlup build OTP-21.2 43 | ``` 44 | 45 | ## Add a Repo 46 | 47 | To add an alternative Erlang/OTP repo use `erlup repo add `. For 48 | example to add Lukas' repo to build the JIT branch: 49 | 50 | ``` shell 51 | $ erlup repo add garazdawi https://github.com/garazdawi/otp 52 | $ erlup fetch -r garazdawi 53 | $ erlup build -r garazdawi origin/beamasm 54 | ``` 55 | 56 | ## Configuring Erlang Compilation 57 | 58 | To pass options to `./configure` (like for setting where SSL ) you can add them in the config file: 59 | 60 | ``` ini 61 | [erlup] 62 | default_configure_options=--enable-lock-counter 63 | ``` 64 | 65 | Or pass through the env variable `ERLUP_CONFIGURE_OPTIONS`: 66 | 67 | ``` shellsession 68 | $ ERLUP_CONFIGURE_OPTIONS=--enable-lock-counter erlup build OTP-21.2 69 | ``` 70 | 71 | ## Acknowledgements 72 | 73 | Inspiration for `erlup` is [erln8](https://github.com/metadave/erln8) by Dave Parfitt. He no longer maintains it and I figured I could use writing my own as a way to learn Rust. 74 | -------------------------------------------------------------------------------- /docs/erlang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsloughter/erlup/193d2977defdf82f2c13bb1c40e9ee185285e26e/docs/erlang.png -------------------------------------------------------------------------------- /docs/erlup.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Fira Sans'; 3 | font-style: normal; 4 | font-weight: 300; 5 | src: local('Fira Sans Light'), url("fonts/FiraSans-Light.woff") format('woff'); 6 | } 7 | @font-face { 8 | font-family: 'Fira Sans'; 9 | font-style: normal; 10 | font-weight: 400; 11 | src: local('Fira Sans'), url("fonts/FiraSans-Regular.woff") format('woff'); 12 | } 13 | @font-face { 14 | font-family: 'Fira Sans'; 15 | font-style: normal; 16 | font-weight: 500; 17 | src: local('Fira Sans Medium'), url("fonts/FiraSans-Medium.woff") format('woff'); 18 | } 19 | 20 | @font-face { 21 | font-family: 'Work Sans'; 22 | font-style: normal; 23 | font-weight: 500; 24 | src: local('Work Sans Medium'), url("fonts/WorkSans-Medium.ttf") format('ttf'); 25 | } 26 | 27 | @font-face { 28 | font-family: 'Inconsolata'; 29 | font-style: normal; 30 | font-weight: 400; 31 | src: local('Inconsolata Regular'), url("fonts/Inconsolata-Regular.ttf") format('ttf'); 32 | } 33 | 34 | body { 35 | margin-top: 2em; 36 | background-color: white; 37 | color: #515151; 38 | font-family: "Fira Sans","Helvetica Neue",Helvetica,Arial,sans-serif; 39 | font-weight: 300; 40 | font-size: 25px; 41 | } 42 | 43 | pre { 44 | font-family: Inconsolata,Menlo,Monaco,Consolas,"Courier New",monospace; 45 | font-weight: 400; 46 | } 47 | 48 | body#idx #pitch > a { 49 | font-weight: 500; 50 | line-height: 2em; 51 | } 52 | 53 | a { 54 | color: #428bca; 55 | text-decoration: none; 56 | } 57 | 58 | a:hover { 59 | color: rgb(42, 100, 150); 60 | } 61 | 62 | body#idx > * { 63 | margin-left: auto; 64 | margin-right: auto; 65 | text-align: center; 66 | width: 35em; 67 | } 68 | 69 | body#idx > #pitch { 70 | width: 35rem; 71 | } 72 | 73 | #pitch em { 74 | font-style: normal; 75 | font-weight: 400; 76 | } 77 | 78 | body#idx p { 79 | margin-top: 2em; 80 | margin-bottom: 2em; 81 | } 82 | 83 | body#idx p.other-platforms-help { 84 | font-size: 0.6em; 85 | } 86 | 87 | .instructions { 88 | background-color: rgb(250, 250, 250); 89 | margin-left: auto; 90 | margin-right: auto; 91 | text-align: center; 92 | border-radius: 3px; 93 | border: 1px solid rgb(204, 204, 204); 94 | box-shadow: 0px 1px 4px 0px rgb(204, 204, 204); 95 | } 96 | 97 | .instructions > * { 98 | width: 40rem; 99 | margin-left: auto; 100 | margin-right: auto; 101 | } 102 | 103 | hr { 104 | margin-top: 2em; 105 | margin-bottom: 2em; 106 | } 107 | 108 | #platform-instructions-unix > pre, 109 | #platform-instructions-win32 > pre, 110 | #platform-instructions-win64 > pre, 111 | #platform-instructions-default > div > pre, 112 | #platform-instructions-unknown > div > pre { 113 | background-color: #515151; 114 | color: white; 115 | margin-left: auto; 116 | margin-right: auto; 117 | padding: 1rem; 118 | width: 45rem; 119 | text-align: center; 120 | border-radius: 3px; 121 | box-shadow: inset 0px 0px 20px 0px #333333; 122 | overflow-x: scroll; 123 | font-size: 0.6em; 124 | } 125 | 126 | #platform-instructions-win32 a.windows-download, 127 | #platform-instructions-win64 a.windows-download, 128 | #platform-instructions-default a.windows-download, 129 | #platform-instructions-unknown a.windows-download { 130 | display: block; 131 | padding-top: 0.4rem; 132 | padding-bottom: 0.6rem; 133 | font-family: "Work Sans", "Fira Sans","Helvetica Neue",Helvetica,Arial,sans-serif; 134 | font-weight: 500; 135 | letter-spacing: 0.1rem; 136 | } 137 | 138 | /* This is the box that prints navigator.platform, navigator.appVersion values */ 139 | #platform-instructions-unknown > div:first-of-type { 140 | font-size: 16px; 141 | line-height: 2rem; 142 | } 143 | 144 | #about { 145 | font-size: 16px; 146 | line-height: 2em; 147 | } 148 | 149 | #about > img { 150 | width: 30px; 151 | height: 30px; 152 | transform: translateY(11px); 153 | } 154 | 155 | #platform-button { 156 | background-color: #515151; 157 | color: white; 158 | margin-left: auto; 159 | margin-right: auto; 160 | padding: 1em; 161 | } 162 | 163 | .display-none { 164 | display: none; 165 | } 166 | 167 | .display-block { 168 | display: block; 169 | } 170 | 171 | .display-inline { 172 | display: inline; 173 | } 174 | -------------------------------------------------------------------------------- /docs/erlup.js: -------------------------------------------------------------------------------- 1 | // IF YOU CHANGE THIS FILE IT MUST BE CHANGED ON BOTH rust-www and rustup.rs 2 | 3 | var platforms = ["default", "unknown", "win32", "win64", "unix"]; 4 | var platform_override = null; 5 | 6 | function detect_platform() { 7 | "use strict"; 8 | 9 | if (platform_override !== null) { 10 | return platforms[platform_override]; 11 | } 12 | 13 | var os = "unknown"; 14 | 15 | if (navigator.platform == "Linux x86_64") {os = "unix";} 16 | if (navigator.platform == "Linux i686") {os = "unix";} 17 | if (navigator.platform == "Linux i686 on x86_64") {os = "unix";} 18 | if (navigator.platform == "Linux aarch64") {os = "unix";} 19 | if (navigator.platform == "Linux armv6l") {os = "unix";} 20 | if (navigator.platform == "Linux armv7l") {os = "unix";} 21 | if (navigator.platform == "Linux armv8l") {os = "unix";} 22 | if (navigator.platform == "Linux ppc64") {os = "unix";} 23 | if (navigator.platform == "Linux mips") {os = "unix";} 24 | if (navigator.platform == "Linux mips64") {os = "unix";} 25 | if (navigator.platform == "Mac") {os = "unix";} 26 | if (navigator.platform == "Win32") {os = "win32";} 27 | if (navigator.platform == "Win64" || 28 | navigator.userAgent.indexOf("WOW64") != -1 || 29 | navigator.userAgent.indexOf("Win64") != -1) { os = "win64"; } 30 | if (navigator.platform == "FreeBSD x86_64") {os = "unix";} 31 | if (navigator.platform == "FreeBSD amd64") {os = "unix";} 32 | if (navigator.platform == "NetBSD x86_64") {os = "unix";} 33 | if (navigator.platform == "NetBSD amd64") {os = "unix";} 34 | 35 | // I wish I knew by now, but I don't. Try harder. 36 | if (os == "unknown") { 37 | if (navigator.appVersion.indexOf("Win")!=-1) {os = "win32";} 38 | if (navigator.appVersion.indexOf("Mac")!=-1) {os = "unix";} 39 | // rust-www/#692 - FreeBSD epiphany! 40 | if (navigator.appVersion.indexOf("FreeBSD")!=-1) {os = "unix";} 41 | } 42 | 43 | // Firefox Quantum likes to hide platform and appVersion but oscpu works 44 | if (navigator.oscpu) { 45 | if (navigator.oscpu.indexOf("Win32")!=-1) {os = "win32";} 46 | if (navigator.oscpu.indexOf("Win64")!=-1) {os = "win64";} 47 | if (navigator.oscpu.indexOf("Mac")!=-1) {os = "unix";} 48 | if (navigator.oscpu.indexOf("Linux")!=-1) {os = "unix";} 49 | if (navigator.oscpu.indexOf("FreeBSD")!=-1) {os = "unix";} 50 | if (navigator.oscpu.indexOf("NetBSD")!=-1) {os = "unix";} 51 | } 52 | 53 | return os; 54 | } 55 | 56 | function vis(elem, value) { 57 | var possible = ["block", "inline", "none"]; 58 | for (var i = 0; i < possible.length; i++) { 59 | if (possible[i] === value) { 60 | elem.classList.add("display-" + possible[i]); 61 | } else { 62 | elem.classList.remove("display-" + possible[i]); 63 | } 64 | } 65 | } 66 | 67 | function adjust_for_platform() { 68 | "use strict"; 69 | 70 | var platform = detect_platform(); 71 | 72 | platforms.forEach(function (platform_elem) { 73 | var platform_div = document.getElementById("platform-instructions-" + platform_elem); 74 | vis(platform_div, "none"); 75 | if (platform == platform_elem) { 76 | vis(platform_div, "block"); 77 | } 78 | }); 79 | 80 | adjust_platform_specific_instrs(platform); 81 | } 82 | 83 | // NB: This has no effect on rustup.rs 84 | function adjust_platform_specific_instrs(platform) { 85 | var platform_specific = document.getElementsByClassName("platform-specific"); 86 | for (var el of platform_specific) { 87 | var el_is_not_win = el.className.indexOf("not-win") !== -1; 88 | var el_is_inline = el.tagName.toLowerCase() == "span"; 89 | var el_visible_style = "block"; 90 | if (el_is_inline) { 91 | el_visible_style = "inline"; 92 | } 93 | if (platform == "win64" || platform == "win32") { 94 | if (el_is_not_win) { 95 | vis(el, "none"); 96 | } else { 97 | vis(el, el_visible_style); 98 | } 99 | } else { 100 | if (el_is_not_win) { 101 | vis(el, el_visible_style); 102 | } else { 103 | vis(el, "none"); 104 | } 105 | } 106 | } 107 | } 108 | 109 | function cycle_platform() { 110 | if (platform_override == null) { 111 | platform_override = 0; 112 | } else { 113 | platform_override = (platform_override + 1) % platforms.length; 114 | } 115 | adjust_for_platform(); 116 | } 117 | 118 | function set_up_cycle_button() { 119 | var cycle_button = document.getElementById("platform-button"); 120 | cycle_button.onclick = cycle_platform; 121 | 122 | var key="test"; 123 | var idx=0; 124 | var unlocked=false; 125 | 126 | document.onkeypress = function(event) { 127 | if (event.key == "n" && unlocked) { 128 | cycle_platform(); 129 | } 130 | 131 | if (event.key == key[idx]) { 132 | idx += 1; 133 | 134 | if (idx == key.length) { 135 | vis(cycle_button, "block"); 136 | unlocked = true; 137 | cycle_platform(); 138 | } 139 | } else if (event.key == key[0]) { 140 | idx = 1; 141 | } else { 142 | idx = 0; 143 | } 144 | }; 145 | } 146 | 147 | function go_to_default_platform() { 148 | platform_override = 0; 149 | adjust_for_platform(); 150 | } 151 | 152 | // NB: This has no effect on rust-lang.org/install.html 153 | function set_up_default_platform_buttons() { 154 | var defaults_buttons = document.getElementsByClassName('default-platform-button'); 155 | for (var i = 0; i < defaults_buttons.length; i++) { 156 | defaults_buttons[i].onclick = go_to_default_platform; 157 | } 158 | } 159 | 160 | function fill_in_bug_report_values() { 161 | var nav_plat = document.getElementById("nav-plat"); 162 | var nav_app = document.getElementById("nav-app"); 163 | nav_plat.textContent = navigator.platform; 164 | nav_app.textContent = navigator.appVersion; 165 | } 166 | 167 | (function () { 168 | adjust_for_platform(); 169 | set_up_cycle_button(); 170 | set_up_default_platform_buttons(); 171 | fill_in_bug_report_values(); 172 | }()); 173 | -------------------------------------------------------------------------------- /docs/fonts/FiraSans-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsloughter/erlup/193d2977defdf82f2c13bb1c40e9ee185285e26e/docs/fonts/FiraSans-Light.woff -------------------------------------------------------------------------------- /docs/fonts/FiraSans-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsloughter/erlup/193d2977defdf82f2c13bb1c40e9ee185285e26e/docs/fonts/FiraSans-Medium.woff -------------------------------------------------------------------------------- /docs/fonts/FiraSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsloughter/erlup/193d2977defdf82f2c13bb1c40e9ee185285e26e/docs/fonts/FiraSans-Regular.woff -------------------------------------------------------------------------------- /docs/fonts/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsloughter/erlup/193d2977defdf82f2c13bb1c40e9ee185285e26e/docs/fonts/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /docs/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Raph Levien (firstname.lastname@gmail.com), Copyright (c) 2012, Cyreal (cyreal.org) 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font creation 14 | efforts of academic and linguistic communities, and to provide a free and 15 | open framework in which fonts may be shared and improved in partnership 16 | with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply 25 | to any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software components as 36 | distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, deleting, 39 | or substituting -- in part or in whole -- any of the components of the 40 | Original Version, by changing formats or by porting the Font Software to a 41 | new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION & CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 49 | redistribute, and sell modified and unmodified copies of the Font 50 | Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, 53 | in Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the corresponding 64 | Copyright Holder. This restriction only applies to the primary font name as 65 | presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created 77 | using the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /docs/fonts/WorkSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsloughter/erlup/193d2977defdf82f2c13bb1c40e9ee185285e26e/docs/fonts/WorkSans-Medium.ttf -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | erlup.rs - The Erlang toolchain installer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 |

21 | erlup is an installer for
22 | the programming language 23 | Erlang 24 |

25 | 26 | 31 | 32 | 42 | 43 | 53 | 54 | 98 | 99 |
100 |
101 |

To install Erlang, if you are running Unix,
run the following 102 | in your terminal, then follow the onscreen instructions.

103 |
curl --proto '=https' --tlsv1.2 -sSf https://sh.erlup.rs | sh
104 |
105 | 106 |
107 | 108 |
109 |

110 | If you are running Windows 64-bit,
download and run 111 | erlup‑init.exe 112 | then follow the onscreen instructions. 113 |

114 |
115 | 116 |
117 | 118 |
119 |

120 | If you are running Windows 32-bit,
download and run 121 | erlup‑init.exe 122 | then follow the onscreen instructions. 123 |

124 |
125 |
126 | 127 |

128 | Need help?
Ask on Erlang Slack
129 | or in the Erlang Users Forum. 130 |

131 | 132 |

133 | 134 | erlup is NOT an official Erlang project. 135 |
136 | other installation options 137 |  ·  138 | about erlup 139 |  ·  140 | web design from rustup 141 |

142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { /* 1 */ 178 | overflow: visible; 179 | } 180 | 181 | /** 182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 183 | * 1. Remove the inheritance of text transform in Firefox. 184 | */ 185 | 186 | button, 187 | select { /* 1 */ 188 | text-transform: none; 189 | } 190 | 191 | /** 192 | * Correct the inability to style clickable types in iOS and Safari. 193 | */ 194 | 195 | button, 196 | [type="button"], 197 | [type="reset"], 198 | [type="submit"] { 199 | -webkit-appearance: button; 200 | } 201 | 202 | /** 203 | * Remove the inner border and padding in Firefox. 204 | */ 205 | 206 | button::-moz-focus-inner, 207 | [type="button"]::-moz-focus-inner, 208 | [type="reset"]::-moz-focus-inner, 209 | [type="submit"]::-moz-focus-inner { 210 | border-style: none; 211 | padding: 0; 212 | } 213 | 214 | /** 215 | * Restore the focus styles unset by the previous rule. 216 | */ 217 | 218 | button:-moz-focusring, 219 | [type="button"]:-moz-focusring, 220 | [type="reset"]:-moz-focusring, 221 | [type="submit"]:-moz-focusring { 222 | outline: 1px dotted ButtonText; 223 | } 224 | 225 | /** 226 | * Correct the padding in Firefox. 227 | */ 228 | 229 | fieldset { 230 | padding: 0.35em 0.75em 0.625em; 231 | } 232 | 233 | /** 234 | * 1. Correct the text wrapping in Edge and IE. 235 | * 2. Correct the color inheritance from `fieldset` elements in IE. 236 | * 3. Remove the padding so developers are not caught out when they zero out 237 | * `fieldset` elements in all browsers. 238 | */ 239 | 240 | legend { 241 | box-sizing: border-box; /* 1 */ 242 | color: inherit; /* 2 */ 243 | display: table; /* 1 */ 244 | max-width: 100%; /* 1 */ 245 | padding: 0; /* 3 */ 246 | white-space: normal; /* 1 */ 247 | } 248 | 249 | /** 250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 251 | */ 252 | 253 | progress { 254 | vertical-align: baseline; 255 | } 256 | 257 | /** 258 | * Remove the default vertical scrollbar in IE 10+. 259 | */ 260 | 261 | textarea { 262 | overflow: auto; 263 | } 264 | 265 | /** 266 | * 1. Add the correct box sizing in IE 10. 267 | * 2. Remove the padding in IE 10. 268 | */ 269 | 270 | [type="checkbox"], 271 | [type="radio"] { 272 | box-sizing: border-box; /* 1 */ 273 | padding: 0; /* 2 */ 274 | } 275 | 276 | /** 277 | * Correct the cursor style of increment and decrement buttons in Chrome. 278 | */ 279 | 280 | [type="number"]::-webkit-inner-spin-button, 281 | [type="number"]::-webkit-outer-spin-button { 282 | height: auto; 283 | } 284 | 285 | /** 286 | * 1. Correct the odd appearance in Chrome and Safari. 287 | * 2. Correct the outline style in Safari. 288 | */ 289 | 290 | [type="search"] { 291 | -webkit-appearance: textfield; /* 1 */ 292 | outline-offset: -2px; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | [type="search"]::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /** 304 | * 1. Correct the inability to style clickable types in iOS and Safari. 305 | * 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; /* 1 */ 310 | font: inherit; /* 2 */ 311 | } 312 | 313 | /* Interactive 314 | ========================================================================== */ 315 | 316 | /* 317 | * Add the correct display in Edge, IE 10+, and Firefox. 318 | */ 319 | 320 | details { 321 | display: block; 322 | } 323 | 324 | /* 325 | * Add the correct display in all browsers. 326 | */ 327 | 328 | summary { 329 | display: list-item; 330 | } 331 | 332 | /* Misc 333 | ========================================================================== */ 334 | 335 | /** 336 | * Add the correct display in IE 10+. 337 | */ 338 | 339 | template { 340 | display: none; 341 | } 342 | 343 | /** 344 | * Add the correct display in IE 10. 345 | */ 346 | 347 | [hidden] { 348 | display: none; 349 | } 350 | -------------------------------------------------------------------------------- /docs/website_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "Strict-Transport-Security": "max-age=63072000; includeSubDomains", 4 | "X-Content-Type-Options": "nosniff", 5 | "X-Frame-Options": "DENY", 6 | "X-XSS-Protection": "1; mode=block", 7 | "Referrer-Policy": "no-referrer, strict-origin-when-cross-origin", 8 | "Content-Security-Policy": "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' https://www.rust-lang.org; font-src 'self'" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsloughter/erlup/193d2977defdf82f2c13bb1c40e9ee185285e26e/image.png -------------------------------------------------------------------------------- /src/build.rs: -------------------------------------------------------------------------------- 1 | extern crate num_cpus; 2 | 3 | use console::{style, Emoji}; 4 | use glob::glob; 5 | use indicatif::{HumanDuration, ProgressBar, ProgressStyle}; 6 | use ini::Ini; 7 | use std::env; 8 | use std::fs::*; 9 | use std::os::unix::fs; 10 | use std::path::Path; 11 | use std::path::*; 12 | use std::process; 13 | use std::process::Command; 14 | use std::str; 15 | use std::time::Duration; 16 | use std::time::Instant; 17 | use tar::Archive; 18 | use tempdir::TempDir; 19 | 20 | use crate::config; 21 | 22 | // http://unicode.org/emoji/charts/full-emoji-list.html 23 | static CHECKMARK: Emoji = Emoji("✅", "✅ "); 24 | static FAIL: Emoji = Emoji("❌", "❌ "); 25 | static WARNING: Emoji = Emoji("🚫", "🚫"); 26 | 27 | pub const BINS: [&str; 11] = [ 28 | "bin/ct_run", 29 | "bin/dialyzer", 30 | "bin/epmd", 31 | "bin/erl", 32 | "bin/erlc", 33 | "bin/erl_call", 34 | "bin/escript", 35 | "bin/run_erl", 36 | "bin/run_test", 37 | "bin/to_erl", 38 | "bin/typer", 39 | ]; 40 | 41 | #[derive(Copy, Clone)] 42 | enum BuildResult { 43 | Success, 44 | Fail, 45 | } 46 | 47 | struct CheckContext<'a> { 48 | src_dir: &'a Path, 49 | install_dir: &'a Path, 50 | build_status: BuildResult, 51 | } 52 | 53 | enum CheckResult<'a> { 54 | Success, 55 | Warning(&'a str), 56 | Fail, 57 | } 58 | 59 | enum BuildStep<'a> { 60 | Exec(&'a str, Vec), 61 | Check(Box CheckResult<'a>>), 62 | } 63 | 64 | pub fn latest_tag(repo_dir: PathBuf) -> String { 65 | let output = Command::new("git") 66 | .args(["rev-list", "--tags", "--max-count=1"]) 67 | .current_dir(repo_dir.as_path()) 68 | .output() 69 | .unwrap_or_else(|e| { 70 | error!("git rev-list failed: {}", e); 71 | process::exit(1) 72 | }); 73 | 74 | if !output.status.success() { 75 | error!( 76 | "finding latest tag of {:?} failed: {}", 77 | repo_dir, 78 | String::from_utf8_lossy(&output.stderr) 79 | ); 80 | process::exit(1); 81 | } 82 | 83 | let rev = str::from_utf8(&output.stdout).unwrap(); 84 | let output = Command::new("git") 85 | .args(["describe", "--tags", (rev.trim())]) 86 | .current_dir(repo_dir.clone()) 87 | .output() 88 | .unwrap_or_else(|e| { 89 | error!("git describe failed: {}", e); 90 | process::exit(1) 91 | }); 92 | 93 | if !output.status.success() { 94 | error!( 95 | "describing latest tag of {:?} failed: {}", 96 | repo_dir, 97 | String::from_utf8_lossy(&output.stderr) 98 | ); 99 | process::exit(1); 100 | } 101 | 102 | String::from_utf8_lossy(&output.stdout).trim().to_string() 103 | } 104 | 105 | pub fn update_bins(bin_path: &Path, links_dir: &Path) { 106 | let _ = std::fs::create_dir_all(links_dir); 107 | for &b in BINS.iter() { 108 | let f = Path::new(b).file_name().unwrap(); 109 | let link = links_dir.join(f); 110 | debug!("linking {} to {}", link.display(), bin_path.display()); 111 | let _ = std::fs::remove_file(&link); 112 | let _ = fs::symlink(bin_path, link); 113 | } 114 | } 115 | 116 | pub fn tags(repo: String, config: Ini) { 117 | let git_repo = &config::lookup("repos", repo.to_string(), &config).unwrap(); 118 | let dir = &config::lookup_cache_dir(&config); 119 | let repo_dir = Path::new(dir).join("repos").join(repo); 120 | 121 | if !repo_dir.exists() { 122 | info!( 123 | "Cloning repo {} to {}", 124 | git_repo, 125 | repo_dir.to_str().unwrap() 126 | ); 127 | clone_repo(git_repo, repo_dir.to_owned()); 128 | } 129 | 130 | let output = Command::new("git") 131 | .args(["tag"]) 132 | .current_dir(repo_dir) 133 | .output() 134 | .unwrap_or_else(|e| { 135 | error!("git command failed: {}", e); 136 | process::exit(1) 137 | }); 138 | 139 | if !output.status.success() { 140 | error!("tag failed: {}", String::from_utf8_lossy(&output.stderr)); 141 | process::exit(1); 142 | } 143 | 144 | println!("{}", String::from_utf8_lossy(&output.stdout).trim()); 145 | } 146 | 147 | pub fn branches(repo: String, config: Ini) { 148 | let git_repo = &config::lookup("repos", repo.to_string(), &config).unwrap(); 149 | let dir = &config::lookup_cache_dir(&config); 150 | let repo_dir = Path::new(dir).join("repos").join(repo); 151 | 152 | if !repo_dir.exists() { 153 | info!( 154 | "Cloning repo {} to {}", 155 | git_repo, 156 | repo_dir.to_str().unwrap() 157 | ); 158 | clone_repo(git_repo, repo_dir.to_owned()); 159 | } 160 | 161 | let output = Command::new("git") 162 | .args(["branch"]) 163 | .current_dir(repo_dir) 164 | .output() 165 | .unwrap_or_else(|e| { 166 | error!("git command failed: {}", e); 167 | process::exit(1) 168 | }); 169 | 170 | if !output.status.success() { 171 | error!("tag failed: {}", String::from_utf8_lossy(&output.stderr)); 172 | process::exit(1); 173 | } 174 | 175 | println!("{}", String::from_utf8_lossy(&output.stdout).trim()); 176 | } 177 | 178 | pub fn fetch(maybe_repo: Option, config: Ini) { 179 | let repo = maybe_repo.unwrap_or("default".to_string()); 180 | let git_repo = &config::lookup("repos", repo.clone(), &config).unwrap_or_else(|| { 181 | error!("Repo {} not found in config", repo); 182 | process::exit(1) 183 | }); 184 | let dir = &config::lookup_cache_dir(&config); 185 | let repo_dir = Path::new(dir).join("repos").join(repo); 186 | 187 | let started = Instant::now(); 188 | let spinner_style = ProgressStyle::default_spinner() 189 | .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ") 190 | .template("{prefix:.bold.dim} {spinner} {wide_msg}") 191 | .unwrap(); 192 | 193 | let pb = ProgressBar::new_spinner(); 194 | pb.set_style(spinner_style); 195 | pb.enable_steady_tick(Duration::from_millis(100)); 196 | 197 | if !repo_dir.exists() { 198 | pb.set_message(format!( 199 | "Cloning repo {} to {}", 200 | git_repo, 201 | repo_dir.to_str().unwrap() 202 | )); 203 | clone_repo(git_repo, repo_dir.to_owned()); 204 | pb.println(format!( 205 | " {} Cloning repo {} to {:?}", 206 | CHECKMARK, git_repo, repo_dir 207 | )); 208 | } 209 | 210 | pb.set_message(format!("Fetching tags from {}", git_repo)); 211 | let output = Command::new("git") 212 | .args(["fetch"]) 213 | .current_dir(repo_dir) 214 | .output() 215 | .unwrap_or_else(|e| { 216 | error!("git fetch failed: {} {}", dir, e); 217 | process::exit(1) 218 | }); 219 | 220 | if !output.status.success() { 221 | error!("fetch failed: {}", String::from_utf8_lossy(&output.stderr)); 222 | process::exit(1); 223 | } 224 | 225 | pb.println(format!(" {} Fetching tags from {}", CHECKMARK, git_repo)); 226 | pb.finish_and_clear(); 227 | println!( 228 | "{} fetch in {}", 229 | style("Finished").green().bold(), 230 | HumanDuration(started.elapsed()) 231 | ); 232 | } 233 | 234 | fn clone_repo(git_repo: &str, repo_dir: std::path::PathBuf) { 235 | let _ = std::fs::create_dir_all(&repo_dir); 236 | let output = Command::new("git") 237 | .args(["clone", git_repo, "."]) 238 | .current_dir(&repo_dir) 239 | .output() 240 | .unwrap_or_else(|e| { 241 | error!("git clone failed: {:?} {}", repo_dir, e); 242 | process::exit(1) 243 | }); 244 | 245 | if !output.status.success() { 246 | error!("clone failed: {}", String::from_utf8_lossy(&output.stderr)); 247 | process::exit(1); 248 | } 249 | } 250 | 251 | #[allow(clippy::too_many_arguments)] 252 | pub fn run( 253 | bin_path: PathBuf, 254 | git_ref: String, 255 | id: String, 256 | repo: String, 257 | repo_url: String, 258 | force: bool, 259 | config_file: &str, 260 | config: Ini, 261 | ) { 262 | let dir = &config::lookup_cache_dir(&config); 263 | 264 | let key = "ERLUP_CONFIGURE_OPTIONS"; 265 | let empty_string = &"".to_string(); 266 | let user_configure_options = match env::var(key) { 267 | Ok(options) => options, 268 | _ => { 269 | config::lookup_with_default("erlup", "default_configure_options", empty_string, &config) 270 | .to_owned() 271 | } 272 | }; 273 | let links_dir = Path::new(dir).join("bin"); 274 | let repo_dir = Path::new(dir).join("repos").join(repo); 275 | 276 | let install_dir = Path::new(dir).join("otps").join(id.clone()); 277 | 278 | if !install_dir.exists() || force { 279 | debug!("building {}:", id); 280 | debug!(" repo url: {}", repo_url); 281 | debug!(" repo dir: {:?}", repo_dir); 282 | debug!(" install: {:?}", install_dir); 283 | debug!(" git_ref: {}", git_ref); 284 | debug!(" options: {}", user_configure_options); 285 | debug!(" force: {}", force); 286 | build( 287 | repo_url, 288 | repo_dir, 289 | install_dir.as_path(), 290 | git_ref, 291 | &user_configure_options, 292 | ); 293 | update_bins(bin_path.as_path(), links_dir.as_path()); 294 | 295 | // update config file with new built otp entry 296 | let dist = install_dir.join("dist"); 297 | config::update(id, dist.to_str().unwrap(), config_file); 298 | } else { 299 | error!("Directory for {} already exists: {:?}", id, install_dir); 300 | error!("If this is incorrect remove that directory,"); 301 | error!("provide a different id with --id or provide --force."); 302 | process::exit(1); 303 | } 304 | } 305 | 306 | pub fn delete(id: String, config_file: &str, config: Ini) { 307 | let dir = &config::lookup_cache_dir(&config); 308 | 309 | let install_dir = Path::new(dir).join("otps").join(id.clone()); 310 | let install_dir_str = install_dir.to_str().unwrap(); 311 | 312 | debug!("deleting {} at {}:", id, install_dir_str); 313 | 314 | // remove the entry from config 315 | config::delete(id, config_file); 316 | 317 | // delete the install dir from disk 318 | std::fs::remove_dir_all(install_dir_str).unwrap_or_else(|e| { 319 | error!("unable to delete {} due to {}", install_dir_str, e); 320 | process::exit(1); 321 | }); 322 | } 323 | 324 | fn run_git(args: Vec<&str>) { 325 | let output = Command::new("git") 326 | .args(&args) 327 | .output() 328 | .unwrap_or_else(|e| { 329 | error!("git command failed: {}", e); 330 | process::exit(1) 331 | }); 332 | 333 | if !output.status.success() { 334 | error!("clone failed: {}", String::from_utf8_lossy(&output.stderr)); 335 | process::exit(1); 336 | } 337 | } 338 | 339 | fn clone(repo: String, dest: &str) { 340 | run_git(vec!["clone", repo.as_str(), dest]); 341 | } 342 | 343 | fn checkout(dir: &Path, repo_dir: &Path, vsn: &str, pb: &ProgressBar) { 344 | let otp_tar = dir.join("otp.tar"); 345 | debug!("otp_tar={}", otp_tar.to_str().unwrap()); 346 | let output = Command::new("git") 347 | .args(["archive", "-o", otp_tar.to_str().unwrap(), vsn]) 348 | .current_dir(repo_dir) 349 | .output() 350 | .unwrap_or_else(|e| { 351 | error!("git archive failed: {}", e); 352 | process::exit(1) 353 | }); 354 | 355 | if !output.status.success() { 356 | pb.println(format!(" {} Checking out {}", FAIL, vsn)); 357 | error!( 358 | "checkout of {} failed: {}", 359 | vsn, 360 | String::from_utf8_lossy(&output.stderr) 361 | ); 362 | process::exit(1); 363 | } 364 | 365 | let mut ar = Archive::new(File::open(otp_tar).unwrap()); 366 | ar.unpack(dir).unwrap(); 367 | } 368 | 369 | fn setup_links(install_dir: &Path) { 370 | for &b in BINS.iter() { 371 | let f = Path::new(b).file_name().unwrap(); 372 | let bin = install_dir.join("dist").join(b); 373 | let paths = glob(bin.to_str().unwrap()).unwrap(); 374 | 375 | match paths.last() { 376 | Some(x) => { 377 | let link = install_dir.join(f); 378 | let _ = fs::symlink(x.unwrap().to_str().unwrap(), link); 379 | } 380 | None => debug!("file to link not found: {}", f.to_str().unwrap()), 381 | } 382 | } 383 | } 384 | 385 | pub fn build( 386 | repo_url: String, 387 | repo_dir: PathBuf, 388 | install_dir: &Path, 389 | vsn: String, 390 | user_configure_options0: &str, 391 | ) { 392 | if !repo_dir.is_dir() { 393 | clone(repo_url, repo_dir.as_os_str().to_str().unwrap()); 394 | } 395 | 396 | let started = Instant::now(); 397 | let spinner_style = ProgressStyle::default_spinner() 398 | .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ") 399 | .template("{prefix:.bold.dim} {spinner} {wide_msg}") 400 | .unwrap(); 401 | 402 | let pb = ProgressBar::new_spinner(); 403 | pb.set_style(spinner_style); 404 | pb.enable_steady_tick(Duration::from_millis(100)); 405 | 406 | match TempDir::new("erlup") { 407 | Ok(dir) => { 408 | let num_cpus = num_cpus::get().to_string(); 409 | 410 | pb.set_message(format!("Checking out {}", vsn)); 411 | 412 | checkout(dir.path(), &repo_dir, &vsn, &pb); 413 | let _ = std::fs::create_dir_all(repo_dir); 414 | let _ = std::fs::create_dir_all(install_dir); 415 | 416 | pb.println(format!( 417 | " {} Checking out {} (done in {})", 418 | CHECKMARK, 419 | vsn, 420 | HumanDuration(started.elapsed()) 421 | )); 422 | debug!("temp dir: {:?}", dir.path()); 423 | 424 | let dist_dir = install_dir.join("dist"); 425 | 426 | // split the configure options into a vector of String in a shell sensitive way 427 | // eg. 428 | // from: 429 | // user_configure_options0: --without-wx --without-observer --without-odbc --without-debugger --without-et --enable-builtin-zlib --without-javac CFLAGS="-g -O2 -march=native" 430 | // to: 431 | // user_configure_options: ["--without-wx", "--without-observer", "--without-odbc", "--without-debugger", "--without-et", "--enable-builtin-zlib", "--without-javac", "CFLAGS=-g -O2 -march=native"] 432 | let mut user_configure_options: Vec = 433 | shell_words::split(user_configure_options0).unwrap_or_else(|e| { 434 | error!("bad configure options {}\n\t{}", user_configure_options0, e); 435 | process::exit(1); 436 | }); 437 | // basic configure options must always include a prefix 438 | let mut configure_options = vec![ 439 | "--prefix".to_string(), 440 | dist_dir.to_str().unwrap().to_string(), 441 | ]; 442 | // append the user defined options 443 | configure_options.append(&mut user_configure_options); 444 | 445 | // declare the build pipeline steps 446 | let build_steps: [BuildStep; 8] = [ 447 | BuildStep::Exec("./otp_build", vec!["autoconf".to_string()]), 448 | BuildStep::Exec("./configure", configure_options), 449 | BuildStep::Check(Box::new(|context| { 450 | if has_openssl(context.src_dir) { 451 | CheckResult::Success 452 | } else { 453 | CheckResult::Warning("No usable OpenSSL found, please specify one with --with-ssl configure option, `crypto` application will not work in current build") 454 | } 455 | })), 456 | BuildStep::Exec("make", vec!["-j".to_string(), num_cpus.to_string()]), 457 | BuildStep::Exec( 458 | "make", 459 | vec![ 460 | "-j".to_string(), 461 | num_cpus.to_string(), 462 | "docs".to_string(), 463 | "DOC_TARGETS=chunks".to_string(), 464 | ], 465 | ), 466 | // after `make` we'll already know if this build failed or not, this allows us 467 | // to make a better decision in wether to delete the installation dir should there 468 | // be one. 469 | BuildStep::Check(Box::new(|context| { 470 | match context.build_status { 471 | BuildResult::Fail => { 472 | debug!("build has failed, aborting install to prevent overwriting a possibly working installation dir"); 473 | // this build has failed, we won't touch the previously existing install 474 | // dir, for all we know it could hold a previously working installation 475 | CheckResult::Fail 476 | } 477 | // if the build succeeded, then we check for an already existing 478 | // install dir, if we find one we can delete it and proceed to the 479 | // install phase 480 | BuildResult::Success => { 481 | // is install dir empty? courtesy of StackOverflow 482 | let is_empty = context 483 | .install_dir 484 | .read_dir() 485 | .map(|mut i| i.next().is_none()) 486 | .unwrap_or(false); 487 | if is_empty { 488 | // it's fine, it was probably us who created the dir just a moment ago, 489 | // that's why it's empty 490 | CheckResult::Success 491 | } else { 492 | debug!("found a non empty installation dir after a successful build, removing it"); 493 | // dir is not empty, maybe a working installation is already there, 494 | // delete the whole thing and proceed, we can go ahead with this 495 | // because we know we have a working build in our hands 496 | let _ = std::fs::remove_dir_all(context.install_dir); 497 | CheckResult::Success 498 | } 499 | } 500 | } 501 | })), 502 | BuildStep::Exec( 503 | "make", 504 | vec![ 505 | "-j".to_string(), 506 | num_cpus.to_string(), 507 | "install".to_string(), 508 | ], 509 | ), 510 | BuildStep::Exec( 511 | "make", 512 | vec![ 513 | "-j".to_string(), 514 | num_cpus.to_string(), 515 | "install-docs".to_string(), 516 | ], 517 | ), 518 | ]; 519 | // execute them sequentially 520 | let mut build_status = BuildResult::Success; 521 | for step in build_steps.iter() { 522 | let step_started = Instant::now(); 523 | 524 | match step { 525 | BuildStep::Exec(command, args) => { 526 | // it only takes one exec command to fail for the build status 527 | // to be fail as well, a subsequent check build step can optionally decide 528 | // to fail the pipeline 529 | if let BuildResult::Fail = 530 | exec(command, args, dir.path(), step_started, &pb) 531 | { 532 | build_status = BuildResult::Fail; 533 | } 534 | } 535 | BuildStep::Check(fun) => { 536 | let context = CheckContext { 537 | src_dir: dir.path(), 538 | install_dir, 539 | build_status, 540 | }; 541 | match fun(&context) { 542 | CheckResult::Success => { 543 | debug!("success"); 544 | } 545 | CheckResult::Warning(warning) => { 546 | debug!("{}", warning); 547 | pb.set_message(warning); 548 | pb.println(format!(" {} {}", WARNING, warning)); 549 | } 550 | CheckResult::Fail => { 551 | // abort 552 | pb.finish_and_clear(); 553 | std::process::exit(1); 554 | } 555 | } 556 | } 557 | } 558 | } 559 | // By closing the `TempDir` explicitly, we can check that it has 560 | // been deleted successfully. If we don't close it explicitly, 561 | // the directory will still be deleted when `tmp_dir` goes out 562 | // of scope, but we won't know whether deleting the directory 563 | // succeeded. 564 | drop(dir); 565 | } 566 | Err(e) => { 567 | error!("failed creating temp directory for build: {}", e); 568 | } 569 | } 570 | 571 | pb.set_message("Setting up symlinks"); 572 | setup_links(install_dir); 573 | pb.println(format!(" {} {}", CHECKMARK, "Setting up symlinks")); 574 | 575 | pb.finish_and_clear(); 576 | println!( 577 | "{} build in {}", 578 | style("Finished").green().bold(), 579 | HumanDuration(started.elapsed()) 580 | ); 581 | } 582 | 583 | fn exec( 584 | command: &str, 585 | args: &Vec, 586 | dir: &Path, 587 | started_ts: Instant, 588 | pb: &ProgressBar, 589 | ) -> BuildResult { 590 | debug!("Running {} {:?}", command, args); 591 | pb.set_message(format!("{} {}", command, args.join(" "))); 592 | let output = Command::new(command) 593 | .args(args) 594 | .current_dir(dir) 595 | .output() 596 | .unwrap_or_else(|e| { 597 | pb.println(format!(" {} {} {}", FAIL, command, args.join(" "))); 598 | error!("build failed: {}", e); 599 | process::exit(1) 600 | }); 601 | 602 | debug!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 603 | debug!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 604 | 605 | match output.status.success() { 606 | true => { 607 | pb.println(format!( 608 | " {} {} {} (done in {})", 609 | CHECKMARK, 610 | command, 611 | args.join(" "), 612 | HumanDuration(started_ts.elapsed()) 613 | )); 614 | BuildResult::Success 615 | } 616 | false => { 617 | error!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 618 | pb.println(format!(" {} {} {}", FAIL, command, args.join(" "))); 619 | BuildResult::Fail 620 | } 621 | } 622 | } 623 | 624 | fn has_openssl(src_dir: &Path) -> bool { 625 | // check that lib/crypto/SKIP doesn't exist, 626 | // if it does it means something went wrong with OpenSSL 627 | !src_dir.join("./lib/crypto/SKIP").exists() 628 | } 629 | -------------------------------------------------------------------------------- /src/cli.yml: -------------------------------------------------------------------------------- 1 | name: erlup 2 | version: 0.1.0 3 | author: Tristan Sloughter 4 | about: Manage Erlang installs. 5 | # args: 6 | # - config: 7 | # short: c 8 | # long: config 9 | # value_name: FILE 10 | # help: Sets a custom config file 11 | # takes_value: true 12 | subcommands: 13 | # - update_links: 14 | # about: Update binary symlinks to erlup executable 15 | # - fetch: 16 | # about: Fetch latest tags for repo 17 | # args: 18 | # - repo: 19 | # short: r 20 | # long: repo 21 | # value_name: REPO 22 | # help: Which Erlang repo to fetch tags for 23 | # takes_value: true 24 | # - tags: 25 | # about: List available tags to build for a repo 26 | # args: 27 | # - repo: 28 | # short: r 29 | # long: repo 30 | # value_name: REPO 31 | # help: Which Erlang repo to list tags for 32 | # takes_value: true 33 | # - branches: 34 | # about: List available branches to build for a repo 35 | # args: 36 | # - repo: 37 | # short: r 38 | # long: repo 39 | # value_name: REPO 40 | # help: Which Erlang repo to list branches for 41 | # takes_value: true 42 | 43 | # - switch: 44 | # about: Switch Erlang versions 45 | # args: 46 | # - ID: 47 | # help: Sets the Erlang to use by id 48 | # required: true 49 | # index: 1 50 | # - default: 51 | # about: Switch Erlang versions 52 | # args: 53 | # - ID: 54 | # help: Sets the default Erlang to use by id 55 | # required: true 56 | # index: 1 57 | - repo: 58 | about: Update repos to the config 59 | args: 60 | - CMD: 61 | value_name: CMD 62 | help: Repo command to run 63 | takes_value: true 64 | index: 1 65 | - NAME: 66 | value_name: NAME 67 | help: Name of the repo to use in the config 68 | takes_value: true 69 | index: 2 70 | - REPO: 71 | value_name: REPO 72 | help: URL of Erlang repo to add 73 | takes_value: true 74 | index: 3 75 | # - build: 76 | # about: Build an Erlang version 77 | # args: 78 | # - VSN: 79 | # help: Version of Erlang to build 80 | # required: true 81 | # index: 1 82 | # - id: 83 | # short: i 84 | # long: id 85 | # value_name: ID 86 | # help: Name to give the Erlang build 87 | # takes_value: true 88 | # - repo: 89 | # short: r 90 | # long: repo 91 | # value_name: REPO 92 | # help: Which Erlang repo to use 93 | # takes_value: true 94 | # - force: 95 | # short: f 96 | # long: force 97 | # help: Forces a build disregarding any previously existing ones 98 | # - delete: 99 | # about: Deletes an Erlang version 100 | # args: 101 | # - id: 102 | # short: i 103 | # long: id 104 | # value_name: ID 105 | # help: Name of the Erlang build 106 | # takes_value: true 107 | # - list: 108 | # about: List installed Erlang versions 109 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use ini::Ini; 2 | use std::fs::*; 3 | use std::path::*; 4 | use std::process; 5 | 6 | fn home_config_file() -> String { 7 | let config_dir = match dirs::config_dir() { 8 | Some(d) => d, 9 | None => { 10 | error!("no home directory available"); 11 | process::exit(1) 12 | } 13 | }; 14 | let cache_dir = match dirs::cache_dir() { 15 | Some(d) => d, 16 | None => { 17 | error!("no home directory available"); 18 | process::exit(1) 19 | } 20 | }; 21 | 22 | let default_config = config_dir.join("erlup").join("config"); 23 | let default_cache = cache_dir.join("erlup"); 24 | 25 | let _ = create_dir_all(config_dir.join("erlup")); 26 | let _ = create_dir_all(cache_dir.join("erlup")); 27 | 28 | if !default_config.exists() { 29 | let mut conf = Ini::new(); 30 | conf.with_section(Some("erlup".to_owned())) 31 | .set("dir", default_cache.to_str().unwrap()); 32 | conf.with_section(Some("repos".to_owned())) 33 | .set("default", "https://github.com/erlang/otp"); 34 | conf.write_to_file(&default_config).unwrap(); 35 | info!( 36 | "Created a default config at {:?}", 37 | default_config.to_owned() 38 | ); 39 | } 40 | 41 | default_config.to_str().unwrap().to_string() 42 | } 43 | 44 | pub fn home_config() -> (String, Ini) { 45 | let config_file = home_config_file(); 46 | (config_file.to_owned(), read_config(config_file)) 47 | } 48 | 49 | pub fn list() { 50 | let (_, config) = home_config(); 51 | if let Some(erlup) = config.section(Some("erlangs")) { 52 | for s in erlup { 53 | let (k, v) = s; 54 | println!("{} -> {}", k, v); 55 | } 56 | } else { 57 | println!("No Erlang releases installed."); 58 | } 59 | } 60 | 61 | pub fn erl_to_use() -> String { 62 | let (_, config) = home_config(); 63 | 64 | let erl_to_use = match Ini::load_from_file("erlup.config") { 65 | Ok(cwd_config) => { 66 | debug!("Found ./erlup.config"); 67 | match lookup("config", "erlang".to_string(), &cwd_config) { 68 | Some(entry) => entry.clone(), 69 | None => { 70 | error!("No Erlang entry found in erlup.config"); 71 | error!("Delete or update the config file"); 72 | process::exit(1) 73 | } 74 | } 75 | } 76 | Err(_) => { 77 | debug!("No ./erlup.config found, going to default"); 78 | match lookup("erlup", "default".to_string(), &config) { 79 | Some(entry) => entry.clone(), 80 | None => { 81 | error!("No default Erlang set. Use `erlup default `"); 82 | process::exit(1) 83 | } 84 | } 85 | } 86 | }; 87 | 88 | debug!("Using Erlang with id {}", erl_to_use); 89 | match lookup("erlangs", erl_to_use.to_string(), &config) { 90 | Some(erl) => erl.clone(), 91 | None => { 92 | error!( 93 | "No directory found for Erlang with id {} in config", 94 | erl_to_use 95 | ); 96 | process::exit(1) 97 | } 98 | } 99 | } 100 | 101 | pub fn read_config(config_file: String) -> Ini { 102 | match Ini::load_from_file(config_file) { 103 | Ok(ini) => ini, 104 | Err(_) => { 105 | let (_, ini) = home_config(); 106 | ini 107 | } 108 | } 109 | } 110 | 111 | pub fn lookup_cache_dir(conf: &Ini) -> &str { 112 | let error_message = "The config file ~/.config/erlup/config is missing erlup.dir setting used for storing repos and built Erlang versions"; 113 | lookup_or_exit("erlup", "dir", error_message, conf) 114 | } 115 | 116 | pub fn lookup(section: &str, key: String, conf: &Ini) -> Option { 117 | debug!("reading section '{}' key '{}'", section, key); 118 | match conf.section(Some(section)) { 119 | Some(section) => section.get(key.as_str()).map(str::to_string), 120 | None => None, 121 | } 122 | } 123 | 124 | pub fn lookup_or_exit<'a>(section: &str, key: &str, msg: &str, conf: &'a Ini) -> &'a str { 125 | debug!("reading section '{}' key '{}'", section, key); 126 | let section = conf.section(Some(section)).unwrap(); 127 | match section.get(key) { 128 | Some(v) => v, 129 | None => { 130 | error!("{}", msg); 131 | process::exit(1) 132 | } 133 | } 134 | } 135 | 136 | pub fn lookup_with_default<'a>( 137 | section: &str, 138 | key: &str, 139 | default: &'a str, 140 | conf: &'a Ini, 141 | ) -> &'a str { 142 | debug!("reading section '{}' key '{}'", section, key); 143 | let section = conf.section(Some(section)).unwrap(); 144 | match section.get(key) { 145 | Some(v) => v, 146 | None => default, 147 | } 148 | } 149 | 150 | pub fn update(id: String, dir: &str, config_file: &str) { 151 | let mut config = Ini::load_from_file(config_file).unwrap(); 152 | config.with_section(Some("erlangs".to_owned())).set(id, dir); 153 | config.write_to_file(config_file).unwrap(); 154 | } 155 | 156 | pub fn delete(id: String, config_file: &str) { 157 | let mut config = Ini::load_from_file(config_file).unwrap(); 158 | config.with_section(Some("erlangs".to_owned())).delete(&id); 159 | config.write_to_file(config_file).unwrap(); 160 | } 161 | 162 | pub fn switch(id: &str) { 163 | let (_, config) = home_config(); 164 | match lookup("erlangs", id.to_string(), &config) { 165 | Some(_) => { 166 | let cwd_config = Path::new("erlup.config"); 167 | { 168 | let _ = File::create(cwd_config); 169 | } 170 | let mut mut_config = Ini::load_from_file("erlup.config").unwrap(); 171 | mut_config 172 | .with_section(Some("config".to_owned())) 173 | .set("erlang", id); 174 | mut_config.write_to_file("erlup.config").unwrap(); 175 | info!("Switched Erlang used in this directory to {}", id); 176 | info!("Wrote setting to file {}", "./erlup.config"); 177 | } 178 | None => { 179 | error!("{} is not a configured Erlang install", id); 180 | process::exit(1) 181 | } 182 | } 183 | } 184 | 185 | pub fn add_repo(repo_id: &str, repo_url: &str, config_file: &str, mut config: Ini) { 186 | config 187 | .with_section(Some("repos".to_owned())) 188 | .set(repo_id, repo_url); 189 | config.write_to_file(config_file).unwrap(); 190 | } 191 | 192 | pub fn get_repos(config: &Ini) -> Vec<(&str, &str)> { 193 | match config.section(Some("repos")) { 194 | Some(section) => section.iter().collect::>(), 195 | None => vec![], 196 | } 197 | } 198 | 199 | pub fn delete_repo(repo_id: &String, config_file: &str, mut config: Ini) { 200 | config 201 | .with_section(Some("repos".to_owned())) 202 | .delete(&repo_id); 203 | config.write_to_file(config_file).unwrap(); 204 | } 205 | 206 | pub fn set_default(id: &str) { 207 | let (_, mut config) = home_config(); 208 | match lookup("erlangs", id.to_string(), &config) { 209 | Some(_) => { 210 | config 211 | .with_section(Some("erlup".to_owned())) 212 | .set("default", id); 213 | let config_file = home_config_file(); 214 | config.write_to_file(config_file).unwrap(); 215 | info!("Default Erlang now {}", id); 216 | } 217 | None => { 218 | error!( 219 | "{} is not a configured Erlang install, can't set it to default", 220 | id 221 | ); 222 | process::exit(1) 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/erl.rs: -------------------------------------------------------------------------------- 1 | use std::path::*; 2 | use std::env::Args; 3 | use std::process::Command; 4 | use std::os::unix::prelude::CommandExt; 5 | 6 | use crate::config; 7 | 8 | pub fn run(bin: &str, args: Args) { 9 | // no -c argument available in this case 10 | let erl_dir = config::erl_to_use(); 11 | let cmd = Path::new(&erl_dir).join("bin").join(bin); 12 | 13 | debug!("running {}", cmd.to_str().unwrap()); 14 | 15 | let _ = Command::new(cmd.to_str().unwrap()).args(args).exec(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | use clap::{Args, Parser, Subcommand}; 7 | use console::style; 8 | use log::{Level, LevelFilter, Record}; 9 | use std::env; 10 | use std::io::Write; 11 | use std::path::*; 12 | use std::process; 13 | 14 | mod build; 15 | mod config; 16 | mod erl; 17 | 18 | #[derive(Parser)] 19 | #[command(version, about, long_about = None)] 20 | #[command(propagate_version = true)] 21 | struct Cli { 22 | #[arg(short, long)] 23 | config: Option, 24 | 25 | #[command(subcommand)] 26 | subcommand: SubCommands, 27 | } 28 | 29 | #[derive(Subcommand)] 30 | enum SubCommands { 31 | /// Update binary symlinks to erlup executable 32 | UpdateLinks, 33 | 34 | /// List installed Erlangs 35 | List, 36 | 37 | /// Fetch latest tags for repo 38 | Fetch(RepoArgs), 39 | 40 | /// List available tags to build for a repo 41 | Tags(RepoArgs), 42 | 43 | /// List available branches to build for a repo 44 | Branches(RepoArgs), 45 | 46 | /// Switch Erlang to use by id 47 | Switch(IdArgs), 48 | 49 | /// Set default Erlang to use by id 50 | Default(IdArgs), 51 | 52 | /// Deletes an Erlang by id 53 | Delete(IdArgs), 54 | 55 | /// Build and Erlang by branch of tag name 56 | Build(BuildArgs), 57 | 58 | /// Update repos to the config 59 | Repo(RepoSubCommands), 60 | } 61 | 62 | #[derive(Args)] 63 | struct RepoArgs { 64 | /// Which Erlang repo to use for command 65 | #[arg(short, long)] 66 | repo: Option, 67 | } 68 | 69 | #[derive(Args)] 70 | struct IdArgs { 71 | /// Id of the Erlang 72 | id: String, 73 | } 74 | 75 | #[derive(Args)] 76 | struct BuildArgs { 77 | /// Branch of tag of the Erlang repo 78 | git_ref: String, 79 | 80 | /// Id to give the Erlang build 81 | #[arg(short, long)] 82 | id: Option, 83 | 84 | /// Which Erlang repo to use for command 85 | #[arg(short, long)] 86 | repo: Option, 87 | 88 | /// Forces a build disregarding any previously existing ones 89 | #[arg(short, long)] 90 | force: Option, 91 | } 92 | 93 | #[derive(Args)] 94 | struct RepoSubCommands { 95 | #[command(subcommand)] 96 | cmd: RepoCmds, 97 | } 98 | 99 | #[derive(Subcommand)] 100 | enum RepoCmds { 101 | /// Add repo to the configuration 102 | Add(RepoAddArgs), 103 | 104 | /// Remove repo from the configuration 105 | Rm(RepoRmArgs), 106 | 107 | /// List available repos 108 | Ls, 109 | } 110 | 111 | #[derive(Args)] 112 | struct RepoAddArgs { 113 | /// Name of the repo to add 114 | name: String, 115 | 116 | /// Url of the git repo for the repo 117 | repo: String, 118 | } 119 | 120 | #[derive(Args)] 121 | struct RepoRmArgs { 122 | /// Name of the repo to remove 123 | name: String, 124 | } 125 | 126 | fn repo_or_default(maybe_repo: Option) -> String { 127 | match maybe_repo { 128 | Some(repo) => repo, 129 | None => "default".to_string(), 130 | } 131 | } 132 | 133 | fn handle_command(bin_path: PathBuf) { 134 | let cli = Cli::parse(); 135 | 136 | let (config_file, config) = match &cli.config { 137 | Some(file) => (file.to_owned(), config::read_config(file.to_owned())), 138 | None => config::home_config(), 139 | }; 140 | debug!("config_file: {}", config_file); 141 | 142 | match &cli.subcommand { 143 | SubCommands::UpdateLinks => { 144 | debug!("running update links"); 145 | let dir = &config::lookup_cache_dir(&config); 146 | let links_dir = Path::new(dir).join("bin"); 147 | build::update_bins(bin_path.as_path(), links_dir.as_path()); 148 | } 149 | SubCommands::List => { 150 | debug!("running list"); 151 | config::list(); 152 | } 153 | SubCommands::Fetch(RepoArgs { repo }) => { 154 | debug!("running fetch: repo={:?}", repo); 155 | build::fetch(repo.clone(), config); 156 | } 157 | SubCommands::Tags(RepoArgs { repo }) => { 158 | debug!("running list tags: repo={:?}", repo); 159 | build::tags(repo_or_default(repo.clone()), config); 160 | } 161 | SubCommands::Branches(RepoArgs { repo }) => { 162 | debug!("running list branches: repo={:?}", repo); 163 | build::branches(repo_or_default(repo.clone()), config); 164 | } 165 | SubCommands::Switch(IdArgs { id }) => { 166 | debug!("running switch: id={}", id); 167 | config::switch(id.as_str()); 168 | } 169 | SubCommands::Default(IdArgs { id }) => { 170 | debug!("running default: id={}", id); 171 | config::set_default(id.as_str()); 172 | } 173 | SubCommands::Delete(IdArgs { id }) => { 174 | debug!("running delete: id={}", id); 175 | build::delete(id.clone(), &config_file, config); 176 | } 177 | SubCommands::Build(BuildArgs { 178 | git_ref, 179 | id, 180 | repo, 181 | force, 182 | }) => { 183 | debug!("running build: {} {:?} {:?} {:?}", git_ref, id, repo, force); 184 | 185 | let repo = repo_or_default(repo.clone()); 186 | let repo_url = &config::lookup("repos", repo.clone(), &config).unwrap_or_else(|| { 187 | error!( 188 | "Repo {} not found in config.\nTo add a repo: erlup repo add ", 189 | repo 190 | ); 191 | process::exit(1) 192 | }); 193 | 194 | let dir = &config::lookup_cache_dir(&config); 195 | let repo_dir = Path::new(dir).join("repos").join(repo.clone()); 196 | 197 | let git_ref = match git_ref.as_str() { 198 | "latest" => build::latest_tag(repo_dir), 199 | _ => git_ref.clone(), 200 | }; 201 | 202 | let id = id.clone().unwrap_or(git_ref.clone()); 203 | let force = match force { 204 | Some(f) => *f, 205 | None => false, 206 | }; 207 | build::run( 208 | bin_path, 209 | git_ref, 210 | id, 211 | repo, 212 | repo_url.clone(), 213 | force, 214 | &config_file, 215 | config, 216 | ); 217 | } 218 | SubCommands::Repo(repo_sub_cmd) => match &repo_sub_cmd.cmd { 219 | RepoCmds::Add(RepoAddArgs { name, repo }) => { 220 | debug!("running repo add: name={} repo={}", name, repo); 221 | config::add_repo(name, repo, &config_file, config); 222 | } 223 | RepoCmds::Rm(RepoRmArgs { name }) => { 224 | debug!("running repo rm: name={}", name); 225 | config::delete_repo(name, &config_file, config); 226 | } 227 | RepoCmds::Ls => { 228 | debug!("running repo ls"); 229 | let repos = config::get_repos(&config); 230 | for (id, url) in repos { 231 | println!("{} -> {}", id, url); 232 | } 233 | } 234 | }, 235 | } 236 | } 237 | 238 | fn setup_logging() { 239 | let format = |buf: &mut env_logger::fmt::Formatter, record: &Record| { 240 | if record.level() == Level::Error { 241 | writeln!(buf, "{}", style(format!("{}", record.args())).red()) 242 | } else if record.level() == Level::Info { 243 | writeln!(buf, "{}", record.args()) 244 | } else { 245 | writeln!(buf, "{}", style(format!("{}", record.args())).blue()) 246 | } 247 | }; 248 | 249 | let key = "DEBUG"; 250 | let level = match env::var(key) { 251 | Ok(_) => LevelFilter::Debug, 252 | _ => LevelFilter::Info, 253 | }; 254 | 255 | env_logger::builder() 256 | .format(format) 257 | .filter(None, level) 258 | .init(); 259 | } 260 | 261 | fn main() { 262 | setup_logging(); 263 | 264 | let mut args = env::args(); 265 | let binname = args.next().unwrap(); 266 | let f = Path::new(&binname).file_name().unwrap(); 267 | 268 | if f.eq("erlup") { 269 | match env::current_exe() { 270 | Ok(bin_path) => { 271 | debug!("current bin path: {}", bin_path.display()); 272 | handle_command(bin_path) 273 | } 274 | Err(e) => { 275 | println!("failed to get current bin path: {}", e); 276 | process::exit(1) 277 | } 278 | } 279 | } else { 280 | match build::BINS 281 | .iter() 282 | .find(|&&x| f.eq(Path::new(x).file_name().unwrap())) 283 | { 284 | Some(x) => { 285 | let bin = Path::new(x).file_name().unwrap(); 286 | erl::run(bin.to_str().unwrap(), args); 287 | } 288 | None => { 289 | error!("No such command: {}", f.to_str().unwrap()); 290 | process::exit(1) 291 | } 292 | } 293 | } 294 | } 295 | --------------------------------------------------------------------------------