diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..3349ad2 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,115 @@ +# Run FFT benchmarks on an AWS instance and return parsed results to Slab CI bot. +name: FFT benchmarks + +on: + workflow_dispatch: + inputs: + instance_id: + description: 'Instance ID' + type: string + instance_image_id: + description: 'Instance AMI ID' + type: string + instance_type: + description: 'Instance product type' + type: string + runner_name: + description: 'Action runner name' + type: string + request_id: + description: 'Slab request ID' + type: string + +env: + CARGO_TERM_COLOR: always + RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json + +jobs: + run-fft-benchmarks: + name: Execute FFT benchmarks in EC2 + runs-on: ${{ github.event.inputs.runner_name }} + if: ${{ !cancelled() }} + steps: + - name: Instance configuration used + run: | + echo "IDs: ${{ inputs.instance_id }}" + echo "AMI: ${{ inputs.instance_image_id }}" + echo "Type: ${{ inputs.instance_type }}" + echo "Request ID: ${{ inputs.request_id }}" + + - name: Get benchmark date + run: | + echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}" + + - name: Checkout tfhe-rs repo with tags + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + with: + fetch-depth: 0 + + - name: Set up home + # "Install rust" step require root user to have a HOME directory which is not set. + run: | + echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}" + + - name: Install rust + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af + with: + toolchain: nightly + override: true + + - name: Run benchmarks + run: | + make bench + + - name: Parse results + run: | + COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})" + COMMIT_HASH="$(git describe --tags --dirty)" + python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \ + --database tfhe_rs_benchmarks \ + --hardware ${{ inputs.instance_type }} \ + --project-version "${COMMIT_HASH}" \ + --branch ${{ github.ref_name }} \ + --commit-date "${COMMIT_DATE}" \ + --bench-date "${{ env.BENCH_DATE }}" + + - name: Remove previous raw results + run: | + rm -rf target/criterion + + - name: Run benchmarks with AVX512 + run: | + make AVX512_SUPPORT=ON bench + + - name: Parse AVX512 results + run: | + python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \ + --name-suffix avx512 \ + --append-results + + - name: Upload parsed results artifact + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce + with: + name: ${{ github.sha }}_fft + path: ${{ env.RESULTS_FILENAME }} + + - name: Checkout Slab repo + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + with: + repository: zama-ai/slab + path: slab + token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }} + + - name: Send data to Slab + shell: bash + run: | + echo "Computing HMac on downloaded artifact" + SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')" + echo "Sending results to Slab..." + curl -v -k \ + -H "Content-Type: application/json" \ + -H "X-Slab-Repository: ${{ github.repository }}" \ + -H "X-Slab-Command: store_data" \ + -H "X-Hub-Signature-256: sha256=${SIGNATURE}" \ + -d @${{ env.RESULTS_FILENAME }} \ + ${{ secrets.SLAB_URL }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 5e6dec7..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Test - -on: - push: - -env: - CARGO_TERM_COLOR: always - -jobs: - cargo-benches: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - name: Install FFTW - run: sudo apt install -y libfftw3-dev - - name: Compile benches - run: cargo bench --no-run - - cargo-tests: - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - name: Test debug - run: cargo test - - name: Test debug serialization - run: cargo test --features=serde - - name: Test debug no-std - run: cargo test --no-default-features - - cargo-tests-nightly: - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - - steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - - name: Test debug nightly - run: cargo test --features=nightly diff --git a/.github/workflows/cargo_build.yml b/.github/workflows/cargo_build.yml new file mode 100644 index 0000000..c14977f --- /dev/null +++ b/.github/workflows/cargo_build.yml @@ -0,0 +1,45 @@ +# Build concrete-fft +name: Cargo Build + +on: + pull_request: + +env: + CARGO_TERM_COLOR: always + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + cargo-builds: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + fail-fast: false + + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + + - name: Get rust toolchain to use for checks and lints + id: toolchain + run: | + echo "rs-toolchain=$(make rs_toolchain)" >> "${GITHUB_OUTPUT}" + + - name: Run pcc checks + run: | + make pcc + + - name: Build release + run: | + make build + + - name: Build release no-std + run: | + make build_no_std + + - name: Build benchmarks + run: | + make build_bench diff --git a/.github/workflows/cargo_test.yml b/.github/workflows/cargo_test.yml new file mode 100644 index 0000000..1783bcb --- /dev/null +++ b/.github/workflows/cargo_test.yml @@ -0,0 +1,56 @@ +# Test concrete-fft +name: Cargo Test + +on: + push: + +env: + CARGO_TERM_COLOR: always + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + cargo-tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + + - name: Install Rust + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af + with: + toolchain: stable + override: true + + - name: Test debug + run: make test + + - name: Test serialization + run: make test_serde + + - name: Test no-std + run: make test_no_std + + cargo-tests-nightly: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + + - name: Install Rust + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af + with: + toolchain: nightly + override: true + + - name: Test nightly + run: make test_nightly + + - name: Test no-std nightly + run: make test_no_std_nightly diff --git a/.github/workflows/check_commit.yml b/.github/workflows/check_commit.yml new file mode 100644 index 0000000..516f5b9 --- /dev/null +++ b/.github/workflows/check_commit.yml @@ -0,0 +1,32 @@ +# Check commit and PR compliance +name: Check commit and PR compliance + +on: + pull_request: + +jobs: + check-commit-pr: + name: Check commit and PR + runs-on: ubuntu-latest + steps: + - name: Check first line + uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee + with: + pattern: '^((feat|fix|chore|refactor|style|test|docs|doc)\(\w+\)\:) .+$' + flags: "gs" + error: 'Your first line has to contain a commit type and scope like "feat(my_feature): msg".' + excludeDescription: "true" # optional: this excludes the description body of a pull request + excludeTitle: "true" # optional: this excludes the title of a pull request + checkAllCommitMessages: "true" # optional: this checks all commits associated with a pull request + accessToken: ${{ secrets.GITHUB_TOKEN }} # github access token is only required if checkAllCommitMessages is true + + - name: Check line length + uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee + with: + pattern: '(^.{0,74}$\r?\n?){0,20}' + flags: "gm" + error: "The maximum line length of 74 characters is exceeded." + excludeDescription: "true" # optional: this excludes the description body of a pull request + excludeTitle: "true" # optional: this excludes the title of a pull request + checkAllCommitMessages: "true" # optional: this checks all commits associated with a pull request + accessToken: ${{ secrets.GITHUB_TOKEN }} # github access token is only required if checkAllCommitMessages is true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index d52e61f..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Check formatting using rustfmt -# and lint with clippy -name: Rustfmt and Clippy check - -on: - push: - -jobs: - rustfmt: - name: rustfmt - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - components: rustfmt - override: true - - - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - clippy: - name: clippy - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - components: clippy - override: true - - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-targets --features=serde -- --no-deps -D warnings diff --git a/.github/workflows/start_benchmark.yml b/.github/workflows/start_benchmark.yml new file mode 100644 index 0000000..9d00b44 --- /dev/null +++ b/.github/workflows/start_benchmark.yml @@ -0,0 +1,33 @@ +# Start all benchmark jobs on Slab CI bot. +name: Start all benchmarks + +on: + push: + branches: + - 'main' + workflow_dispatch: + +jobs: + start-benchmarks: + runs-on: ubuntu-latest + steps: + - name: Checkout Slab repo + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + with: + repository: zama-ai/slab + path: slab + token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }} + + - name: Start AWS job in Slab + shell: bash + # TODO: step result must be correlated to HTTP return code. + run: | + echo -n '{"command": "bench", "git_ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' > command.json + SIGNATURE="$(slab/scripts/hmac_calculator.sh command.json '${{ secrets.JOB_SECRET }}')" + curl -v -k \ + -H "Content-Type: application/json" \ + -H "X-Slab-Repository: ${{ github.repository }}" \ + -H "X-Slab-Command: start_aws" \ + -H "X-Hub-Signature-256: sha256=${SIGNATURE}" \ + -d @command.json \ + ${{ secrets.SLAB_URL }} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2536243 --- /dev/null +++ b/Makefile @@ -0,0 +1,116 @@ +SHELL:=$(shell /usr/bin/env which bash) +RS_CHECK_TOOLCHAIN:=$(shell cat toolchain.txt | tr -d '\n') +CARGO_RS_CHECK_TOOLCHAIN:=+$(RS_CHECK_TOOLCHAIN) +RS_BUILD_TOOLCHAIN:=nightly +CARGO_RS_BUILD_TOOLCHAIN:=+$(RS_BUILD_TOOLCHAIN) +MIN_RUST_VERSION:=1.65 +AVX512_SUPPORT?=OFF +# This is done to avoid forgetting it, we still precise the RUSTFLAGS in the commands to be able to +# copy paste the command in the terminal and change them if required without forgetting the flags +export RUSTFLAGS?=-C target-cpu=native + +ifeq ($(AVX512_SUPPORT),ON) + AVX512_FEATURE=nightly +else + AVX512_FEATURE= +endif + +.PHONY: rs_check_toolchain # Echo the rust toolchain used for checks +rs_check_toolchain: + @echo $(RS_CHECK_TOOLCHAIN) + +.PHONY: rs_build_toolchain # Echo the rust toolchain used for builds +rs_build_toolchain: + @echo $(RS_BUILD_TOOLCHAIN) + +.PHONY: install_rs_check_toolchain # Install the toolchain used for checks +install_rs_check_toolchain: + @rustup toolchain list | grep -q "$(RS_CHECK_TOOLCHAIN)" || \ + rustup toolchain install --profile default "$(RS_CHECK_TOOLCHAIN)" || \ + ( echo "Unable to install $(RS_CHECK_TOOLCHAIN) toolchain, check your rustup installation. \ + Rustup can be downloaded at https://rustup.rs/" && exit 1 ) + +.PHONY: install_rs_build_toolchain # Install the toolchain used for builds +install_rs_build_toolchain: + @( rustup toolchain list | grep -q "$(RS_BUILD_TOOLCHAIN)" && \ + ./scripts/check_cargo_min_ver.sh \ + --rust-toolchain "$(CARGO_RS_BUILD_TOOLCHAIN)" \ + --min-rust-version "$(MIN_RUST_VERSION)" ) || \ + rustup toolchain install --profile default "$(RS_BUILD_TOOLCHAIN)" || \ + ( echo "Unable to install $(RS_BUILD_TOOLCHAIN) toolchain, check your rustup installation. \ + Rustup can be downloaded at https://rustup.rs/" && exit 1 ) + +.PHONY: fmt # Format rust code +fmt: install_rs_check_toolchain + cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt + +.PHONT: check_fmt # Check rust code format +check_fmt: install_rs_check_toolchain + cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt --check + +.PHONY: clippy # Run clippy lints +clippy: install_rs_check_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \ + --features=serde -- --no-deps -D warnings + +.PHONY: build +build: install_rs_build_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release + +.PHONY: build_no_std +build_no_std: install_rs_build_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --release \ + --no-default-features + +.PHONY: build_bench +build_bench: install_rs_check_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \ + --no-run + +.PHONY: test +test: install_rs_build_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release + +.PHONY: test_serde +test_serde: install_rs_build_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release + --features=serde + +.PHONY: test_nightly +test_nightly: install_rs_build_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \ + --features=nightly + +.PHONY: test_no_std +test_no_std: install_rs_build_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \ + --no-default-features + +.PHONY: test_no_std_nightly +test_no_std_nightly: install_rs_build_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --release \ + --no-default-features \ + --features=nightly + +.PHONY: test_all +test_all: test test_serde test_nightly test_no_std test_no_std_nightly + +.PHONY: doc # Build rust doc +doc: install_rs_check_toolchain + RUSTDOCFLAGS="--html-in-header katex-header.html -Dwarnings" \ + cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" doc --no-deps + +.PHONY: bench # Run benchmarks +bench: install_rs_check_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \ + --features=$(AVX512_FEATURE) + +.PHONY: pcc # pcc stands for pre commit checks +pcc: check_fmt doc clippy + +.PHONY: conformance # Automatically fix problems that can be fixed +conformance: fmt + +.PHONY: help # Generate list of targets with descriptions +help: + @grep '^\.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1\t\2/' | expand -t30 | sort diff --git a/ci/benchmark_parser.py b/ci/benchmark_parser.py new file mode 100644 index 0000000..81fcdb1 --- /dev/null +++ b/ci/benchmark_parser.py @@ -0,0 +1,159 @@ +""" +benchmark_parser +---------------- + +Parse criterion benchmark or keys size results. +""" +import argparse +import csv +import pathlib +import json +import sys + + +parser = argparse.ArgumentParser() +parser.add_argument('results', + help='Location of criterion benchmark results directory.' + 'If the --key-size option is used, then the value would have to point to' + 'a CSV file.') +parser.add_argument('output_file', help='File storing parsed results') +parser.add_argument('-d', '--database', dest='database', + help='Name of the database used to store results') +parser.add_argument('-w', '--hardware', dest='hardware', + help='Hardware reference used to perform benchmark') +parser.add_argument('-V', '--project-version', dest='project_version', + help='Commit hash reference') +parser.add_argument('-b', '--branch', dest='branch', + help='Git branch name on which benchmark was performed') +parser.add_argument('--commit-date', dest='commit_date', + help='Timestamp of commit hash used in project_version') +parser.add_argument('--bench-date', dest='bench_date', + help='Timestamp when benchmark was run') +parser.add_argument('--name-suffix', dest='name_suffix', default='', + help='Suffix to append to each of the result test names') +parser.add_argument('--append-results', dest='append_results', action='store_true', + help='Append parsed results to an existing file') + + +def recursive_parse(directory, name_suffix=""): + """ + Parse all the benchmark results in a directory. It will attempt to parse all the files having a + .json extension at the top-level of this directory. + + :param directory: path to directory that contains raw results as :class:`pathlib.Path` + :param name_suffix: a :class:`str` suffix to apply to each test name found + + :return: :class:`list` of data points + """ + excluded_directories = ["child_generate", "fork", "parent_generate", "report"] + result_values = list() + for dire in directory.iterdir(): + if dire.name in excluded_directories or not dire.is_dir(): + continue + for subdir in dire.iterdir(): + if subdir.name != "new": + continue + + test_name = parse_benchmark_file(subdir) + for stat_name, value in parse_estimate_file(subdir).items(): + test_name_parts = list(filter(None, [test_name, stat_name, name_suffix])) + result_values.append({"value": value, "test": "_".join(test_name_parts)}) + + return result_values + + +def parse_benchmark_file(directory): + """ + Parse file containing details of the parameters used for a benchmark. + + :param directory: directory where a benchmark case results are located as :class:`pathlib.Path` + + :return: name of the test as :class:`str` + """ + raw_res = _parse_file_to_json(directory, "benchmark.json") + return raw_res["full_id"].replace(" ", "_") + + +def parse_estimate_file(directory): + """ + Parse file containing timing results for a benchmark. + + :param directory: directory where a benchmark case results are located as :class:`pathlib.Path` + + :return: :class:`dict` of data points + """ + raw_res = _parse_file_to_json(directory, "estimates.json") + return { + stat_name: raw_res[stat_name]["point_estimate"] + for stat_name in ("mean", "std_dev") + } + + +def _parse_file_to_json(directory, filename): + result_file = directory.joinpath(filename) + return json.loads(result_file.read_text()) + + +def dump_results(parsed_results, filename, input_args): + """ + Dump parsed results formatted as JSON to file. + + :param parsed_results: :class:`list` of data points + :param filename: filename for dump file as :class:`pathlib.Path` + :param input_args: CLI input arguments + """ + if input_args.append_results: + parsed_content = json.loads(filename.read_text()) + parsed_content["points"].extend(parsed_results) + filename.write_text(json.dumps(parsed_content)) + else: + filename.parent.mkdir(parents=True, exist_ok=True) + series = { + "database": input_args.database, + "hardware": input_args.hardware, + "project_version": input_args.project_version, + "branch": input_args.branch, + "insert_date": input_args.bench_date, + "commit_date": input_args.commit_date, + "points": parsed_results, + } + filename.write_text(json.dumps(series)) + + +def check_mandatory_args(input_args): + """ + Check for availability of required input arguments, the program will exit if one of them is + not present. If `append_results` flag is set, all the required arguments will be ignored. + + :param input_args: CLI input arguments + """ + if input_args.append_results: + return + + missing_args = list() + for arg_name in vars(input_args): + if arg_name in ["results_dir", "output_file", "name_suffix", "append_results"]: + continue + if not getattr(input_args, arg_name): + missing_args.append(arg_name) + + if missing_args: + for arg_name in missing_args: + print(f"Missing required argument: --{arg_name.replace('_', '-')}") + sys.exit(1) + + +if __name__ == "__main__": + args = parser.parse_args() + check_mandatory_args(args) + + raw_results = pathlib.Path(args.results) + print("Parsing benchmark results... ") + results = recursive_parse(raw_results, args.name_suffix) + print("Parsing results done") + + output_file = pathlib.Path(args.output_file) + print(f"Dump parsed results into '{output_file.resolve()}' ... ", end="") + dump_results(results, output_file, args) + + print("Done") diff --git a/ci/slab.toml b/ci/slab.toml new file mode 100644 index 0000000..cd67d3c --- /dev/null +++ b/ci/slab.toml @@ -0,0 +1,9 @@ +[profile.bench] +region = "eu-west-3" +image_id = "ami-04deffe45b5b236fd" +instance_type = "m6i.metal" + +[command.bench] +workflow = "benchmark.yml" +profile = "bench" +check_run_name = "FFT AWS Benchmarks" diff --git a/scripts/check_cargo_min_ver.sh b/scripts/check_cargo_min_ver.sh new file mode 100755 index 0000000..7ece83a --- /dev/null +++ b/scripts/check_cargo_min_ver.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -e + +function usage() { + echo "$0: check minimum cargo version" + echo + echo "--help Print this message" + echo "--rust-toolchain The toolchain to check the version for with leading" + echo "--min-rust-version Check toolchain version is >= to this version, default is 1.65" + echo +} + +RUST_TOOLCHAIN="" +# We set the default rust version 1.65 which is the minimum version required for stable GATs +MIN_RUST_VERSION="1.65" + +while [ -n "$1" ] +do + case "$1" in + "--help" | "-h" ) + usage + exit 0 + ;; + + "--rust-toolchain" ) + shift + RUST_TOOLCHAIN="$1" + ;; + + "--min-rust-version" ) + shift + MIN_RUST_VERSION="$1" + ;; + + *) + echo "Unknown param : $1" + exit 1 + ;; + esac + shift +done + +if [[ "${RUST_TOOLCHAIN::1}" != "+" ]]; then + RUST_TOOLCHAIN="+${RUST_TOOLCHAIN}" +fi + +ver_string="$(cargo ${RUST_TOOLCHAIN:+"${RUST_TOOLCHAIN}"} --version | \ + cut -d ' ' -f 2 | cut -d '-' -f 1)" +ver_major="$(echo "${ver_string}" | cut -d '.' -f 1)" +ver_minor="$(echo "${ver_string}" | cut -d '.' -f 2)" + +min_ver_major="$(echo "${MIN_RUST_VERSION}" | cut -d '.' -f 1)" +min_ver_minor="$(echo "${MIN_RUST_VERSION}" | cut -d '.' -f 2)" + +if [[ "${ver_major}" -ge "${min_ver_major}" ]] && [[ "${ver_minor}" -ge "${min_ver_minor}" ]]; then + exit 0 +fi + +exit 1 diff --git a/toolchain.txt b/toolchain.txt new file mode 100644 index 0000000..595cdb7 --- /dev/null +++ b/toolchain.txt @@ -0,0 +1 @@ +nightly-2023-01-30