Repository: sdkman/sdkman-cli Branch: master Commit: 532356b8e8a4 Files: 117 Total size: 268.7 KB Directory structure: gitextract_5nup9gj_/ ├── .editorconfig ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ ├── feature_request.md │ │ └── support_request.md │ ├── auto_assign.yml │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── beta.yml │ ├── chloe-triage.yml │ ├── notify-on-failure.yml │ ├── pr.yml │ └── release.yml ├── .gitignore ├── .sdkmanrc ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── bin/ │ └── release-binary.sh ├── build.gradle ├── contrib/ │ └── completion/ │ └── bash/ │ └── sdk ├── dco.txt ├── gradle/ │ ├── archive.gradle │ ├── release.gradle │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── settings.gradle └── src/ ├── jreleaser/ │ └── changelog.tpl ├── main/ │ └── bash/ │ ├── sdkman-availability.sh │ ├── sdkman-cache.sh │ ├── sdkman-config.sh │ ├── sdkman-current.sh │ ├── sdkman-default.sh │ ├── sdkman-env-helpers.sh │ ├── sdkman-env.sh │ ├── sdkman-flush.sh │ ├── sdkman-help.sh │ ├── sdkman-home.sh │ ├── sdkman-init.sh │ ├── sdkman-install.sh │ ├── sdkman-list.sh │ ├── sdkman-main.sh │ ├── sdkman-offline.sh │ ├── sdkman-path-helpers.sh │ ├── sdkman-selfupdate.sh │ ├── sdkman-uninstall.sh │ ├── sdkman-update.sh │ ├── sdkman-upgrade.sh │ ├── sdkman-use.sh │ ├── sdkman-utils.sh │ └── sdkman-version.sh └── test/ ├── groovy/ │ └── sdkman/ │ ├── cucumber/ │ │ └── RunCukeTests.groovy │ ├── env/ │ │ ├── BashEnv.groovy │ │ ├── CleanBashEnvBuilder.groovy │ │ └── SdkmanBashEnvBuilder.groovy │ ├── specs/ │ │ ├── CandidatesCacheUpdateFailureSpec.groovy │ │ ├── CandidatesCacheUpdateSpec.groovy │ │ ├── CompletionSpec.groovy │ │ ├── ConfigCommandSpec.groovy │ │ ├── CurrentCommandSpec.groovy │ │ ├── EnvCommandSpec.groovy │ │ ├── InitialisationSpec.groovy │ │ ├── SdkCompatibilitySpec.groovy │ │ └── SelfupdateFeatureSpec.groovy │ ├── steps/ │ │ ├── command_line_interop_steps.groovy │ │ ├── env.groovy │ │ ├── env_steps.groovy │ │ ├── flush_steps.groovy │ │ ├── initialisation_steps.groovy │ │ ├── installation_steps.groovy │ │ ├── stub_steps.groovy │ │ ├── update_steps.groovy │ │ └── use_steps.groovy │ ├── stubs/ │ │ ├── CurlStub.groovy │ │ ├── HookResponses.groovy │ │ ├── UnameStub.groovy │ │ └── WebServiceStub.groovy │ └── support/ │ ├── BashEnvSpecification.groovy │ ├── FilesystemUtils.groovy │ ├── SdkmanEnvSpecification.groovy │ ├── UnixUtils.groovy │ └── WireMockServerProvider.groovy ├── jmeter/ │ ├── SDKman.jmx │ └── candidates.csv └── resources/ ├── __files/ │ └── hooks/ │ ├── post_hook_java_8.0.101_linuxx64.sh │ ├── post_hook_java_8.0.111_linuxx64.sh │ └── post_hook_java_8.0.111_universal.sh └── features/ ├── checksum_verification.feature ├── command_line_interop.feature ├── current_candidate.feature ├── default_version.feature ├── flush.feature ├── home.feature ├── hooks.feature ├── install_candidate.feature ├── install_sdkman.feature ├── java_installation.feature ├── list_candidate_versions.feature ├── list_candidates.feature ├── local_developement_versions.feature ├── mnemonics.feature ├── offline_mode.feature ├── path_initialisation.feature ├── per_project_configuration.feature ├── self_update.feature ├── service_unavailable.feature ├── uninstall_candidate.feature ├── update.feature ├── upgrade_candidate.feature ├── use_version.feature └── version.feature ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = tab insert_final_newline = false max_line_length = 120 tab_width = 4 trim_trailing_whitespace = false ij_continuation_indent_size = 8 ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on ij_formatter_tags_enabled = false ij_smart_tabs = false ij_wrap_on_typing = false [{*.yaml,*.yml}] indent_size = 2 [{*.bash,*.sh,*.zsh}] ij_shell_binary_ops_start_line = false ij_shell_keep_column_alignment_padding = false ij_shell_minify_program = false ij_shell_redirect_followed_by_space = true ij_shell_switch_cases_indented = false [*.feature] ij_gherkin_keep_indents_on_empty_lines = false [{*.gant,*.gradle,*.groovy,*.gy}] ij_groovy_align_group_field_declarations = false ij_groovy_align_multiline_array_initializer_expression = false ij_groovy_align_multiline_assignment = false ij_groovy_align_multiline_binary_operation = false ij_groovy_align_multiline_chained_methods = false ij_groovy_align_multiline_extends_list = false ij_groovy_align_multiline_for = true ij_groovy_align_multiline_list_or_map = true ij_groovy_align_multiline_method_parentheses = false ij_groovy_align_multiline_parameters = true ij_groovy_align_multiline_parameters_in_calls = false ij_groovy_align_multiline_resources = true ij_groovy_align_multiline_ternary_operation = false ij_groovy_align_multiline_throws_list = false ij_groovy_align_named_args_in_map = true ij_groovy_align_throws_keyword = false ij_groovy_array_initializer_new_line_after_left_brace = false ij_groovy_array_initializer_right_brace_on_new_line = false ij_groovy_array_initializer_wrap = off ij_groovy_assert_statement_wrap = off ij_groovy_assignment_wrap = off ij_groovy_binary_operation_wrap = off ij_groovy_blank_lines_after_class_header = 0 ij_groovy_blank_lines_after_imports = 1 ij_groovy_blank_lines_after_package = 1 ij_groovy_blank_lines_around_class = 1 ij_groovy_blank_lines_around_field = 0 ij_groovy_blank_lines_around_field_in_interface = 0 ij_groovy_blank_lines_around_method = 1 ij_groovy_blank_lines_around_method_in_interface = 1 ij_groovy_blank_lines_before_imports = 1 ij_groovy_blank_lines_before_method_body = 0 ij_groovy_blank_lines_before_package = 0 ij_groovy_block_brace_style = end_of_line ij_groovy_block_comment_at_first_column = true ij_groovy_call_parameters_new_line_after_left_paren = false ij_groovy_call_parameters_right_paren_on_new_line = false ij_groovy_call_parameters_wrap = off ij_groovy_catch_on_new_line = false ij_groovy_class_annotation_wrap = split_into_lines ij_groovy_class_brace_style = end_of_line ij_groovy_class_count_to_use_import_on_demand = 5 ij_groovy_do_while_brace_force = never ij_groovy_else_on_new_line = false ij_groovy_enum_constants_wrap = off ij_groovy_extends_keyword_wrap = off ij_groovy_extends_list_wrap = off ij_groovy_field_annotation_wrap = split_into_lines ij_groovy_finally_on_new_line = false ij_groovy_for_brace_force = never ij_groovy_for_statement_new_line_after_left_paren = false ij_groovy_for_statement_right_paren_on_new_line = false ij_groovy_for_statement_wrap = off ij_groovy_if_brace_force = never ij_groovy_import_annotation_wrap = 2 ij_groovy_indent_case_from_switch = true ij_groovy_indent_label_blocks = true ij_groovy_insert_inner_class_imports = false ij_groovy_keep_blank_lines_before_right_brace = 2 ij_groovy_keep_blank_lines_in_code = 2 ij_groovy_keep_blank_lines_in_declarations = 2 ij_groovy_keep_control_statement_in_one_line = true ij_groovy_keep_first_column_comment = true ij_groovy_keep_indents_on_empty_lines = false ij_groovy_keep_line_breaks = true ij_groovy_keep_multiple_expressions_in_one_line = false ij_groovy_keep_simple_blocks_in_one_line = false ij_groovy_keep_simple_classes_in_one_line = true ij_groovy_keep_simple_lambdas_in_one_line = true ij_groovy_keep_simple_methods_in_one_line = true ij_groovy_label_indent_absolute = false ij_groovy_label_indent_size = 0 ij_groovy_lambda_brace_style = end_of_line ij_groovy_layout_static_imports_separately = true ij_groovy_line_comment_add_space = false ij_groovy_line_comment_at_first_column = true ij_groovy_method_annotation_wrap = split_into_lines ij_groovy_method_brace_style = end_of_line ij_groovy_method_call_chain_wrap = off ij_groovy_method_parameters_new_line_after_left_paren = false ij_groovy_method_parameters_right_paren_on_new_line = false ij_groovy_method_parameters_wrap = off ij_groovy_modifier_list_wrap = false ij_groovy_names_count_to_use_import_on_demand = 3 ij_groovy_parameter_annotation_wrap = off ij_groovy_parentheses_expression_new_line_after_left_paren = false ij_groovy_parentheses_expression_right_paren_on_new_line = false ij_groovy_prefer_parameters_wrap = false ij_groovy_resource_list_new_line_after_left_paren = false ij_groovy_resource_list_right_paren_on_new_line = false ij_groovy_resource_list_wrap = off ij_groovy_space_after_assert_separator = true ij_groovy_space_after_colon = true ij_groovy_space_after_comma = true ij_groovy_space_after_comma_in_type_arguments = true ij_groovy_space_after_for_semicolon = true ij_groovy_space_after_quest = true ij_groovy_space_after_type_cast = true ij_groovy_space_before_annotation_parameter_list = false ij_groovy_space_before_array_initializer_left_brace = false ij_groovy_space_before_assert_separator = false ij_groovy_space_before_catch_keyword = true ij_groovy_space_before_catch_left_brace = true ij_groovy_space_before_catch_parentheses = true ij_groovy_space_before_class_left_brace = true ij_groovy_space_before_closure_left_brace = true ij_groovy_space_before_colon = true ij_groovy_space_before_comma = false ij_groovy_space_before_do_left_brace = true ij_groovy_space_before_else_keyword = true ij_groovy_space_before_else_left_brace = true ij_groovy_space_before_finally_keyword = true ij_groovy_space_before_finally_left_brace = true ij_groovy_space_before_for_left_brace = true ij_groovy_space_before_for_parentheses = true ij_groovy_space_before_for_semicolon = false ij_groovy_space_before_if_left_brace = true ij_groovy_space_before_if_parentheses = true ij_groovy_space_before_method_call_parentheses = false ij_groovy_space_before_method_left_brace = true ij_groovy_space_before_method_parentheses = false ij_groovy_space_before_quest = true ij_groovy_space_before_switch_left_brace = true ij_groovy_space_before_switch_parentheses = true ij_groovy_space_before_synchronized_left_brace = true ij_groovy_space_before_synchronized_parentheses = true ij_groovy_space_before_try_left_brace = true ij_groovy_space_before_try_parentheses = true ij_groovy_space_before_while_keyword = true ij_groovy_space_before_while_left_brace = true ij_groovy_space_before_while_parentheses = true ij_groovy_space_in_named_argument = true ij_groovy_space_in_named_argument_before_colon = false ij_groovy_space_within_empty_array_initializer_braces = false ij_groovy_space_within_empty_method_call_parentheses = false ij_groovy_spaces_around_additive_operators = true ij_groovy_spaces_around_assignment_operators = true ij_groovy_spaces_around_bitwise_operators = true ij_groovy_spaces_around_equality_operators = true ij_groovy_spaces_around_lambda_arrow = true ij_groovy_spaces_around_logical_operators = true ij_groovy_spaces_around_multiplicative_operators = true ij_groovy_spaces_around_regex_operators = true ij_groovy_spaces_around_relational_operators = true ij_groovy_spaces_around_shift_operators = true ij_groovy_spaces_within_annotation_parentheses = false ij_groovy_spaces_within_array_initializer_braces = false ij_groovy_spaces_within_braces = true ij_groovy_spaces_within_brackets = false ij_groovy_spaces_within_cast_parentheses = false ij_groovy_spaces_within_catch_parentheses = false ij_groovy_spaces_within_for_parentheses = false ij_groovy_spaces_within_gstring_injection_braces = false ij_groovy_spaces_within_if_parentheses = false ij_groovy_spaces_within_list_or_map = false ij_groovy_spaces_within_method_call_parentheses = false ij_groovy_spaces_within_method_parentheses = false ij_groovy_spaces_within_parentheses = false ij_groovy_spaces_within_switch_parentheses = false ij_groovy_spaces_within_synchronized_parentheses = false ij_groovy_spaces_within_try_parentheses = false ij_groovy_spaces_within_tuple_expression = false ij_groovy_spaces_within_while_parentheses = false ij_groovy_special_else_if_treatment = true ij_groovy_ternary_operation_wrap = off ij_groovy_throws_keyword_wrap = off ij_groovy_throws_list_wrap = off ij_groovy_use_flying_geese_braces = false ij_groovy_use_fq_class_names = false ij_groovy_use_fq_class_names_in_javadoc = true ij_groovy_use_relative_indents = false ij_groovy_use_single_class_imports = true ij_groovy_variable_annotation_wrap = off ij_groovy_while_brace_force = never ij_groovy_while_on_new_line = false ij_groovy_wrap_long_lines = false ================================================ FILE: .github/CODEOWNERS ================================================ * @sdkman/sdkman-cli ================================================ FILE: .github/FUNDING.yml ================================================ open_collective: sdkman ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: You found a bug in SDKMAN! title: "Bug: [A concise title]" labels: 'bug' assignees: '' --- **Bug report** **To reproduce** **System info** ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: SDKMAN! usage documentation url: https://sdkman.io/usage about: Find documentation about the available commands here. - name: SDKMAN! Discord server url: https://discord.gg/y9mVJYVyu4 about: Please ask and answer questions about the usage of SDKMAN! here. - name: SDKMAN! on Stack Overflow url: https://stackoverflow.com/questions/tagged/sdkman about: You might find help to common issues here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for SDKMAN! title: 'Feature: [A concise title]' labels: 'enhancement' assignees: '' --- - [ ] I have read the [CONTRIBUTING guidelines](CONTRIBUTING.md) - [ ] I've had a conversation on our community [Discord](https://discord.gg/y9mVJYVyu4) server. **Feature request** ================================================ FILE: .github/ISSUE_TEMPLATE/support_request.md ================================================ --- name: Support request or question about: You need help using SDKMAN! title: "Question: [Short summary]" labels: 'support' assignees: '' --- - [ ] I have checked [the documentation](https://sdkman.io/usage) - [ ] I have performed a quick search in [past issues](https://github.com/sdkman/sdkman-cli/issues?q=) or [Stack Overflow](https://stackoverflow.com/questions/tagged/sdkman) for similar problems - [ ] I have brought up a conversation in the community [Discord](https://discord.gg/y9mVJYVyu4) server **Question** **System info** ================================================ FILE: .github/auto_assign.yml ================================================ --- addReviewers: true addAssignees: author reviewers: - marc0der - helpermethod numberOfReviewers: 0 ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: daily ================================================ FILE: .github/pull_request_template.md ================================================ - [ ] a GitHub Issue was opened for this feature / bug. - [ ] test coverage was added (Cucumber or Spock as appropriate). Fixes #XXX ================================================ FILE: .github/workflows/beta.yml ================================================ name: Beta Release on: push: branches: - master paths-ignore: - .github/workflows/* jobs: pre-release: name: "Beta Release" environment: production runs-on: "ubuntu-latest" services: mongodb: image: mongo:3.2 ports: - 27017:27017 steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' cache: 'gradle' - name: Run tests run: ./gradlew clean test --info - name: Set short git hash id: var run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" - name: Build artifacts run: ./gradlew -Penv=beta -Phash=${{ steps.var.outputs.sha_short }} clean assemble - name: Release if: github.repository == 'sdkman/sdkman-cli' uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" automatic_release_tag: "latest" prerelease: true title: "Beta release" files: | build/distributions/sdkman-cli-*.zip - name: Update MongoDB if: github.repository == 'sdkman/sdkman-cli' env: MONGO_URL: ${{ secrets.MONGO_URL }} MONGO_USERNAME: ${{ secrets.MONGO_USERNAME }} MONGO_PASSWORD: ${{ secrets.MONGO_PASSWORD }} SHORT_HASH: ${{ steps.var.outputs.sha_short }} run: bin/release-binary.sh "$MONGO_URL" "$MONGO_USERNAME" "$MONGO_PASSWORD" "$SHORT_HASH" "latest" ================================================ FILE: .github/workflows/chloe-triage.yml ================================================ name: Notify Chloé (Issue Triage) on: issues: types: [opened] issue_comment: types: [created] pull_request: types: [opened] jobs: notify: uses: sdkman/.github/.github/workflows/reusable-triage.yml@main secrets: inherit ================================================ FILE: .github/workflows/notify-on-failure.yml ================================================ name: Notify on Failure on: workflow_run: workflows: ["*"] branches: [master, main] types: [completed] jobs: notify: uses: sdkman/.github/.github/workflows/reusable-notify-on-failure.yml@main secrets: inherit ================================================ FILE: .github/workflows/pr.yml ================================================ name: Pull Requests #run only on PR, not fork. Pull request that way the PR submitter will not have access to the repo credentials on: pull_request jobs: test: runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' cache: 'gradle' - name: Run with Gradle run: ./gradlew clean test --info - uses: kentaro-m/auto-assign-action@v1.2.5 with: configuration-path: ".github/auto_assign.yml" ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch: inputs: version: description: "Release version" required: true jobs: build: name: "Stable Release" if: github.repository == 'sdkman/sdkman-cli' runs-on: ubuntu-latest environment: production env: JRELEASER_GITHUB_TOKEN: ${{ github.token }} JRELEASER_TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }} JRELEASER_TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} JRELEASER_TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} JRELEASER_TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} JRELEASER_MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN}} services: mongodb: image: mongo:3.2 ports: - 27017:27017 steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' cache: 'gradle' - name: Run tests run: ./gradlew clean test --info - name: Build artifacts run: ./gradlew -Penv=stable -Prelease=${{ github.event.inputs.version }} clean assemble - name: Release run: ./gradlew -Penv=stable -Prelease=${{ github.event.inputs.version }} jreleaserFullRelease --exclude-announcer=twitter - name: Update MongoDB env: MONGO_URL: ${{ secrets.MONGO_URL }} MONGO_USERNAME: ${{ secrets.MONGO_USERNAME }} MONGO_PASSWORD: ${{ secrets.MONGO_PASSWORD }} RELEASE_TAG: ${{ github.event.inputs.version }} run: bin/release-binary.sh "$MONGO_URL" "$MONGO_USERNAME" "$MONGO_PASSWORD" "$RELEASE_TAG" "stable" - name: Tweet about it run: ./gradlew -Penv=stable -Prelease=${{ github.event.inputs.version }} jreleaserAnnounce ================================================ FILE: .gitignore ================================================ *~ .#* .classpath .gradle/ .gradletasknamecache .idea/ .pid.lock .project .settings/ .vscode/ \#* build/ classes sdkman-cli.iml sdkman.iml sdkman.ipr sdkman.iws mongo.json out/ sdkman-cli.iml target/ bin/test ================================================ FILE: .sdkmanrc ================================================ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below java=11.0.17-tem ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview SDKMAN! CLI is a bash-based command-line tool for managing parallel versions of multiple Software Development Kits on Unix-based systems. The project is primarily written in bash scripts with Groovy/Spock for testing using Cucumber BDD tests. **Important**: This project is in maintenance mode. Only bug fixes are accepted as the project is being rewritten in Rust under a new project. No new enhancements to existing commands will be accepted. ## Development Environment - Requires JDK 11 (specified in `.sdkmanrc`) - Uses Gradle 8.0.1 for build management - Run `sdk env install` to install the correct JDK version - Run `sdk env` to switch to the appropriate SDK versions ## Build and Test Commands ### Core Commands - `./gradlew test` - Run all Cucumber BDD tests - `./gradlew clean` - Clean build artifacts - `./gradlew build` - Build the project ### Running Specific Tests - Tests can be run with tags: use `@manual` and `@review` tags to exclude certain tests - Individual feature files can be run by specifying the feature file path - Environment can be set via `env` property (local/beta/stable) - defaults to local ### Development Setup Before starting development, ensure the correct Java version: ```bash sdk env install # Install JDK from .sdkmanrc sdk env # Switch to correct SDK versions ``` ## Architecture ### Code Structure - `src/main/bash/` - Core bash scripts implementing SDKMAN commands - `sdkman-main.sh` - Main entry point and command routing - `sdkman-init.sh` - Environment initialization and platform detection - Individual command scripts (e.g., `sdkman-install.sh`, `sdkman-list.sh`) - `src/test/groovy/` - Groovy test code using Spock framework - `src/test/resources/features/` - Cucumber feature files (BDD tests) ### Core Components - **Command Router**: `sdkman-main.sh` handles command aliases and routing - **Environment Setup**: `sdkman-init.sh` manages platform detection and configuration - **Modular Commands**: Each SDKMAN command is implemented in its own bash script - **API Integration**: Commands interact with SDKMAN API for candidate information ### Testing Strategy - **Cucumber BDD Tests**: Primary testing approach using Gherkin feature files - **Spock Unit Tests**: Some Groovy unit tests for specific components - Tests cover both happy path and edge cases - Mock external dependencies using WireMock - Test environment uses bash process execution ### Configuration - `.sdkmanrc` file specifies required Java version (11.0.17-tem) - Environment-specific API endpoints configured in `build.gradle`: - **local**: `http://localhost:8080/2` (for development) - **beta**: `https://beta.sdkman.io/2` - **stable**: `https://api.sdkman.io/2` (production) - Pass environment via `-Penv=` to gradle commands ## Key Files - `build.gradle` - Main build configuration with test dependencies - `src/main/bash/sdkman-main.sh` - Command entry point and routing - `src/test/groovy/sdkman/cucumber/RunCukeTests.groovy` - Cucumber test runner - `src/test/resources/features/` - BDD feature specifications ## Testing Guidelines - All features should have Cucumber tests covering happy and unhappy paths - Tests should be written before implementation (TDD approach) - Use `@manual` and `@review` tags to exclude certain tests from automation - Mock external API calls using WireMock stubs ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at info@sdkman.io. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to SDKMAN! CLI Thank you for your interest in contributing to SDKMAN! CLI. We greatly value the feedback and contributions of our users. ## Important Notice **This project is in maintenance mode.** We are only accepting bug fixes at this time, as SDKMAN! CLI is being rewritten in Rust. New features and enhancements will not be accepted. ## Code of Conduct This project adheres to the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to info@sdkman.io. ## How to Contribute ### Before You Start We distinguish between: - **Bug Reports**: Issues with existing functionality - **Support Requests**: Questions about usage We prefer to have a conversation on [SDKMAN Discord](https://discord.gg/y9mVJYVyu4) before creating new issues if you're unsure how to categorize your request. Join our Help channel to discuss. ### Reporting Bugs 1. **Search existing issues** to avoid duplicates 2. **Use the issue template** when creating a new issue via our [GitHub Issue Tracker](https://github.com/sdkman/sdkman-cli/issues/new) 3. **Provide detailed information** including: - Steps to reproduce the issue - Expected behavior - Actual behavior - Your environment (OS, shell, Java version) - Relevant logs or error messages **Note:** Issues that don't follow the template may be closed. ### Submitting Pull Requests Pull requests are always welcome but must follow these guidelines: #### Prerequisites 1. **Link to an issue**: Every PR must reference a valid GitHub issue 2. **Discuss first**: Talk about your proposed fix on Discord or in the issue before starting work 3. **Keep it small**: Small, focused PRs are strongly preferred over large changes 4. **Include tests**: Each PR should include passing tests that prove its validity (where feasible) #### Development Setup 1. **Install required tools**: ```bash sdk env install # Install JDK 11 from .sdkmanrc sdk env # Switch to correct SDK versions ``` 2. **Verify your setup**: ```bash ./gradlew test ``` #### Development Workflow 1. **Fork the repository** and create a new branch from `master` 2. **Make your changes** following our code standards 3. **Write or update tests** to cover your changes 4. **Run the test suite** to ensure everything passes: ```bash ./gradlew test ``` 5. **Commit your changes** using clear, descriptive commit messages 6. **Push to your fork** and submit a pull request #### Pull Request Guidelines - Fill in the PR template completely - Link back to the GitHub issue by replacing `#XXX` with the issue number - Ensure all tests pass - Keep commits focused and atomic - Write clear commit messages in imperative mood (e.g., "fix user login bug") - Be responsive to feedback during code review #### Testing Requirements - All bug fixes must include a test that would have caught the bug - Tests should follow the existing Cucumber BDD pattern - Tests should cover both happy path and edge cases - Run `./gradlew test` to verify all tests pass ### Code Standards - Follow the existing code style in bash scripts - Use meaningful variable and function names - Add comments for complex logic - Avoid introducing new dependencies without discussion - Ensure backward compatibility ### Getting Help - Join our [Discord server](https://discord.gg/y9mVJYVyu4) for questions - Check existing issues and discussions - Read the documentation in the repository ## Project Structure - `src/main/bash/` - Core bash scripts implementing SDKMAN commands - `src/test/groovy/` - Groovy test code using Spock framework - `src/test/resources/features/` - Cucumber feature files (BDD tests) ## Recognition Contributors will be recognized in our release notes and commit history. We appreciate your efforts to make SDKMAN! better! ## Developer Certificate of Origin By contributing to this project, you agree to the terms of the [Developer Certificate of Origin](dco.txt). This certifies that you have the right to submit your contribution under the project's open source license. ## Questions? If you have questions about contributing, feel free to ask on [Discord](https://discord.gg/y9mVJYVyu4) or open a discussion on GitHub. ================================================ FILE: Dockerfile ================================================ FROM openjdk:11 RUN apt-get update && \ apt-get -y install zip # Copy all here RUN mkdir -p /usr/src/app ADD . /usr/src/app WORKDIR /usr/src/app ENTRYPOINT ["./gradlew"] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: README.md ================================================ # SDKMAN! CLI ### The Software Development Kit Manager Command Line Interface [![Backers on Open Collective](https://img.shields.io/opencollective/backers/sdkman)](#backers) [![Sponsors on Open Collective](https://img.shields.io/opencollective/sponsors/sdkman)](#sponsors) [![Discord](https://img.shields.io/discord/1245471991117512754)](https://discord.gg/y9mVJYVyu4) SDKMAN is a tool for managing parallel Versions of multiple Software Development Kits on any Unix-based system. It provides a convenient command-line interface for installing, switching, removing, and listing Candidates. See documentation on the [SDKMAN! website](https://sdkman.io). ## NOTICE **We are rewriting all the commands for SDKMAN! in [Rust](https://www.rust-lang.org/) under a [new project](https://github.com/sdkman/sdkman-cli-native) that supplements this one. Only bug fixes to supporting code will be accepted in this project. As a result, no further enhancements will be accepted on the commands in this project, and the commands here will be phased out in due course. This project will eventually form a lightweight wrapper/launcher for the replacement Rust commands.** ## Installation Open your favourite terminal and enter the following: $ curl -s https://get.sdkman.io | bash If the environment needs tweaking for SDKMAN to be installed, the installer will prompt you accordingly and ask you to restart. ## Running the Cucumber Features All SDKMAN's BDD tests describing the CLI behaviour are written in Cucumber and can be found under `src/test/resources/features`. These can be run with Gradle by running the following command: $ ./gradlew test To perform development, you will need to have a JDK 11 installed which can be obtained by running the following after installing SDKMAN: $ sdk env install ## Hosting We're proud to host our backend services on DigitalOcean as a sponsored partner. [![DigitalOcean Referral Badge](https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%203.svg)](https://www.digitalocean.com/?refcode=d99e5747251d&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge) ## Contributors This project exists thanks to all the people who contribute. ## Backers Thank you to all our backers! [[Become a backer](https://opencollective.com/sdkman#backer)] ## Sponsors Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/sdkman#sponsor)] ================================================ FILE: bin/release-binary.sh ================================================ #!/usr/bin/env bash MONGO_URL="$1" MONGO_USERNAME="$2" MONGO_PASSWORD="$3" PARAM_1="$4" PARAM_2="$5" echo "Mongo URL: $MONGO_URL" if [[ -z "$MONGO_USERNAME" || -z "$MONGO_PASSWORD" ]]; then echo "No mongo credentials so doing nothing..." return 1 fi if [[ "$PARAM_2" == 'stable' ]]; then FIELD="stableCliVersion" VERSION="$PARAM_1" elif [[ "$PARAM_2" == 'latest' ]]; then FIELD="betaCliVersion" VERSION="latest+$PARAM_1" else return 1 fi echo "Release: $FIELD as $VERSION" docker run mongo:3.2 mongo "${MONGO_URL}" \ --username="${MONGO_USERNAME}" \ --password="${MONGO_PASSWORD}" \ --quiet \ --eval "db.application.updateOne({}, {\$set: { \"$FIELD\": \"$VERSION\"}});" ================================================ FILE: build.gradle ================================================ plugins { id('groovy') id('org.jreleaser').version('1.4.0').apply(false) } String userHome = System.getProperty('user.home') ext.installBinDir = "${userHome}/.sdkman/bin" ext.installSrcDir = "${userHome}/.sdkman/src" ext.installContribDir = "${userHome}/.sdkman/contrib" ext.environment = hasProperty('env') ? env : 'local' ext.hash = hasProperty('hash') ? hash : 'hashme' ext.release = hasProperty('release') ? release : 'latest' if ("$environment" == 'stable') { ext.candidatesApi = 'https://api.sdkman.io/2' ext.brokerApi = 'https://broker.sdkman.io' } else if ("$environment" == 'beta') { ext.candidatesApi = 'https://beta.sdkman.io/2' ext.brokerApi = 'https://broker.sdkman.io' } else { ext.candidatesApi = 'http://localhost:8080/2' ext.brokerApi = 'https://localhost:8080' } ext.sdkmanVersion = ext.release == 'latest' ? "latest+${ext.hash}".toString() : ext.release println("Environment is set to: $environment") println("Short git hash: $hash") println("Release set to: $release") println("Candidates API: $candidatesApi") println("Broker API: $brokerApi") println("Version: $sdkmanVersion") apply from: 'gradle/archive.gradle' apply from: 'gradle/release.gradle' repositories { mavenCentral() } dependencies { testImplementation('com.github.tomakehurst:wiremock:2.27.2') { exclude module: 'junit' } testImplementation('io.cucumber:cucumber-groovy:4.7.1') testImplementation('io.cucumber:cucumber-junit:4.7.4') testImplementation('io.cucumber:gherkin:5.2.0') testImplementation('junit:junit:4.13.2') testImplementation('org.codehaus.groovy:groovy:2.4.21') testImplementation('org.codehaus.groovy:groovy-json:2.4.21') testImplementation('org.codehaus.groovy:groovy-templates:2.4.21') testImplementation('org.slf4j:slf4j-api:1.7.32') testImplementation('org.slf4j:slf4j-simple:1.7.32') testImplementation('org.spockframework:spock-core:1.3-groovy-2.4') { exclude module: 'groovy-all' } } ================================================ FILE: contrib/completion/bash/sdk ================================================ #!/usr/bin/bash _sdk() { local -r previous_word=${COMP_WORDS[COMP_CWORD - 1]} local -r current_word=${COMP_WORDS[COMP_CWORD]} if ((COMP_CWORD == 3)); then local -r before_previous_word=${COMP_WORDS[COMP_CWORD - 2]} __sdkman_complete_candidate_version "$before_previous_word" "$previous_word" "$current_word" return fi __sdkman_complete_command "$previous_word" "$current_word" } __sdkman_complete_command() { local -r command=$1 local -r current_word=$2 local -a candidates case $command in sdk) candidates=("install" "uninstall" "list" "use" "config" "default" "home" "env" "current" "upgrade" "version" "help" "offline" "selfupdate" "update" "flush") ;; current|c|default|d|home|h|uninstall|rm|upgrade|ug|use|u) local -r candidate_paths=("${SDKMAN_CANDIDATES_DIR}"/*) for candidate_path in "${candidate_paths[@]}"; do candidates+=("${candidate_path##*/}") done ;; install|i|list|ls) candidates=${SDKMAN_CANDIDATES[@]} ;; env|e) candidates=("init" "install" "clear") ;; offline) candidates=("enable" "disable") ;; selfupdate) candidates=("force") ;; flush) candidates=("temp" "version") ;; esac COMPREPLY=($(compgen -W "${candidates[*]}" -- "$current_word")) } __sdkman_complete_candidate_version() { local -r command=$1 local -r candidate=$2 local -r candidate_version=$3 local -a candidates case $command in default|d|home|h|uninstall|rm|use|u) local -r version_paths=("${SDKMAN_CANDIDATES_DIR}/${candidate}"/*) for version_path in "${version_paths[@]}"; do [[ $version_path = *current ]] && continue candidates+=("${version_path##*/}") done ;; install|i) while IFS= read -r -d, version || [[ -n "$version" ]]; do candidates+=("$version") done <<< "$(curl --silent "${SDKMAN_CANDIDATES_API}/candidates/$candidate/${SDKMAN_PLATFORM}/versions/all")" ;; esac COMPREPLY=($(compgen -W "${candidates[*]}" -- "$candidate_version")) } complete -o default -F _sdk sdk # Set 'sdkman_auto_complete' to 'true' in .sdkman/etc/config to enable completion ================================================ FILE: dco.txt ================================================ Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ================================================ FILE: gradle/archive.gradle ================================================ import org.apache.tools.ant.filters.ReplaceTokens import org.gradle.api.tasks.testing.logging.TestExceptionFormat def baseDir = "$buildDir/stage/sdkman-${ext.sdkmanVersion}" task prepareBin(type: Copy) { from('src/main/bash') into("$baseDir/bin") include('**/sdkman-init.sh') filter( ReplaceTokens, tokens: [ SDKMAN_CANDIDATES_API: candidatesApi, SDKMAN_BROKER_API: brokerApi ] ) } task prepareScripts(type: Copy) { from('src/main/bash') into("$baseDir/src") include('**/*') exclude('**/sdkman-init.sh') } task prepareContrib(type: Copy) { from('contrib') into("$baseDir/contrib") } tasks.test.configure { dependsOn(prepareScripts) testLogging.exceptionFormat = TestExceptionFormat.FULL } task assembleArchive(type: Zip, dependsOn: [prepareBin, prepareScripts, prepareContrib]) { archiveVersion = sdkmanVersion from('build/stage') { include('**/*') } } tasks.assemble.configure { dependsOn(assembleArchive) } tasks.test.configure { dependsOn(prepareBin, prepareScripts, prepareContrib) } ================================================ FILE: gradle/release.gradle ================================================ apply plugin: 'org.jreleaser' jreleaser { project { name = 'sdkman-cli' version = sdkmanVersion description = 'Sdkman - The Software Development Kit Manager' website = 'https://sdkman.io' authors = ['Marco Vermeulen'] license = 'Apache-2.0' extraProperties.put('inceptionYear', '2012') } release { github { overwrite = true tagName = '{{projectVersion}}' changelog { formatted = 'ALWAYS' format = '- {{commitShortHash}} {{commitTitle}}' contributors { format = '- {{contributorName}}' } contentTemplate = file('src/jreleaser/changelog.tpl') hide { contributors = ['GitHub'] } } } } distributions { 'sdkman-cli' { distributionType = 'BINARY' artifact { path = "build/distributions/{{distributionName}}-${sdkmanVersion}.zip" extraProperties.put("universal", "true") } } } announce { twitter { active = 'RELEASE' status = 'Released version {{tagName}} of SDKMAN! {{releaseNotesUrl}}' } mastodon { active = 'RELEASE' status = 'Released version {{tagName}} of SDKMAN! {{releaseNotesUrl}}' host = 'https://fosstodon.org' } } } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: settings.gradle ================================================ pluginManagement { repositories { mavenLocal() gradlePluginPortal() mavenCentral() } } rootProject.name = "sdkman-cli" ================================================ FILE: src/jreleaser/changelog.tpl ================================================ ## Changelog {{changelogChanges}} {{changelogContributors}} ================================================ FILE: src/main/bash/sdkman-availability.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdkman_update_service_availability() { local healthcheck_status=$(__sdkman_determine_healthcheck_status) __sdkman_set_availability "$healthcheck_status" } function __sdkman_determine_healthcheck_status() { if [[ "$SDKMAN_OFFLINE_MODE" == "true" || "$COMMAND" == "offline" && "$QUALIFIER" == "enable" ]]; then echo "" else echo $(__sdkman_secure_curl_with_timeouts "${SDKMAN_CANDIDATES_API}/healthcheck") fi } function __sdkman_set_availability() { local healthcheck_status="$1" local detect_html="$(echo "$healthcheck_status" | tr '[:upper:]' '[:lower:]' | grep 'html')" if [[ -z "$healthcheck_status" ]]; then SDKMAN_AVAILABLE="false" __sdkman_display_offline_warning "$healthcheck_status" elif [[ -n "$detect_html" ]]; then SDKMAN_AVAILABLE="false" __sdkman_display_proxy_warning else SDKMAN_AVAILABLE="true" fi } function __sdkman_display_offline_warning() { local healthcheck_status="$1" if [[ -z "$healthcheck_status" && "$COMMAND" != "offline" && "$SDKMAN_OFFLINE_MODE" != "true" ]]; then __sdkman_echo_red "==== INTERNET NOT REACHABLE! ===================================================" __sdkman_echo_red "" __sdkman_echo_red " Some functionality is disabled or only partially available." __sdkman_echo_red " If this persists, please enable the offline mode:" __sdkman_echo_red "" __sdkman_echo_red " $ sdk offline" __sdkman_echo_red "" __sdkman_echo_red "================================================================================" echo "" fi } function __sdkman_display_proxy_warning() { __sdkman_echo_red "==== PROXY DETECTED! ===========================================================" __sdkman_echo_red "Please ensure you have open internet access to continue." __sdkman_echo_red "================================================================================" echo "" } ================================================ FILE: src/main/bash/sdkman-cache.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function ___sdkman_check_candidates_cache() { local candidates_cache="$1" if [[ -f "$candidates_cache" && -z "$(< "$candidates_cache")" ]]; then __sdkman_echo_red 'WARNING: Cache is corrupt. SDKMAN cannot be used until updated.' echo '' __sdkman_echo_no_colour ' $ sdk update' echo '' return 1 else __sdkman_echo_debug "Using existing cache: $SDKMAN_CANDIDATES_CSV" return 0 fi } ================================================ FILE: src/main/bash/sdkman-config.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_config() { local -r editor=(${EDITOR:=vi}) if ! command -v "${editor[@]}" > /dev/null; then __sdkman_echo_red "No default editor configured." __sdkman_echo_yellow "Please set the default editor with the EDITOR environment variable." return 1 fi "${editor[@]}" "${SDKMAN_DIR}/etc/config" } ================================================ FILE: src/main/bash/sdkman-current.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_current() { local candidate="$1" echo "" if [ -n "$candidate" ]; then __sdkman_determine_current_version "$candidate" if [ -n "$CURRENT" ]; then __sdkman_echo_no_colour "Using ${candidate} version ${CURRENT}" else __sdkman_echo_red "Not using any version of ${candidate}" fi else local installed_count=0 for ((i = 0; i <= ${#SDKMAN_CANDIDATES[*]}; i++)); do # Eliminate empty entries due to incompatibility if [[ -n ${SDKMAN_CANDIDATES[${i}]} ]]; then __sdkman_determine_current_version "${SDKMAN_CANDIDATES[${i}]}" if [ -n "$CURRENT" ]; then if [ ${installed_count} -eq 0 ]; then __sdkman_echo_no_colour 'Using:' echo "" fi __sdkman_echo_no_colour "${SDKMAN_CANDIDATES[${i}]}: ${CURRENT}" ((installed_count += 1)) fi fi done if [ ${installed_count} -eq 0 ]; then __sdkman_echo_no_colour 'No candidates are in use' fi fi } function __sdkman_determine_current_version() { local candidate present candidate="$1" present=$(__sdkman_path_contains "${SDKMAN_CANDIDATES_DIR}/${candidate}") if [[ "$present" == 'true' ]]; then if [[ $PATH =~ ${SDKMAN_CANDIDATES_DIR}/${candidate}/([^/]+)/bin ]]; then if [[ "$zsh_shell" == "true" ]]; then CURRENT=${match[1]} else CURRENT=${BASH_REMATCH[1]} fi fi if [[ "$CURRENT" == "current" ]]; then CURRENT=$(readlink "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" | sed "s!${SDKMAN_CANDIDATES_DIR}/${candidate}/!!g") fi else CURRENT="" fi } ================================================ FILE: src/main/bash/sdkman-default.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_default() { __sdkman_deprecation_notice "default" local candidate version candidate="$1" version="$2" __sdkman_check_candidate_present "$candidate" || return 1 __sdkman_determine_version "$candidate" "$version" || return 1 if [ ! -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${VERSION}" ]; then echo "" __sdkman_echo_red "Stop! Candidate version is not installed." echo "" __sdkman_echo_yellow "Tip: Run the following to install this version" echo "" __sdkman_echo_yellow "$ sdk install ${candidate} ${VERSION}" return 1 fi __sdkman_link_candidate_version "$candidate" "$VERSION" echo "" __sdkman_echo_green "Default ${candidate} version set to ${VERSION}" } ================================================ FILE: src/main/bash/sdkman-env-helpers.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdkman_check_candidate_present() { local candidate="$1" if [ -z "$candidate" ]; then echo "" __sdkman_echo_red "No candidate provided." __sdk_help return 1 fi } function __sdkman_check_version_present() { local version="$1" if [ -z "$version" ]; then echo "" __sdkman_echo_red "No candidate version provided." __sdk_help return 1 fi } function __sdkman_determine_version() { local candidate version folder candidate="$1" version="$2" folder="$3" if [[ "$SDKMAN_AVAILABLE" == "false" && -n "$version" && -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" ]]; then VERSION="$version" elif [[ "$SDKMAN_AVAILABLE" == "false" && -z "$version" && -L "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" ]]; then VERSION=$(readlink "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" | sed "s!${SDKMAN_CANDIDATES_DIR}/${candidate}/!!g") elif [[ "$SDKMAN_AVAILABLE" == "false" && -n "$version" ]]; then __sdkman_echo_red "Stop! ${candidate} ${version} is not available while offline." return 1 elif [[ "$SDKMAN_AVAILABLE" == "false" && -z "$version" ]]; then __sdkman_echo_red "This command is not available while offline." return 1 else if [[ -z "$version" ]]; then version=$(__sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/candidates/default/${candidate}") fi local validation_url="${SDKMAN_CANDIDATES_API}/candidates/validate/${candidate}/${version}/${SDKMAN_PLATFORM}" VERSION_VALID=$(__sdkman_secure_curl "$validation_url") __sdkman_echo_debug "Validate $candidate $version for $SDKMAN_PLATFORM: $VERSION_VALID" __sdkman_echo_debug "Validation URL: $validation_url" if [[ "$VERSION_VALID" == 'valid' || "$VERSION_VALID" == 'invalid' && -n "$folder" ]]; then VERSION="$version" elif [[ "$VERSION_VALID" == 'invalid' && -L "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" ]]; then VERSION="$version" elif [[ "$VERSION_VALID" == 'invalid' && -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" ]]; then VERSION="$version" else if [[ -z "$version" ]]; then version="\b" fi echo "" __sdkman_echo_red "Stop! $candidate $version is not available. Possible causes:" __sdkman_echo_red " * $version is an invalid version" __sdkman_echo_red " * $candidate binaries are incompatible with your platform" __sdkman_echo_red " * $candidate has not been released yet" echo "" __sdkman_echo_yellow "Tip: see all available versions for your platform:" echo "" __sdkman_echo_yellow " $ sdk list $candidate" return 1 fi fi } ================================================ FILE: src/main/bash/sdkman-env.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_env() { local -r sdkmanrc=".sdkmanrc" local -r subcommand="$1" case $subcommand in "") __sdkman_load_env "$sdkmanrc" ;; init) __sdkman_create_env_file "$sdkmanrc";; install) __sdkman_setup_env "$sdkmanrc";; clear) __sdkman_clear_env "$sdkmanrc";; esac } function __sdkman_setup_env() { local sdkmanrc="$1" if [[ ! -f "$sdkmanrc" ]]; then __sdkman_echo_red "Could not find $sdkmanrc in the current directory." echo "" __sdkman_echo_yellow "Run 'sdk env init' to create it." return 1 fi sdkman_auto_answer="true" USE="n" __sdkman_env_each_candidate "$sdkmanrc" "__sdk_install" __sdkman_load_env "$sdkmanrc" } function __sdkman_load_env() { local sdkmanrc="$1" if [[ ! -f "$sdkmanrc" ]]; then __sdkman_echo_red "Could not find $sdkmanrc in the current directory." echo "" __sdkman_echo_yellow "Run 'sdk env init' to create it." return 1 fi __sdkman_env_each_candidate "$sdkmanrc" "__sdkman_check_and_use" && SDKMAN_ENV=$PWD } function __sdkman_check_and_use() { local -r candidate=$1 local -r version=$2 if [[ ! -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" ]]; then __sdkman_echo_red "Stop! $candidate $version is not installed." echo "" __sdkman_echo_yellow "Run 'sdk env install' to install it." return 1 fi __sdk_use "$candidate" "$version" } function __sdkman_create_env_file() { local sdkmanrc="$1" if [[ -f "$sdkmanrc" ]]; then __sdkman_echo_red "$sdkmanrc already exists!" return 1 fi __sdkman_determine_current_version "java" local version [[ -n "$CURRENT" ]] && version="$CURRENT" || version="$(__sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/candidates/default/java")" cat <<-eof >|"$sdkmanrc" # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below java=$version eof __sdkman_echo_green "$sdkmanrc created." } function __sdkman_clear_env() { local sdkmanrc="$1" if [[ -z $SDKMAN_ENV ]]; then __sdkman_echo_red "No environment currently set!" return 1 fi if [[ ! -f ${SDKMAN_ENV}/${sdkmanrc} ]]; then __sdkman_echo_red "Could not find ${SDKMAN_ENV}/${sdkmanrc}." return 1 fi __sdkman_env_each_candidate "${SDKMAN_ENV}/${sdkmanrc}" "__sdkman_env_restore_default_version" unset SDKMAN_ENV } function __sdkman_env_restore_default_version() { local -r candidate="$1" local candidate_dir default_version candidate_dir="${SDKMAN_CANDIDATES_DIR}/${candidate}/current" if __sdkman_is_symlink $candidate_dir; then default_version=$(basename $(readlink ${candidate_dir})) __sdk_use "$candidate" "$default_version" >/dev/null && __sdkman_echo_yellow "Restored $candidate version to $default_version (default)" else __sdkman_echo_yellow "No default version of $candidate was found" fi } function __sdkman_env_each_candidate() { local -r filepath=$1 local -r func=$2 local normalised_line while IFS= read -r line || [[ -n "$line" ]]; do normalised_line="$(__sdkman_normalise "$line")" __sdkman_is_blank_line "$normalised_line" && continue if ! __sdkman_matches_candidate_format "$normalised_line"; then __sdkman_echo_red "Invalid candidate format!" echo "" __sdkman_echo_yellow "Expected 'candidate=version' but found '$normalised_line'" return 1 fi $func "${normalised_line%=*}" "${normalised_line#*=}" || return done < "$filepath" } function __sdkman_is_symlink() { [[ -h "$1" ]] } function __sdkman_is_blank_line() { [[ -z "$1" ]] } function __sdkman_normalise() { local -r line_without_comments="${1/\#*/}" echo "${line_without_comments//[[:space:]]/}" } function __sdkman_matches_candidate_format() { [[ "$1" =~ ^[[:lower:]]+\=.+$ ]] } ================================================ FILE: src/main/bash/sdkman-flush.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_flush() { local qualifier="$1" case "$qualifier" in version) if [[ -f "${SDKMAN_DIR}/var/version" ]]; then rm -f "${SDKMAN_DIR}/var/version" __sdkman_echo_green "Version file has been flushed." fi ;; temp) __sdkman_cleanup_folder "tmp" ;; tmp) __sdkman_cleanup_folder "tmp" ;; metadata) __sdkman_cleanup_folder "var/metadata" ;; *) __sdkman_cleanup_folder "tmp" __sdkman_cleanup_folder "var/metadata" ;; esac } function __sdkman_cleanup_folder() { local folder="$1" local sdkman_cleanup_dir local sdkman_cleanup_disk_usage local sdkman_cleanup_count sdkman_cleanup_dir="${SDKMAN_DIR}/${folder}" sdkman_cleanup_disk_usage=$(du -sh "$sdkman_cleanup_dir") sdkman_cleanup_count=$(ls -1 "$sdkman_cleanup_dir" | wc -l) rm -rf "$sdkman_cleanup_dir" mkdir "$sdkman_cleanup_dir" __sdkman_echo_green "${sdkman_cleanup_count} archive(s) flushed, freeing ${sdkman_cleanup_disk_usage}." } ================================================ FILE: src/main/bash/sdkman-help.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_help() { __sdkman_deprecation_notice "help" __sdkman_echo_no_colour "" __sdkman_echo_no_colour "Usage: sdk [candidate] [version]" __sdkman_echo_no_colour " sdk offline " __sdkman_echo_no_colour "" __sdkman_echo_no_colour " commands:" __sdkman_echo_no_colour " install or i [version] [local-path]" __sdkman_echo_no_colour " uninstall or rm " __sdkman_echo_no_colour " list or ls [candidate]" __sdkman_echo_no_colour " use or u " __sdkman_echo_no_colour " config" __sdkman_echo_no_colour " default or d [version]" __sdkman_echo_no_colour " home or h " __sdkman_echo_no_colour " env or e [init|install|clear]" __sdkman_echo_no_colour " current or c [candidate]" __sdkman_echo_no_colour " upgrade or ug [candidate]" __sdkman_echo_no_colour " version or v" __sdkman_echo_no_colour " help" __sdkman_echo_no_colour " offline [enable|disable]" if [[ "$sdkman_selfupdate_feature" == "true" ]]; then __sdkman_echo_no_colour " selfupdate [force]" fi __sdkman_echo_no_colour " update" __sdkman_echo_no_colour " flush [tmp|metadata|version]" __sdkman_echo_no_colour "" __sdkman_echo_no_colour " candidate : the SDK to install: groovy, scala, grails, gradle, kotlin, etc." __sdkman_echo_no_colour " use list command for comprehensive list of candidates" __sdkman_echo_no_colour " eg: \$ sdk list" __sdkman_echo_no_colour " version : where optional, defaults to latest stable if not provided" __sdkman_echo_no_colour " eg: \$ sdk install groovy" __sdkman_echo_no_colour " local-path : optional path to an existing local installation" __sdkman_echo_no_colour " eg: \$ sdk install groovy 2.4.13-local /opt/groovy-2.4.13" __sdkman_echo_no_colour "" } ================================================ FILE: src/main/bash/sdkman-home.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_home() { __sdkman_deprecation_notice "home" local candidate version candidate="$1" version="$2" __sdkman_check_version_present "$version" || return 1 __sdkman_check_candidate_present "$candidate" || return 1 __sdkman_determine_version "$candidate" "$version" || return 1 if [[ ! -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" ]]; then echo "" __sdkman_echo_red "Stop! Candidate version is not installed." echo "" __sdkman_echo_yellow "Tip: Run the following to install this version" echo "" __sdkman_echo_yellow "$ sdk install ${candidate} ${version}" return 1 fi echo -n "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" } ================================================ FILE: src/main/bash/sdkman-init.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # set env vars if not set if [ -z "$SDKMAN_CANDIDATES_API" ]; then export SDKMAN_CANDIDATES_API="@SDKMAN_CANDIDATES_API@" fi if [ -z "$SDKMAN_BROKER_API" ]; then export SDKMAN_BROKER_API="@SDKMAN_BROKER_API@" fi if [ -z "$SDKMAN_DIR" ]; then export SDKMAN_DIR="$HOME/.sdkman" fi # Load the sdkman config if it exists. if [ -f "${SDKMAN_DIR}/etc/config" ]; then source "${SDKMAN_DIR}/etc/config" fi # Read the platform file SDKMAN_PLATFORM="$(cat "${SDKMAN_DIR}/var/platform")" export SDKMAN_PLATFORM # OS specific support (must be 'true' or 'false'). cygwin=false darwin=false solaris=false freebsd=false SDKMAN_KERNEL="$(uname -s)" case "${SDKMAN_KERNEL}" in CYGWIN*) cygwin=true ;; Darwin*) darwin=true ;; SunOS*) solaris=true ;; FreeBSD*) freebsd=true esac # Determine shell zsh_shell=false bash_shell=false if [[ -n "$ZSH_VERSION" ]]; then zsh_shell=true elif [[ -n "$BASH_VERSION" ]]; then bash_shell=true fi # Source sdkman module scripts and extension files. # # Extension files are prefixed with 'sdkman-' and found in the ext/ folder. # Use this if extensions are written with the functional approach and want # to use functions in the main sdkman script. For more details, refer to # . OLD_IFS="$IFS" IFS=$'\n' scripts=($(find "${SDKMAN_DIR}/src" "${SDKMAN_DIR}/ext" -type f -name 'sdkman-*.sh')) for f in "${scripts[@]}"; do source "$f" done IFS="$OLD_IFS" unset OLD_IFS scripts f # Create upgrade delay file if it doesn't exist if [[ ! -f "${SDKMAN_DIR}/var/delay_upgrade" ]]; then touch "${SDKMAN_DIR}/var/delay_upgrade" fi # set curl connect-timeout and max-time if [[ -z "$sdkman_curl_connect_timeout" ]]; then sdkman_curl_connect_timeout=7; fi if [[ -z "$sdkman_curl_max_time" ]]; then sdkman_curl_max_time=10; fi # set curl retry if [[ -z "${sdkman_curl_retry}" ]]; then sdkman_curl_retry=0; fi # set curl retry max time in seconds if [[ -z "${sdkman_curl_retry_max_time}" ]]; then sdkman_curl_retry_max_time=60; fi # set curl to continue downloading automatically if [[ -z "${sdkman_curl_continue}" ]]; then sdkman_curl_continue=true; fi # read list of candidates and set array SDKMAN_CANDIDATES_CACHE="${SDKMAN_DIR}/var/candidates" SDKMAN_CANDIDATES_CSV=$(<"$SDKMAN_CANDIDATES_CACHE") __sdkman_echo_debug "Setting candidates csv: $SDKMAN_CANDIDATES_CSV" if [[ "$zsh_shell" == 'true' ]]; then SDKMAN_CANDIDATES=(${(s:,:)SDKMAN_CANDIDATES_CSV}) else IFS=',' read -a SDKMAN_CANDIDATES <<< "${SDKMAN_CANDIDATES_CSV}" fi export SDKMAN_CANDIDATES_DIR="${SDKMAN_DIR}/candidates" for candidate_name in "${SDKMAN_CANDIDATES[@]}"; do candidate_dir="${SDKMAN_CANDIDATES_DIR}/${candidate_name}/current" if [[ -h "$candidate_dir" || -d "${candidate_dir}" ]]; then __sdkman_export_candidate_home "$candidate_name" "$candidate_dir" __sdkman_prepend_candidate_to_path "$candidate_dir" fi done unset candidate_name candidate_dir export PATH # source completion scripts if [[ "$sdkman_auto_complete" == 'true' ]]; then if [[ "$zsh_shell" == 'true' ]]; then # initialize zsh completions (if not already done) if ! (( $+functions[compdef] )) ; then autoload -Uz compinit if [[ $ZSH_DISABLE_COMPFIX == 'true' ]]; then compinit -u -C else compinit fi fi autoload -U bashcompinit bashcompinit source "${SDKMAN_DIR}/contrib/completion/bash/sdk" __sdkman_echo_debug "ZSH completion script loaded..." elif [[ "$bash_shell" == 'true' ]]; then source "${SDKMAN_DIR}/contrib/completion/bash/sdk" __sdkman_echo_debug "Bash completion script loaded..." else __sdkman_echo_debug "No completion scripts found for $SHELL" fi fi if [[ "$sdkman_auto_env" == "true" ]]; then if [[ "$zsh_shell" == "true" ]]; then function sdkman_auto_env() { if [[ -n $SDKMAN_ENV ]] && [[ ! $PWD =~ ^$SDKMAN_ENV ]]; then sdk env clear fi if [[ -f .sdkmanrc ]]; then sdk env fi } chpwd_functions+=(sdkman_auto_env) else function sdkman_auto_env() { if [[ -n $SDKMAN_ENV ]] && [[ ! $PWD =~ ^$SDKMAN_ENV ]]; then sdk env clear fi if [[ "$SDKMAN_OLD_PWD" != "$PWD" ]] && [[ -f ".sdkmanrc" ]]; then sdk env fi export SDKMAN_OLD_PWD="$PWD" } trimmed_prompt_command="${PROMPT_COMMAND%"${PROMPT_COMMAND##*[![:space:]]}"}" [[ -z "$trimmed_prompt_command" ]] && PROMPT_COMMAND="sdkman_auto_env" || PROMPT_COMMAND="${trimmed_prompt_command%\;};sdkman_auto_env" fi sdkman_auto_env fi ================================================ FILE: src/main/bash/sdkman-install.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_install() { local candidate version folder candidate="$1" version="$2" folder="$3" __sdkman_check_candidate_present "$candidate" || return 1 __sdkman_determine_version "$candidate" "$version" "$folder" || return 1 if [[ -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${VERSION}" || -L "${SDKMAN_CANDIDATES_DIR}/${candidate}/${VERSION}" ]]; then echo "" __sdkman_echo_yellow "${candidate} ${VERSION} is already installed." return 0 fi if [[ ${VERSION_VALID} == 'valid' ]]; then __sdkman_determine_current_version "$candidate" __sdkman_install_candidate_version "$candidate" "$VERSION" || return 1 if [[ "$sdkman_auto_answer" != 'true' && "$auto_answer_upgrade" != 'true' && -n "$CURRENT" ]]; then __sdkman_echo_confirm "Do you want ${candidate} ${VERSION} to be set as default? (Y/n): " read USE fi if [[ -z "$USE" || "$USE" == "y" || "$USE" == "Y" ]]; then echo "" __sdkman_echo_green "Setting ${candidate} ${VERSION} as default." __sdkman_link_candidate_version "$candidate" "$VERSION" __sdkman_add_to_path "$candidate" fi return 0 elif [[ "$VERSION_VALID" == 'invalid' && -n "$folder" ]]; then __sdkman_install_local_version "$candidate" "$VERSION" "$folder" || return 1 else echo "" __sdkman_echo_red "Stop! $1 is not a valid ${candidate} version." return 1 fi } function __sdkman_install_candidate_version() { local candidate version candidate="$1" version="$2" __sdkman_download "$candidate" "$version" || return 1 __sdkman_echo_green "Installing: ${candidate} ${version}" mkdir -p "${SDKMAN_CANDIDATES_DIR}/${candidate}" rm -rf "${SDKMAN_DIR}/tmp/out" unzip -oq "${SDKMAN_DIR}/tmp/${candidate}-${version}.zip" -d "${SDKMAN_DIR}/tmp/out" mv -f "$SDKMAN_DIR"/tmp/out/* "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" __sdkman_echo_green "Done installing!" echo "" } function __sdkman_install_local_version() { local candidate version folder version_length version_length_max version_length_max=15 candidate="$1" version="$2" folder="$3" # Validate max length of version version_length=${#version} __sdkman_echo_debug "Validating that actual version length ($version_length) does not exceed max ($version_length_max)" if [[ $version_length -gt $version_length_max ]]; then __sdkman_echo_red "Invalid version! ${version} with length ${version_length} exceeds max of ${version_length_max}!" return 1 fi mkdir -p "${SDKMAN_CANDIDATES_DIR}/${candidate}" # handle relative paths if [[ "$folder" != /* ]]; then folder="$(pwd)/$folder" fi if [[ -d "$folder" ]]; then __sdkman_echo_green "Linking ${candidate} ${version} to ${folder}" ln -s "$folder" "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" __sdkman_echo_green "Done installing!" else __sdkman_echo_red "Invalid path! Refusing to link ${candidate} ${version} to ${folder}." return 1 fi echo "" } function __sdkman_download() { local candidate version candidate="$1" version="$2" metadata_folder="${SDKMAN_DIR}/var/metadata" mkdir -p "${metadata_folder}" local platform_parameter="$SDKMAN_PLATFORM" local download_url="${SDKMAN_BROKER_API}/download/${candidate}/${version}/${platform_parameter}" local base_name="${candidate}-${version}" local tmp_headers_file="${SDKMAN_DIR}/tmp/${base_name}.headers.tmp" local headers_file="${metadata_folder}/${base_name}.headers" export local binary_input="${SDKMAN_DIR}/tmp/${base_name}.bin" export local zip_output="${SDKMAN_DIR}/tmp/${base_name}.zip" echo "" __sdkman_echo_no_colour "Downloading: ${candidate} ${version}" echo "" __sdkman_echo_no_colour "In progress..." echo "" # download binary __sdkman_secure_curl_download "${download_url}" --output "${binary_input}" --dump-header "${tmp_headers_file}" grep '^X-Sdkman' "${tmp_headers_file}" > "${headers_file}" __sdkman_echo_debug "Downloaded binary to: ${binary_input} (HTTP headers written to: ${headers_file})" # post-installation hook: implements function __sdkman_post_installation_hook # responsible for taking `binary_input` and producing `zip_output` local post_installation_hook="${SDKMAN_DIR}/tmp/hook_post_${candidate}_${version}.sh" __sdkman_echo_debug "Get post-installation hook: ${SDKMAN_CANDIDATES_API}/hooks/post/${candidate}/${version}/${platform_parameter}" __sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/hooks/post/${candidate}/${version}/${platform_parameter}" >| "$post_installation_hook" __sdkman_echo_debug "Copy remote post-installation hook: ${post_installation_hook}" source "$post_installation_hook" __sdkman_post_installation_hook || return 1 __sdkman_echo_debug "Processed binary as: $zip_output" __sdkman_echo_debug "Completed post-installation hook..." __sdkman_validate_zip "${zip_output}" || return 1 __sdkman_checksum_zip "${zip_output}" "${headers_file}" || return 1 echo "" } function __sdkman_validate_zip() { local zip_archive zip_ok zip_archive="$1" zip_ok=$(unzip -t "$zip_archive" | grep 'No errors detected in compressed data') if [ -z "$zip_ok" ]; then rm -f "$zip_archive" echo "" __sdkman_echo_red "Stop! The archive was corrupt and has been removed! Please try installing again." return 1 fi } function __sdkman_checksum_zip() { local -r zip_archive="$1" local -r headers_file="$2" local algorithm checksum cmd local shasum_avail=false local md5sum_avail=false if [ -z "${headers_file}" ]; then echo "" __sdkman_echo_debug "Skipping checksum for cached artifact" return elif [ ! -f "${headers_file}" ]; then echo "" __sdkman_echo_yellow "Metadata file not found at '${headers_file}', skipping checksum..." return fi if [[ "$sdkman_checksum_enable" != "true" ]]; then echo "" __sdkman_echo_yellow "Checksums are disabled, skipping verification..." return fi #Check for the appropriate checksum tools if command -v shasum > /dev/null 2>&1; then shasum_avail=true fi if command -v md5sum > /dev/null 2>&1; then md5sum_avail=true fi while IFS= read -r line; do algorithm=$(echo $line | sed -n 's/^X-Sdkman-Checksum-\(.*\):.*$/\1/p' | tr '[:lower:]' '[:upper:]') checksum=$(echo $line | sed -n 's/^X-Sdkman-Checksum-.*:\(.*\)$/\1/p' | tr -cd '[:alnum:]') if [[ -n ${algorithm} && -n ${checksum} ]]; then if [[ "$algorithm" =~ 'SHA' && "$shasum_avail" == 'true' ]]; then cmd="echo \"${checksum} *${zip_archive}\" | shasum --check --quiet" elif [[ "$algorithm" =~ 'MD5' && "$md5sum_avail" == 'true' ]]; then cmd="echo \"${checksum} ${zip_archive}\" | md5sum --check --quiet" fi if [[ -n $cmd ]]; then __sdkman_echo_no_colour "Verifying artifact: ${zip_archive} (${algorithm}:${checksum})" if ! eval "$cmd"; then rm -f "$zip_archive" echo "" __sdkman_echo_red "Stop! An invalid checksum was detected and the archive removed! Please try re-installing." return 1 fi else __sdkman_echo_no_colour "Not able to perform checksum verification at this time." fi fi done < "${headers_file}" } ================================================ FILE: src/main/bash/sdkman-list.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_list() { local candidate="$1" if [[ -z "$candidate" ]]; then __sdkman_list_candidates else __sdkman_list_versions "$candidate" fi } function __sdkman_list_candidates() { if [[ "$SDKMAN_AVAILABLE" == "false" ]]; then __sdkman_echo_red "This command is not available while offline." else __sdkman_echo_paged "$(__sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/candidates/list")" fi } function __sdkman_list_versions() { local candidate versions_csv candidate="$1" versions_csv="$(__sdkman_build_version_csv "$candidate")" __sdkman_determine_current_version "$candidate" if [[ "$SDKMAN_AVAILABLE" == "false" ]]; then __sdkman_offline_list "$candidate" "$versions_csv" else __sdkman_echo_paged "$(__sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/candidates/${candidate}/${SDKMAN_PLATFORM}/versions/list?current=${CURRENT}&installed=${versions_csv}")" fi } function __sdkman_build_version_csv() { local candidate versions_csv candidate="$1" versions_csv="" if [[ -d "${SDKMAN_CANDIDATES_DIR}/${candidate}" ]]; then for version in $(find "${SDKMAN_CANDIDATES_DIR}/${candidate}" -maxdepth 1 -mindepth 1 \( -type l -o -type d \) -exec basename '{}' \; | sort -r); do if [[ "$version" != 'current' ]]; then versions_csv="${version},${versions_csv}" fi done versions_csv=${versions_csv%?} fi echo "$versions_csv" } function __sdkman_offline_list() { local candidate versions_csv candidate="$1" versions_csv="$2" __sdkman_echo_no_colour "--------------------------------------------------------------------------------" __sdkman_echo_yellow "Offline: only showing installed ${candidate} versions" __sdkman_echo_no_colour "--------------------------------------------------------------------------------" local versions=($(echo ${versions_csv//,/ })) for ((i = ${#versions} - 1; i >= 0; i--)); do if [[ -n "${versions[${i}]}" ]]; then if [[ "${versions[${i}]}" == "$CURRENT" ]]; then __sdkman_echo_no_colour " > ${versions[${i}]}" else __sdkman_echo_no_colour " * ${versions[${i}]}" fi fi done if [[ -z "${versions[@]}" ]]; then __sdkman_echo_yellow " None installed!" fi __sdkman_echo_no_colour "--------------------------------------------------------------------------------" __sdkman_echo_no_colour "* - installed " __sdkman_echo_no_colour "> - currently in use " __sdkman_echo_no_colour "--------------------------------------------------------------------------------" } ================================================ FILE: src/main/bash/sdkman-main.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function ___sdkman_help() { if [[ -f "$SDKMAN_DIR/libexec/help" ]]; then "$SDKMAN_DIR/libexec/help" else __sdk_help fi } function sdk() { COMMAND="$1" QUALIFIER="$2" case "$COMMAND" in l) COMMAND="list" ;; ls) COMMAND="list" ;; v) COMMAND="version" ;; u) COMMAND="use" ;; i) COMMAND="install" ;; rm) COMMAND="uninstall" ;; c) COMMAND="current" ;; ug) COMMAND="upgrade" ;; d) COMMAND="default" ;; h) COMMAND="home" ;; e) COMMAND="env" ;; esac # # Various sanity checks and default settings # # Check candidates cache if [[ "$COMMAND" != "update" ]]; then ___sdkman_check_candidates_cache "$SDKMAN_CANDIDATES_CACHE" || return 1 fi # Always presume internet availability SDKMAN_AVAILABLE="true" if [ -z "$SDKMAN_OFFLINE_MODE" ]; then SDKMAN_OFFLINE_MODE="false" fi # ...unless proven otherwise __sdkman_update_service_availability # Load the sdkman config if it exists. if [ -f "${SDKMAN_DIR}/etc/config" ]; then source "${SDKMAN_DIR}/etc/config" fi # no command provided if [[ -z "$COMMAND" ]]; then ___sdkman_help return 1 fi # Check if it is a valid command CMD_FOUND="" if [[ "$COMMAND" != "selfupdate" || "$sdkman_selfupdate_feature" == "true" ]]; then CMD_TARGET="${SDKMAN_DIR}/src/sdkman-${COMMAND}.sh" if [[ -f "$CMD_TARGET" ]]; then CMD_FOUND="$CMD_TARGET" fi fi # Check if it is a sourced function CMD_TARGET="${SDKMAN_DIR}/ext/sdkman-${COMMAND}.sh" if [[ -f "$CMD_TARGET" ]]; then CMD_FOUND="$CMD_TARGET" fi # couldn't find the command if [[ -z "$CMD_FOUND" ]]; then echo "" __sdkman_echo_red "Invalid command: $COMMAND" echo "" ___sdkman_help fi # Validate offline qualifier if [[ "$COMMAND" == "offline" && -n "$QUALIFIER" && -z $(echo "enable disable" | grep -w "$QUALIFIER") ]]; then echo "" __sdkman_echo_red "Stop! $QUALIFIER is not a valid offline mode." fi # Store the return code of the requested command local final_rc=0 # Native commands found under libexec local native_command="${SDKMAN_DIR}/libexec/${COMMAND}" if [[ "$sdkman_native_enable" == 'true' && -f "$native_command" ]]; then "$native_command" "${@:2}" elif [ -n "$CMD_FOUND" ]; then # Check whether the candidate exists if [[ -n "$QUALIFIER" && "$COMMAND" != "help" && "$COMMAND" != "offline" && "$COMMAND" != "flush" && "$COMMAND" != "selfupdate" && "$COMMAND" != "env" && "$COMMAND" != "completion" && "$COMMAND" != "edit" && "$COMMAND" != "home" && -z $(echo ${SDKMAN_CANDIDATES[@]} | grep -w "$QUALIFIER") ]]; then echo "" __sdkman_echo_red "Stop! $QUALIFIER is not a valid candidate." return 1 fi # Internal commands use underscores rather than hyphens local converted_command_name=$(echo "$COMMAND" | tr '-' '_') # Available as a shell function __sdk_"$converted_command_name" "${@:2}" fi final_rc=$? return $final_rc } ================================================ FILE: src/main/bash/sdkman-offline.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_offline() { local mode="$1" if [[ -z "$mode" || "$mode" == "enable" ]]; then SDKMAN_OFFLINE_MODE="true" __sdkman_echo_green "Offline mode enabled." fi if [[ "$mode" == "disable" ]]; then SDKMAN_OFFLINE_MODE="false" __sdkman_echo_green "Online mode re-enabled!" fi } ================================================ FILE: src/main/bash/sdkman-path-helpers.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdkman_path_contains() { local candidate exists candidate="$1" exists="$(echo "$PATH" | grep "$candidate")" if [[ -n "$exists" ]]; then echo 'true' else echo 'false' fi } function __sdkman_add_to_path() { local candidate present candidate="$1" present=$(__sdkman_path_contains "$candidate") if [[ "$present" == 'false' ]]; then PATH="$SDKMAN_CANDIDATES_DIR/$candidate/current/bin:$PATH" fi } function __sdkman_set_candidate_home() { local candidate version upper_candidate candidate="$1" version="$2" upper_candidate=$(echo "$candidate" | tr '[:lower:]' '[:upper:]') export "${upper_candidate}_HOME"="${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" } function __sdkman_export_candidate_home() { local candidate_name candidate_dir ucase_candidate_name candidate_name="$1" candidate_dir="$2" if [ "$zsh_shell" = true ]; then ucase_candidate_name="${candidate_name:u}" elif [ "$bash_shell" = true ]; then ucase_candidate_name="${candidate_name^^}" else ucase_candidate_name="$(printf %s "$candidate_name" | tr '[:lower:]' '[:upper:]')" fi export "${ucase_candidate_name}_HOME=$candidate_dir" } function __sdkman_prepend_candidate_to_path() { local candidate_dir candidate_dir="$1" if [ -d "${candidate_dir}/bin" ]; then candidate_dir="${candidate_dir}/bin" fi [[ ":$PATH:" == *":$candidate_dir:"* ]] || PATH="${candidate_dir}:${PATH}" } function __sdkman_link_candidate_version() { local candidate version candidate="$1" version="$2" # Change the 'current' symlink for the candidate, hence affecting all shells. if [[ -L "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" || -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" ]]; then rm -rf "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" fi ln -s "${version}" "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" } ================================================ FILE: src/main/bash/sdkman-selfupdate.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_selfupdate() { local force_selfupdate local sdkman_script_version_api local sdkman_native_version_api if [[ "$SDKMAN_AVAILABLE" == "false" ]]; then echo "This command is not available while offline." return 1 fi if [[ "$sdkman_beta_channel" == "true" ]]; then sdkman_script_version_api="${SDKMAN_CANDIDATES_API}/broker/version/sdkman/script/beta" sdkman_native_version_api="${SDKMAN_CANDIDATES_API}/broker/version/sdkman/native/beta" else sdkman_script_version_api="${SDKMAN_CANDIDATES_API}/broker/version/sdkman/script/stable" sdkman_native_version_api="${SDKMAN_CANDIDATES_API}/broker/version/sdkman/native/stable" fi sdkman_remote_script_version=$(__sdkman_secure_curl "$sdkman_script_version_api") sdkman_remote_native_version=$(__sdkman_secure_curl "$sdkman_native_version_api") sdkman_local_script_version=$(< "$SDKMAN_DIR/var/version") sdkman_local_native_version=$(< "$SDKMAN_DIR/var/version_native") __sdkman_echo_debug "Script: local version: $sdkman_local_script_version; remote version: $sdkman_remote_script_version" __sdkman_echo_debug "Native: local version: $sdkman_local_native_version; remote version: $sdkman_remote_native_version" force_selfupdate="$1" export sdkman_debug_mode if [[ "$sdkman_local_script_version" == "$sdkman_remote_script_version" && "$sdkman_local_native_version" == "$sdkman_remote_native_version" && "$force_selfupdate" != "force" ]]; then echo "No update available at this time." elif [[ "$sdkman_beta_channel" == "true" ]]; then __sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/selfupdate/beta/${SDKMAN_PLATFORM}" | bash else __sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/selfupdate/stable/${SDKMAN_PLATFORM}" | bash fi } ================================================ FILE: src/main/bash/sdkman-uninstall.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_uninstall() { __sdkman_deprecation_notice "uninstall" local candidate version current candidate="$1" version="$2" __sdkman_check_candidate_present "$candidate" || return 1 __sdkman_check_version_present "$version" || return 1 current=$(readlink "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" | sed "s!${SDKMAN_CANDIDATES_DIR}/${candidate}/!!g") if [[ -L "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" && "$version" == "$current" ]]; then echo "" __sdkman_echo_green "Deselecting ${candidate} ${version}..." unlink "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" fi echo "" if [ -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" ]; then __sdkman_echo_green "Uninstalling ${candidate} ${version}..." rm -rf "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" else __sdkman_echo_red "${candidate} ${version} is not installed." fi } ================================================ FILE: src/main/bash/sdkman-update.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_update() { local candidates_uri="${SDKMAN_CANDIDATES_API}/candidates/all" __sdkman_echo_debug "Using candidates endpoint: $candidates_uri" local fresh_candidates_csv=$(__sdkman_secure_curl_with_timeouts "$candidates_uri") __sdkman_echo_debug "Local candidates: $SDKMAN_CANDIDATES_CSV" __sdkman_echo_debug "Fetched candidates: $fresh_candidates_csv" if [[ -n "${fresh_candidates_csv}" ]] && ! grep -iq 'html' <<< "${fresh_candidates_csv}"; then __sdkman_echo_debug "Fresh and cached candidate lengths: ${#fresh_candidates_csv} ${#SDKMAN_CANDIDATES_CSV}" local fresh_candidates combined_candidates diff_candidates if [[ "${zsh_shell}" == 'true' ]]; then fresh_candidates=(${(s:,:)fresh_candidates_csv}) else IFS=',' read -a fresh_candidates <<< "${fresh_candidates_csv}" fi combined_candidates=("${fresh_candidates[@]}" "${SDKMAN_CANDIDATES[@]}") diff_candidates=($(printf $'%s\n' "${combined_candidates[@]}" | sort | uniq -u)) if ((${#diff_candidates[@]})); then local delta delta=("${fresh_candidates[@]}" "${diff_candidates[@]}") delta=($(printf $'%s\n' "${delta[@]}" | sort | uniq -d)) if ((${#delta[@]})); then __sdkman_echo_green "\nAdding new candidates(s): ${delta[*]}" fi delta=("${SDKMAN_CANDIDATES[@]}" "${diff_candidates[@]}") delta=($(printf $'%s\n' "${delta[@]}" | sort | uniq -d)) if ((${#delta[@]})); then __sdkman_echo_green "\nRemoving obsolete candidates(s): ${delta[*]}" fi echo "${fresh_candidates_csv}" >| "${SDKMAN_CANDIDATES_CACHE}" __sdkman_echo_yellow $'\nPlease open a new terminal now...' else touch "${SDKMAN_CANDIDATES_CACHE}" __sdkman_echo_green $'\nNo new candidates found at this time.' fi fi } ================================================ FILE: src/main/bash/sdkman-upgrade.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_upgrade() { local all candidates candidate upgradable installed_count upgradable_count upgradable_candidates if [ -n "$1" ]; then all=false candidates=$1 else all=true if [[ "$zsh_shell" == 'true' ]]; then candidates=(${SDKMAN_CANDIDATES[@]}) else candidates=${SDKMAN_CANDIDATES[@]} fi fi installed_count=0 upgradable_count=0 echo "" for candidate in ${candidates}; do upgradable="$(__sdkman_determine_upgradable_version "$candidate")" case $? in 1) $all || __sdkman_echo_red "Not using any version of ${candidate}" ;; 2) echo "" __sdkman_echo_red "Stop! Could not get remote version of ${candidate}" return 1 ;; *) if [ -n "$upgradable" ]; then [ ${upgradable_count} -eq 0 ] && __sdkman_echo_no_colour "Available defaults:" __sdkman_echo_no_colour "$upgradable" ((upgradable_count += 1)) upgradable_candidates=(${upgradable_candidates[@]} $candidate) fi ((installed_count += 1)) ;; esac done if $all; then if [ ${installed_count} -eq 0 ]; then __sdkman_echo_no_colour 'No candidates are in use' elif [ ${upgradable_count} -eq 0 ]; then __sdkman_echo_no_colour "All candidates are up-to-date" fi elif [ ${upgradable_count} -eq 0 ]; then __sdkman_echo_no_colour "${candidate} is up-to-date" fi if [ ${upgradable_count} -gt 0 ]; then echo "" if [[ "$sdkman_auto_answer" != 'true' ]]; then __sdkman_echo_confirm "Use prescribed default version(s)? (Y/n): " read UPGRADE_ALL fi export auto_answer_upgrade='true' if [[ -z "$UPGRADE_ALL" || "$UPGRADE_ALL" == "y" || "$UPGRADE_ALL" == "Y" ]]; then # Using array for bash & zsh compatibility for ((i = 0; i <= ${#upgradable_candidates[*]}; i++)); do upgradable_candidate="${upgradable_candidates[${i}]}" # Filter empty elements (in bash arrays are zero index based, in zsh they are 1 based) if [[ -n "$upgradable_candidate" ]]; then __sdk_install $upgradable_candidate fi done fi unset auto_answer_upgrade fi } function __sdkman_determine_upgradable_version() { local candidate local_versions remote_default_version candidate="$1" # Resolve local versions local_versions="$(echo $(find "${SDKMAN_CANDIDATES_DIR}/${candidate}" -maxdepth 1 -mindepth 1 -type d -exec basename '{}' \; 2> /dev/null) | sed -e "s/ /, /g")" if [ ${#local_versions} -eq 0 ]; then return 1 fi # Resolve remote default version remote_default_version="$(__sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/candidates/default/${candidate}")" if [ -z "$remote_default_version" ]; then return 2 fi # Check upgradable or not if [ ! -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${remote_default_version}" ]; then __sdkman_echo_yellow "${candidate} (local: ${local_versions}; default: ${remote_default_version})" fi } ================================================ FILE: src/main/bash/sdkman-use.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_use() { local candidate version install candidate="$1" version="$2" __sdkman_check_version_present "$version" || return 1 __sdkman_check_candidate_present "$candidate" || return 1 if [[ ! -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}" ]]; then echo "" __sdkman_echo_red "Stop! Candidate version is not installed." echo "" __sdkman_echo_yellow "Tip: Run the following to install this version" echo "" __sdkman_echo_yellow "$ sdk install ${candidate} ${version}" return 1 fi # Just update the *_HOME and PATH for this shell. __sdkman_set_candidate_home "$candidate" "$version" if [[ $PATH =~ ${SDKMAN_CANDIDATES_DIR}/${candidate}/([^/]+) ]]; then local matched_version if [[ "$zsh_shell" == "true" ]]; then matched_version=${match[1]} else matched_version=${BASH_REMATCH[1]} fi export PATH=${PATH//${SDKMAN_CANDIDATES_DIR}\/${candidate}\/${matched_version}/${SDKMAN_CANDIDATES_DIR}\/${candidate}\/${version}} fi if [[ ! (-L "${SDKMAN_CANDIDATES_DIR}/${candidate}/current" || -d "${SDKMAN_CANDIDATES_DIR}/${candidate}/current") ]]; then __sdkman_echo_green "Setting ${candidate} version ${version} as default." __sdkman_link_candidate_version "$candidate" "$version" fi echo "" __sdkman_echo_green "Using ${candidate} version ${version} in this shell." } ================================================ FILE: src/main/bash/sdkman-utils.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdkman_echo_debug() { if [[ "$sdkman_debug_mode" == 'true' ]]; then echo "$1" fi } function __sdkman_secure_curl() { if [[ "${sdkman_insecure_ssl}" == 'true' ]]; then curl --insecure --silent --location "$1" else curl --silent --location "$1" fi } function __sdkman_secure_curl_download() { local curl_params curl_params=('--progress-bar' '--location') if [[ "${sdkman_debug_mode}" == 'true' ]]; then curl_params+=('--verbose') fi if [[ "${sdkman_curl_continue}" == 'true' ]]; then curl_params+=('-C' '-') fi if [[ -n "${sdkman_curl_retry_max_time}" ]]; then curl_params+=('--retry-max-time' "${sdkman_curl_retry_max_time}") fi if [[ -n "${sdkman_curl_retry}" ]]; then curl_params+=('--retry' "${sdkman_curl_retry}") fi if [[ "${sdkman_insecure_ssl}" == 'true' ]]; then curl_params+=('--insecure') fi curl "${curl_params[@]}" "${@}" } function __sdkman_secure_curl_with_timeouts() { if [[ "${sdkman_insecure_ssl}" == 'true' ]]; then curl --insecure --silent --location --connect-timeout ${sdkman_curl_connect_timeout} --max-time ${sdkman_curl_max_time} "$1" else curl --silent --location --connect-timeout ${sdkman_curl_connect_timeout} --max-time ${sdkman_curl_max_time} "$1" fi } function __sdkman_echo_paged() { if [[ -n "$PAGER" ]]; then echo "$@" | eval "$PAGER" elif command -v less >& /dev/null; then echo "$@" | less else echo "$@" fi } function __sdkman_echo() { if [[ "$sdkman_colour_enable" == 'false' ]]; then echo -e "$2" else echo -e "\033[1;$1$2\033[0m" fi } function __sdkman_echo_red() { __sdkman_echo "31m" "$1" } function __sdkman_echo_no_colour() { echo "$1" } function __sdkman_echo_yellow() { __sdkman_echo "33m" "$1" } function __sdkman_echo_green() { __sdkman_echo "32m" "$1" } function __sdkman_echo_cyan() { __sdkman_echo "36m" "$1" } function __sdkman_echo_confirm() { if [[ "$sdkman_colour_enable" == 'false' ]]; then echo -n "$1" else echo -e -n "\033[1;33m$1\033[0m" fi } function __sdkman_deprecation_notice() { local message=" [Deprecation Notice]: This legacy '$1' command is replaced by a native implementation and it will be removed in a future release. Please follow the discussion here: https://github.com/sdkman/sdkman-cli/discussions/1332" if [[ "$sdkman_colour_enable" == 'false' ]]; then __sdkman_echo_no_colour "$message" else __sdkman_echo_yellow "$message" fi } ================================================ FILE: src/main/bash/sdkman-version.sh ================================================ #!/usr/bin/env bash # # Copyright 2021 Marco Vermeulen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function __sdk_version() { __sdkman_deprecation_notice "version" local version=$(cat $SDKMAN_DIR/var/version) echo "" __sdkman_echo_yellow "SDKMAN $version" } ================================================ FILE: src/test/groovy/sdkman/cucumber/RunCukeTests.groovy ================================================ package sdkman.cucumber import io.cucumber.junit.Cucumber import io.cucumber.junit.CucumberOptions import org.junit.runner.RunWith @RunWith(Cucumber) @CucumberOptions( strict = true, features = ["src/test/resources/features"], glue = ["sdkman.steps"], tags = ["not @manual", "not @review"] ) class RunCukeTests {} ================================================ FILE: src/test/groovy/sdkman/env/BashEnv.groovy ================================================ package sdkman.env import groovy.transform.ToString /** *

As part of the sdkman test suite we need to launch a bash shell and execute * multiple commands in it. This is tricky to do using Java's support for * working with external processes as the API can't tell you when a command * has finished executing.

*

This class provides some hacks that allow you to serially execute commands * in an external bash process in a fairly reliable manner and to retrieve the * output of those commands.

*/ @ToString(includeNames = true) class BashEnv { static final PROMPT = "" static final EXIT_CODE_CMD = 'echo "Exit code is: $?"' static final EXIT_CODE_PATTERN = ~/Exit code is: (\d+)\s*${PROMPT}?$/ private final Object outputLock = new Object() def exitCode def process def processOutput = new StringBuilder() def commandOutput // Command timeout in milliseconds def timeout = 5000 def workDir def env BashEnv(workDir, Map env) { this.workDir = workDir as File def basicPath = "/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/run/current-system/sw/bin" def localBinDir = "${workDir}/bin" def modifiedPath = "$localBinDir:$basicPath" env = env + [PS1: PROMPT, PATH: modifiedPath] this.env = env.collect { k, v -> k + '=' + v } } /** * Starts the external bash process. */ void start() { process = ["bash", "--noprofile", "--norc", "--posix", "-i", "-o", "noclobber"].execute(env, workDir) consumeProcessStream(process.inputStream) consumeProcessStream(process.errorStream) } /** * Stops the external bash process and waits for it to finish. */ void stop() { execute("exit") process.waitFor() } /** * Sends a command line to the external bash process and returns once the * command has finished executing. If the command is interactive and requires * input during it's execution (for example a y/n answer to a question) you * can provide that input as a list of strings. */ void execute(String cmdline, List inputs = []) { resetOutput() if (cmdline != "exit") { exitCode = null } process.outputStream << cmdline << "\n" process.outputStream.flush() if (cmdline != "exit") { for (input in inputs) { process.outputStream << input << "\n" } process.outputStream << EXIT_CODE_CMD << "\n" process.outputStream.flush() } def start = System.currentTimeMillis() while (cmdline != "exit") { Thread.sleep 100 synchronized (outputLock) { // Remove all the extraneous text that's not related to the // command's output. This includes the command string itself, // the 'echo' command to display the command's exit code, and // the exit code line. removeFromOutput(cmdline + "\n") removeFromOutput(PROMPT + EXIT_CODE_CMD + "\n") def str = processOutput.toString() def m = EXIT_CODE_PATTERN.matcher(str) if (m) { exitCode = m[0][1] // Remove this exit code line from the output. commandOutput = m.replaceAll('') break } // If the command times out, we should break out of the loop and // display whatever output has already been produced. if (System.currentTimeMillis() - start > timeout) { commandOutput = "ALERT! Command timed out. Last output was:\n\n${processOutput}" break } } } } /** * Returns the exit code of the last command that was executed. */ int getStatus() { if (!exitCode) throw new IllegalStateException("Did you run execute() before getting the status?") return exitCode.toInteger() } /** * Returns the text output (both stdout and stderr) of the last command * that was executed. */ String getOutput() { return commandOutput } /** * Clears the saved command output. */ void resetOutput() { synchronized (outputLock) { processOutput = new StringBuilder() } } private void consumeProcessStream(final InputStream stream) { char[] buffer = new char[256] Thread.start { def reader = new InputStreamReader(stream) def charsRead = 0 while (charsRead != -1) { charsRead = reader.read(buffer, 0, 256) if (charsRead > 0) { synchronized (outputLock) { processOutput.append(buffer, 0, charsRead) } } } } } private void removeFromOutput(String line) { synchronized (outputLock) { def pos = processOutput.indexOf(line) if (pos != -1) { processOutput.delete(pos, pos + line.size() - 1) } } } } ================================================ FILE: src/test/groovy/sdkman/env/CleanBashEnvBuilder.groovy ================================================ package sdkman.env class CleanBashEnvBuilder { private final File baseFolder String httpProxy static CleanBashEnvBuilder create(File baseFolder) { new CleanBashEnvBuilder(baseFolder) } private CleanBashEnvBuilder(File baseFolder) { this.baseFolder = baseFolder } CleanBashEnvBuilder withHttpProxy(String httpProxy) { this.httpProxy = httpProxy this } BashEnv build() { def env = [HOME: baseFolder.absolutePath] if (httpProxy) { env.put("http_proxy", httpProxy) } new BashEnv(baseFolder.absolutePath, env) } } ================================================ FILE: src/test/groovy/sdkman/env/SdkmanBashEnvBuilder.groovy ================================================ package sdkman.env import groovy.transform.ToString import sdkman.stubs.CurlStub import sdkman.stubs.UnameStub import sdkman.support.UnixUtils @ToString(includeNames = true) class SdkmanBashEnvBuilder { final BUILD_STAGE_DIR = "build/stage/sdkman-latest+hashme" final BUILD_BIN_DIR = "$BUILD_STAGE_DIR/bin" final BUILD_SRC_DIR = "$BUILD_STAGE_DIR/src" final BUILD_COMPLETION_DIR = "$BUILD_STAGE_DIR/contrib/completion/bash" //mandatory fields private final File baseFolder //optional fields with sensible defaults private Optional curlStub = Optional.empty() private Optional unameStub = Optional.empty() private List candidates = ['groovy', 'grails', 'java'] private String platform = UnixUtils.inferPlatform() private boolean offlineMode = false private String candidatesApi = "http://localhost:8080/2" private String brokerApi = "http://localhost:8080/2" private String jdkHome = "/path/to/my/jdk" private String httpProxy private String scriptVersion private String nativeVersion private boolean debugMode = true Map config = [ sdkman_auto_answer : 'false', sdkman_beta_channel: 'false', sdkman_native_enable: 'false', sdkman_selfupdate_feature: 'true' ] File sdkmanDir, sdkmanBinDir, sdkmanVarDir, sdkmanSrcDir, sdkmanEtcDir, sdkmanExtDir, sdkmanTmpDir, sdkmanCandidatesDir, sdkmanMetadataDir, sdkmanContribDir static SdkmanBashEnvBuilder create(File baseFolder) { new SdkmanBashEnvBuilder(baseFolder) } private SdkmanBashEnvBuilder(File baseFolder) { this.baseFolder = baseFolder } SdkmanBashEnvBuilder withCurlStub(CurlStub curlStub) { this.curlStub = Optional.of(curlStub) this } SdkmanBashEnvBuilder withUnameStub(UnameStub unameStub) { this.unameStub = Optional.of(unameStub) this } SdkmanBashEnvBuilder withPlatform(String platform) { this.platform = platform this } SdkmanBashEnvBuilder withCandidates(List candidates) { this.candidates = candidates this } SdkmanBashEnvBuilder withConfiguration(String key, String value) { config.put key, value this } SdkmanBashEnvBuilder withOfflineMode(boolean offlineMode) { this.offlineMode = offlineMode this } SdkmanBashEnvBuilder withCandidatesApi(String service) { this.candidatesApi = service this } SdkmanBashEnvBuilder withBrokerApi(String service) { this.brokerApi = service this } SdkmanBashEnvBuilder withJdkHome(String jdkHome) { this.jdkHome = jdkHome this } SdkmanBashEnvBuilder withHttpProxy(String httpProxy) { this.httpProxy = httpProxy this } SdkmanBashEnvBuilder withScriptVersion(String version) { this.scriptVersion = version this } SdkmanBashEnvBuilder withNativeVersion(String version) { this.nativeVersion = version this } SdkmanBashEnvBuilder withDebugMode(boolean debugMode) { this.debugMode = debugMode this } BashEnv build() { sdkmanDir = prepareDirectory(baseFolder, ".sdkman") sdkmanBinDir = prepareDirectory(sdkmanDir, "bin") sdkmanVarDir = prepareDirectory(sdkmanDir, "var") sdkmanSrcDir = prepareDirectory(sdkmanDir, "src") sdkmanEtcDir = prepareDirectory(sdkmanDir, "etc") sdkmanExtDir = prepareDirectory(sdkmanDir, "ext") sdkmanTmpDir = prepareDirectory(sdkmanDir, "tmp") sdkmanCandidatesDir = prepareDirectory(sdkmanDir, "candidates") sdkmanMetadataDir = prepareDirectory(sdkmanVarDir, "metadata") sdkmanContribDir = prepareDirectory(sdkmanDir, "contrib") curlStub.map { it.build() } unameStub.map { it.build() } initializeConfiguration(sdkmanEtcDir, config) initializeCandidates(sdkmanCandidatesDir, candidates) initializeCandidatesCache(sdkmanVarDir, candidates) initializePlatformDescriptor(sdkmanVarDir, platform) initializeScriptVersionFile(sdkmanVarDir, scriptVersion) initializeNativeVersionFile(sdkmanVarDir, nativeVersion) primeInitScript(sdkmanBinDir) primeModuleScripts(sdkmanSrcDir) primeBashCompletionScript(sdkmanContribDir) def env = [ SDKMAN_DIR : sdkmanDir.absolutePath, SDKMAN_CANDIDATES_DIR: sdkmanCandidatesDir.absolutePath, SDKMAN_OFFLINE_MODE : "$offlineMode", SDKMAN_CANDIDATES_API: candidatesApi, SDKMAN_BROKER_API : brokerApi, sdkman_debug_mode : Boolean.toString(debugMode), JAVA_HOME : jdkHome ] if (httpProxy) { env.put("http_proxy", httpProxy) } new BashEnv(baseFolder.absolutePath, env) } private prepareDirectory(File target, String directoryName) { def directory = new File(target, directoryName) directory.mkdirs() directory } private initializeScriptVersionFile(File folder, String version) { if (version) { new File(folder, "version") << version } } private initializeNativeVersionFile(File folder, String version) { if (version) { new File(folder, "version_native") << version } } private initializeCandidates(File folder, List candidates) { candidates.each { candidate -> new File(folder, candidate).mkdirs() } } private initializeCandidatesCache(File folder, List candidates) { def candidatesCache = new File(folder, "candidates") if (candidates) { candidatesCache << candidates.join(",") } else { candidatesCache << "" } } private initializePlatformDescriptor(File folder, String platform) { def platformDescriptor = new File(folder, "platform") platformDescriptor << platform } private initializeConfiguration(File targetFolder, Map config) { def configFile = new File(targetFolder, "config") config.each { key, value -> configFile << "$key=$value\n" } } private primeInitScript(File targetFolder) { def sourceInitScript = new File(BUILD_BIN_DIR, 'sdkman-init.sh') if (!sourceInitScript.exists()) throw new IllegalStateException("sdkman-init.sh has not been prepared for consumption.") def destInitScript = new File(targetFolder, "sdkman-init.sh") destInitScript << sourceInitScript.text destInitScript } private primeBashCompletionScript(File targetFolder) { def sourceCompletionScript = new File(BUILD_COMPLETION_DIR, 'sdk') if (!sourceCompletionScript.exists()) throw new IllegalStateException("sdk has not been prepared for consumption.") new FileTreeBuilder(targetFolder).with { completion { bash { sdk(sourceCompletionScript.text) } } } } private primeModuleScripts(File targetFolder) { for (f in new File(BUILD_SRC_DIR).listFiles()) { if (!(f.name in ['selfupdate.sh', 'install.sh', 'sdkman-init.sh'])) { new File(targetFolder, f.name) << f.text } } } } ================================================ FILE: src/test/groovy/sdkman/specs/CandidatesCacheUpdateFailureSpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification class CandidatesCacheUpdateFailureSpec extends SdkmanEnvSpecification { static final String CANDIDATES_API = "http://localhost:8080/2" static final String HEALTHCHECK_ENDPOINT = "$CANDIDATES_API/healthcheck" static final String CANDIDATES_ALL_ENDPOINT = "$CANDIDATES_API/candidates/all" File candidatesCache def setup() { candidatesCache = new File("${sdkmanDotDirectory}/var", "candidates") curlStub.primeWith(HEALTHCHECK_ENDPOINT, "echo dbfb025be9f97fda2052b5febcca0155") .primeWith(CANDIDATES_ALL_ENDPOINT, "echo html") sdkmanBashEnvBuilder.withConfiguration("sdkman_debug_mode", "true") } void "should not update candidates if error downloading candidate list"() { given: bash = sdkmanBashEnvBuilder .withCandidates([]) .build() and: bash.start() when: bash.execute("source $bootstrapScript") bash.execute("sdk update") then: !bash.output.contains('Fresh and cached candidate lengths') } } ================================================ FILE: src/test/groovy/sdkman/specs/CandidatesCacheUpdateSpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification class CandidatesCacheUpdateSpec extends SdkmanEnvSpecification { static final String CANDIDATES_API = "http://localhost:8080/2" static final String HEALTHCHECK_ENDPOINT = "$CANDIDATES_API/healthcheck" static final String CANDIDATES_ALL_ENDPOINT = "$CANDIDATES_API/candidates/all" File candidatesCache def setup() { candidatesCache = new File("${sdkmanDotDirectory}/var", "candidates") curlStub.primeWith(HEALTHCHECK_ENDPOINT, "echo dbfb025be9f97fda2052b5febcca0155") .primeWith(CANDIDATES_ALL_ENDPOINT, "echo groovy,scala") sdkmanBashEnvBuilder.withConfiguration("sdkman_debug_mode", "true") } void "should issue a warning and escape if cache is empty"() { given: bash = sdkmanBashEnvBuilder .withCandidates([]) .build() and: bash.start() when: bash.execute("source $bootstrapScript") bash.execute("sdk version") then: bash.output.contains('WARNING: Cache is corrupt. SDKMAN cannot be used until updated.') bash.output.contains('$ sdk update') and: !bash.output.contains("SDKMAN 5.0.0") } void "should log a success message if cache exists"() { given: bash = sdkmanBashEnvBuilder .withCandidates(['groovy']) .build() and: bash.start() when: bash.execute("source $bootstrapScript") bash.execute("sdk help") then: bash.output.contains('Using existing cache') } void "should bypass cache check if update command issued"() { given: bash = sdkmanBashEnvBuilder .withCandidates([]) .build() and: bash.start() when: bash.execute("source $bootstrapScript") bash.execute("sdk update") then: bash.output.contains('Adding new candidates(s): groovy scala') and: candidatesCache.text.trim() == "groovy,scala" } } ================================================ FILE: src/test/groovy/sdkman/specs/CompletionSpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification class CompletionSpec extends SdkmanEnvSpecification { static final String CANDIDATES_API = "http://localhost:8080/2" def "should complete the list of commands"() { given: bash = sdkmanBashEnvBuilder .withConfiguration("sdkman_auto_complete", "true") .build() bash.start() bash.execute("source $bootstrapScript") when: bash.execute("COMP_CWORD=1; COMP_WORDS=(sdk); _sdk") bash.execute('echo "\${COMPREPLY[@]}"') then: bash.output.contains("install uninstall list use config default home env current upgrade version help offline selfupdate update flush") } def "should complete the list of candidates"() { given: bash = sdkmanBashEnvBuilder .withCandidates(["java", "groovy"]) .withConfiguration("sdkman_auto_complete", "true") .build() bash.start() bash.execute("source $bootstrapScript") when: bash.execute("COMP_CWORD=2; COMP_WORDS=(sdk install); _sdk") bash.execute('echo "\${COMPREPLY[@]}"') then: bash.output.contains("java groovy") } def "should complete the list of Java versions"() { given: curlStub.primeWith("$CANDIDATES_API/candidates/java/darwinx64/versions/all", "echo 16.0.1.hs-adpt,17.0.0-tem") unameStub.forKernel("Darwin").forMachine("x86_64") bash = sdkmanBashEnvBuilder .withConfiguration("sdkman_auto_complete", "true") .withUnameStub(unameStub) .withPlatform("darwinx64") .build() bash.start() bash.execute("source $bootstrapScript") when: bash.execute("COMP_CWORD=3; COMP_WORDS=(sdk install java); _sdk") bash.execute('echo "\${COMPREPLY[@]}"') then: bash.output.contains("16.0.1.hs-adpt 17.0.0-tem") } } ================================================ FILE: src/test/groovy/sdkman/specs/ConfigCommandSpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification class ConfigCommandSpec extends SdkmanEnvSpecification { def "it should open the config in the system's default editor"() { given: bash = sdkmanBashEnvBuilder .withOfflineMode(true) .build() bash.start() bash.execute("source $bootstrapScript") when: setupEnv(bash) bash.execute("sdk config") then: verifyOutput(bash.output, sdkmanBaseDirectory) where: setupEnv << [ { it.execute('nano() { echo "nano was called with $*"; }') it.execute("EDITOR=nano") }, { it.execute('code() { echo "code was called with $*"; }') it.execute("EDITOR='code --wait'") }, { it.execute('vi() { echo "vi was called with $*"; }') it.execute("unset EDITOR") }, { it.execute("EDITOR=/does/not/exist") } ] verifyOutput << [ { output, baseDirectory -> output.contains("nano was called with ${baseDirectory}/.sdkman/etc/config") }, { output, baseDirectory -> output.contains("code was called with --wait ${baseDirectory}/.sdkman/etc/config") }, { output, baseDirectory -> output.contains("vi was called with ${baseDirectory}/.sdkman/etc/config") }, { output, _ -> output.contains("No default editor configured.") } ] } } ================================================ FILE: src/test/groovy/sdkman/specs/CurrentCommandSpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification import java.nio.file.Paths import static java.nio.file.Files.createSymbolicLink class CurrentCommandSpec extends SdkmanEnvSpecification { static final CANDIDATES_API = "http://localhost:8080/2" static final HEALTHCHECK_ENDPOINT = "$CANDIDATES_API/healthcheck" def setup() { curlStub.primeWith(HEALTHCHECK_ENDPOINT, "echo dbfb025be9f97fda2052b5febcca0155") } void "should display current version of all candidates installed"() { given: def installedCandidates = [ "gradle": "2.7", "groovy": "2.4.4", "vertx" : "3.0.0" ] def allCandidates = [ "asciidoctorj", "crash", "gaiden", "glide", "gradle", "grails", "griffon", "groovy", "groovyserv", "jbake", "jbossforge", "lazybones", "springboot", "vertx" ] bash = sdkmanBashEnvBuilder .withOfflineMode(false) .withCandidates(installedCandidates.keySet().toList()) .build() prepareFoldersFor(installedCandidates) bash.start() bash.execute("source $bootstrapScript") when: bash.execute('sdk current') then: bash.output.contains("Using:") bash.output.contains("groovy: 2.4.4") bash.output.contains("gradle: 2.7") bash.output.contains("vertx: 3.0.0") } private prepareFoldersFor(Map installedCandidates) { installedCandidates.forEach { candidate, version -> def candidateVersionDirectory = "$candidatesDirectory/$candidate/$version" def candidateVersionBinDirectory = "$candidateVersionDirectory/bin" new File(candidateVersionBinDirectory).mkdirs() def candidateVersionPath = Paths.get(candidateVersionDirectory) def symlink = Paths.get("$candidatesDirectory/$candidate/current") createSymbolicLink(symlink, candidateVersionPath) } } } ================================================ FILE: src/test/groovy/sdkman/specs/EnvCommandSpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification import java.nio.file.Paths import static java.nio.file.Files.createSymbolicLink class EnvCommandSpec extends SdkmanEnvSpecification { static final String CANDIDATES_API = "http://localhost:8080/2" static final String CANDIDATES_DEFAULT_JAVA = "$CANDIDATES_API/candidates/default/java" def "should generate .sdkmanrc when called with 'init'"() { given: curlStub.primeWith(CANDIDATES_DEFAULT_JAVA, "echo 11.0.6.hs-adpt") setupCandidates(candidatesDirectory) bash = sdkmanBashEnvBuilder .withOfflineMode(true) .build() bash.start() bash.execute("source $bootstrapScript") when: bash.execute("sdk env init") then: new File(bash.workDir, '.sdkmanrc').text.contains(expected) where: setupCandidates << [ { directory -> new FileTreeBuilder(directory).with { "java" { "8.0.252.hs" { "bin" {} } } } createSymbolicLink(Paths.get("$directory/java/current"), Paths.get("$directory/java/8.0.252.hs")) }, {} // NOOP ] expected << ["java=8.0.252.hs\n", "java=11.0.6.hs-adpt\n"] } def "should use the candidates contained in .sdkmanrc"() { given: new FileTreeBuilder(candidatesDirectory).with { "grails" { "2.1.0" {} } "groovy" { "2.4.1" {} } } bash = sdkmanBashEnvBuilder .withOfflineMode(true) .build() new File(bash.workDir, '.sdkmanrc').text = sdkmanrc bash.start() bash.execute("source $bootstrapScript") when: bash.execute("sdk env") then: verifyAll(bash.output) { contains("Using groovy version 2.4.1 in this shell.") contains("Using grails version 2.1.0 in this shell.") } where: sdkmanrc << [ "grails=2.1.0\ngroovy=2.4.1", "grails=2.1.0\ngroovy=2.4.1\n", " grails=2.1.0\ngroovy=2.4.1\n", "grails=2.1.0 \ngroovy=2.4.1\n", "grails=2.1.0\ngroovy = 2.4.1\n", ] } def "should execute 'sdk env' when entering a directory with an .sdkmanrc"() { given: new FileTreeBuilder(candidatesDirectory).with { "groovy" { "2.4.1" {} } } bash = sdkmanBashEnvBuilder .withOfflineMode(true) .withConfiguration("sdkman_auto_env", sdkmanAutoEnv) .build() new FileTreeBuilder(bash.workDir).with { "project" { ".sdkmanrc"("groovy=2.4.1\n") } } bash.start() bash.execute("source $bootstrapScript") when: bash.execute("cd project") then: verifyOutput(bash.output) where: sdkmanAutoEnv | verifyOutput 'true' | { it.contains("Using groovy version 2.4.1 in this shell") } 'false' | { !it.contains("Using groovy version 2.4.1 in this shell") } } def "should not execute 'sdk env' when already being in a directory with an .sdkmanrc"() { given: new FileTreeBuilder(candidatesDirectory).with { "groovy" { "2.4.1" {} } } bash = sdkmanBashEnvBuilder .withOfflineMode(true) .withConfiguration("sdkman_auto_env", "true") .build() new FileTreeBuilder(bash.workDir).with { "project" { ".sdkmanrc"("groovy=2.4.1\n") } } bash.start() bash.execute("source $bootstrapScript") when: bash.execute("cd project") bash.execute("ls") then: !bash.output.contains("Using groovy version 2.4.1 in this shell") } def "should execute 'sdk env' when opening a new terminal in a directory with an .sdkmanrc"() { given: new FileTreeBuilder(candidatesDirectory).with { "groovy" { "2.4.1" {} } } bash = sdkmanBashEnvBuilder .withOfflineMode(true) .withConfiguration("sdkman_auto_env", "true") .build() new FileTreeBuilder(bash.workDir).with { ".sdkmanrc"("groovy=2.4.1\n") } bash.start() when: bash.execute("source $bootstrapScript") then: bash.output.contains("Using groovy version 2.4.1 in this shell") } def "should execute 'sdk env' after executing 'sdk env clear'"() { given: new FileTreeBuilder(candidatesDirectory).with { "groovy" { "2.4.1" {} } } bash = sdkmanBashEnvBuilder .withOfflineMode(true) .withConfiguration("sdkman_auto_env", "true") .build() new FileTreeBuilder(bash.workDir).with { "project" { ".sdkmanrc"("groovy=2.4.1\n") } } bash.start() bash.execute("source $bootstrapScript") when: bash.execute("cd project") bash.execute("cd ..") bash.execute("cd project") then: bash.output.contains('Using groovy version 2.4.1 in this shell') } def "should execute 'sdk env clear' when exiting from a directory with an .sdkmanrc"() { given: new FileTreeBuilder(candidatesDirectory).with { "groovy" { "2.4.1" {} "2.4.6" {} } } createSymbolicLink(Paths.get("$candidatesDirectory/groovy/current"), Paths.get("$candidatesDirectory/groovy/2.4.6")) bash = sdkmanBashEnvBuilder .withOfflineMode(true) .withConfiguration("sdkman_auto_env", "true") .build() new FileTreeBuilder(bash.workDir).with { "project" { ".sdkmanrc"("groovy=2.4.1\n") } } bash.start() bash.execute("source $bootstrapScript") when: bash.execute("cd project") bash.execute("cd ..") then: bash.output.contains("Restored groovy version to 2.4.6") } def "should execute 'sdk env clear; sdk env' when switching to another directory with an .sdkmanrc"() { given: new FileTreeBuilder(candidatesDirectory).with { "groovy" { "2.4.1" {} "2.4.6" {} "2.5.14" {} } "ant" { "1.9.15" {} "1.10.8" {} } } createSymbolicLink(Paths.get("$candidatesDirectory/groovy/current"), Paths.get("$candidatesDirectory/groovy/2.5.14")) createSymbolicLink(Paths.get("$candidatesDirectory/ant/current"), Paths.get("$candidatesDirectory/ant/1.10.8")) bash = sdkmanBashEnvBuilder .withOfflineMode(true) .withConfiguration("sdkman_auto_env", "true") .build() new FileTreeBuilder(bash.workDir).with { "projectA" { ".sdkmanrc"("groovy=2.4.1\nant=1.9.15\n") } "projectB" { ".sdkmanrc"("groovy=2.4.6\n") } } bash.start() bash.execute("source $bootstrapScript") when: bash.execute("cd projectA") then: bash.output.contains('Using groovy version 2.4.1 in this shell') bash.output.contains('Using ant version 1.9.15 in this shell') when: bash.execute("cd ../projectB") then: bash.output.contains("Restored ant version to 1.10.8") bash.output.contains('Using groovy version 2.4.6 in this shell') when: bash.execute("cd ..") then: bash.output.contains('Restored groovy version to 2.5.14') !bash.output.contains('ant') } def "should not execute 'sdk env clear' when entering a subdirectory within the current active configuration"() { given: new FileTreeBuilder(candidatesDirectory).with { "groovy" { "2.4.1" {} } } bash = sdkmanBashEnvBuilder .withOfflineMode(true) .withConfiguration("sdkman_auto_env", "true") .build() new FileTreeBuilder(bash.workDir).with { "project" { ".sdkmanrc"("groovy=2.4.1\n") } "src" {} } bash.start() bash.execute("source $bootstrapScript") when: bash.execute("cd project") bash.execute("cd src") then: !bash.output.contains("Restored groovy version to 2.4.6") } def "should issue an error if .sdkmanrc contains a malformed candidate version"() { given: bash = sdkmanBashEnvBuilder .withOfflineMode(true) .build() new File(bash.workDir, ".sdkmanrc").text = "groovy 2.4.1" bash.start() bash.execute("source $bootstrapScript") when: bash.execute("sdk env") then: verifyAll(bash) { status == 1 output.contains("Invalid candidate format!") } } def "should issue an error when .sdkmanrc contains a candidate version which is not installed"() { given: bash = sdkmanBashEnvBuilder .withOfflineMode(true) .build() new File(bash.workDir, ".sdkmanrc").text = "groovy=2.4.1" bash.start() bash.execute("source $bootstrapScript") when: bash.execute("sdk env") then: verifyAll(bash) { status == 1 output.contains("Stop! groovy 2.4.1 is not installed.") output.contains("Run 'sdk env install' to install it.") } } def "should support blank lines, comments and inline comments"() { given: new FileTreeBuilder(candidatesDirectory).with { "groovy" { "2.4.1" {} } } bash = sdkmanBashEnvBuilder .withOfflineMode(true) .build() new File(bash.workDir, ".sdkmanrc").text = sdkmanrc bash.start() bash.execute("source $bootstrapScript") when: bash.execute("sdk env") then: bash.output.contains("Using groovy version 2.4.1 in this shell.") where: sdkmanrc << [ "\ngroovy=2.4.1\n", "# this is a comment\ngroovy=2.4.1\n", "groovy=2.4.1 # this is a comment too\n" ] } } ================================================ FILE: src/test/groovy/sdkman/specs/InitialisationSpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification import java.nio.file.Files import java.nio.file.Paths class InitialisationSpec extends SdkmanEnvSpecification { static final allCandidates = [ "asciidoctorj", "crash", "gaiden", "glide", "gradle", "grails", "griffon", "groovy", "groovyserv", "jbake", "jbossforge", "lazybones", "springboot", "vertx" ] def setup() { bash = sdkmanBashEnvBuilder .withCandidates(allCandidates) .build() prepareCandidateDirectories(allCandidates) } void "should include all candidates in PATH"() { given: bash.start() bash.execute("source $bootstrapScript") bash.resetOutput() when: bash.execute('echo "$PATH"') def pathParts = bash.output.split(':') def pathElementMatcher = ~/$candidatesDirectory\/([^\/]+)\/.*/ def includedCandidates = pathParts .collect { it.replace("\n", "") } .collect { it =~ pathElementMatcher } .findAll { it } .collect { it[0][1] } .sort() println("Available: ${allCandidates}") println("Included : $includedCandidates") and: def missingCandidates = allCandidates - includedCandidates then: missingCandidates.isEmpty() } void "should reinitialize candidates in PATH if necessary"() { given: bash.start() bash.execute("source $bootstrapScript") bash.resetOutput() and: def originalPath = bash.env.grep { it =~ /^PATH=/ }.first() as String bash.execute(originalPath) when: bash.execute("source $bootstrapScript") bash.execute('echo "$PATH"') then: def pathParts = bash.output.split(':') def pathElementMatcher = ~/$candidatesDirectory\/([^\/]+)\/.*/ def includedCandidates = pathParts .collect { it.replace("\n", "") } .collect { it =~ pathElementMatcher } .findAll { it } .collect { it[0][1] } .sort() println("Available: $allCandidates") println("Included : $includedCandidates") def missingCandidates = allCandidates - includedCandidates missingCandidates.isEmpty() } void "should not duplicate PATH entries if re-sourced"() { given: bash.start() bash.execute("source $bootstrapScript") bash.resetOutput() when: bash.execute("source $bootstrapScript") bash.execute('echo "$PATH"') def pathParts = bash.output.split(':') def pathElementMatcher = ~/$candidatesDirectory\/([^\/]+)\/.*/ def includedCandidates = pathParts .collect { it.replace("\n", "") } .collect { it =~ pathElementMatcher } .findAll { it } .collect { it[0][1] } .sort() println("Available: $allCandidates") println("Included : $includedCandidates") and: def duplicateCandidates = includedCandidates - allCandidates then: duplicateCandidates.isEmpty() } private prepareCandidateDirectories(List candidates) { candidates.forEach { def current = Paths.get("$candidatesDirectory/$it/current") def targetFilename = "$candidatesDirectory/$it/xxx" new File(targetFilename).createNewFile() def target = Paths.get(targetFilename) Files.createSymbolicLink(current, target) } } } ================================================ FILE: src/test/groovy/sdkman/specs/SdkCompatibilitySpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification import java.nio.file.Files import java.nio.file.Paths class SdkCompatibilitySpec extends SdkmanEnvSpecification { def allCandidates = ["groovy", "grails", "scala", "sbt"] def setup() { bash = sdkmanBashEnvBuilder .withCandidates(allCandidates) .build() } void "should add candidate bin folder to the path if present"() { given: def candidateFolder = prepareCandidateFolder("scala", "2.11.7", true) and: bash.start() bash.execute("source $bootstrapScript") bash.resetOutput() when: bash.execute('echo $PATH') def pathEntries = bash.output.split(':') def firstPathEntry = pathEntries.first() then: firstPathEntry.contains("$candidateFolder/bin") } void "should add candidate base folder to the path if no bin folder present"() { given: def candidateFolder = prepareCandidateFolder("sbt", "1.0.3", false) and: bash.start() bash.execute("source $bootstrapScript") bash.resetOutput() when: bash.execute('echo $PATH') def pathEntries = bash.output.split(':') def firstPathEntry = pathEntries.first() then: firstPathEntry.contains("$candidateFolder") and: !firstPathEntry.contains("$candidateFolder/bin") } private prepareCandidateFolder(String candidate, String version, boolean hasBinFolder) { def candidateBaseDir = "${candidatesDirectory.absolutePath}/$candidate" def candidateCurrentDir = Paths.get("$candidateBaseDir/current") def candidateLocation = "${candidatesDirectory.absolutePath}/$candidate/$version" def candidatePath = Paths.get(candidateLocation) def binLocation = hasBinFolder ? "$candidateLocation/bin/" : "$candidateLocation" def executableFilename = "run.sh" def executableFile = "$binLocation/$executableFilename" as File new File(binLocation).mkdirs() executableFile.createNewFile() Files.createSymbolicLink(candidateCurrentDir, candidatePath) candidateCurrentDir.toString() } } ================================================ FILE: src/test/groovy/sdkman/specs/SelfupdateFeatureSpec.groovy ================================================ package sdkman.specs import sdkman.support.SdkmanEnvSpecification import java.time.Instant import static java.time.temporal.ChronoUnit.DAYS class SelfupdateFeatureSpec extends SdkmanEnvSpecification { static final String CANDIDATES_API = "http://localhost:8080/2" static final String HEALTHCHECK_ENDPOINT = "$CANDIDATES_API/healthcheck" static final String VERSION_ENDPOINT = "$CANDIDATES_API/broker/download/sdkman/version/stable" def setup() { curlStub.primeWith(HEALTHCHECK_ENDPOINT, "echo dbfb025be9f97fda2052b5febcca0155") curlStub.primeWith(VERSION_ENDPOINT, "echo 5.0.0") } def "should list selfupdate as a valid command when the selfupdate feature is toggled on"() { given: bash = sdkmanBashEnvBuilder .withConfiguration("sdkman_selfupdate_feature", selfUpdateFeature) .build() bash.start() bash.execute("source $bootstrapScript") when: bash.execute("sdk help") then: verifyOutput(bash.output) where: selfUpdateFeature | verifyOutput "false" | { !it.contains("selfupdate") } "true" | { it.contains("selfupdate") } } def "should source sdkman-selfupdate.sh when the selfupdate feature is toggled on"() { given: bash = sdkmanBashEnvBuilder .withConfiguration("sdkman_selfupdate_feature", selfupdateFeature) .build() bash.start() bash.execute("source $bootstrapScript") when: bash.execute("sdk selfupdate") then: verifyOutput(bash.output) where: selfupdateFeature | verifyOutput "false" | { it.contains("Invalid command: selfupdate") } "true" | { !it.contains("Invalid command: selfupdate") } } } ================================================ FILE: src/test/groovy/sdkman/steps/command_line_interop_steps.groovy ================================================ package sdkman.steps import static cucumber.api.groovy.EN.And And(~'^I enter \"([^\"]*)\"$') { String command -> bash.execute(command) result = bash.output } And(~'^I enter "([^"]*)" and answer "([^"]*)"$') { String command, String answer -> bash.execute(command, [answer]) result = bash.output } And(~'^I see \"([^\"]*)\"$') { String output -> assert result.contains(output) } And(~'^I do not see "([^"]*)"$') { String output -> assert !result.contains(output) } And(~'^I see only \"([^\"]*)\"$') { String output -> assert result?.replaceAll("\\n", "") == output } And(~'^I see the current sdkman version$') { -> assert result.contains("SDKMAN") } And(~'^I see a single occurrence of \"([^\"]*)\"$') { String occurrence -> assert result.count(occurrence) == 1 } And(~'^I see no occurrences of \"([^\"]*)\"$') { String occurrence -> assert result.count(occurrence) == 0 } And(~'the "(.*)" variable contains "(.*)"') { String home, String segment -> bash.execute("echo \$$home") assert bash.output.contains(segment) } And(~'the "(.*)" variable is not set') { String home -> bash.execute("echo \$$home") assert !bash.output.contains(".sdkman/") } And(~'^the home path ends with \"([^\"]*)\"$') { String suffix -> def path = sdkmanBaseDir.absolutePath + "/" + suffix assert result.trim().endsWith(path) } ================================================ FILE: src/test/groovy/sdkman/steps/env.groovy ================================================ package sdkman.steps import com.github.tomakehurst.wiremock.client.WireMock import sdkman.support.FilesystemUtils import sdkman.support.WireMockServerProvider import static cucumber.api.groovy.Hooks.After import static cucumber.api.groovy.Hooks.Before HTTP_PROXY = System.getProperty("httpProxy") ?: "" FAKE_JDK_PATH = "/path/to/my/openjdk" SERVICE_UP_HOST = "localhost" SERVICE_UP_PORT = 8080 SERVICE_UP_URL = "http://$SERVICE_UP_HOST:$SERVICE_UP_PORT" SERVICE_DOWN_URL = "http://localhost:0" counter = "${(Math.random() * 10000).toInteger()}".padLeft(4, "0") localGroovyCandidate = "/tmp/groovy-core" as File sdkmanScriptVersion = "5.0.0" sdkmanNativeVersion = "0.0.1" sdkmanBaseEnv = FilesystemUtils.prepareBaseDir().absolutePath sdkmanBaseDir = sdkmanBaseEnv as File sdkmanDirEnv = "$sdkmanBaseEnv/.sdkman" sdkmanDir = sdkmanDirEnv as File candidatesDir = "${sdkmanDirEnv}/candidates" as File binDir = "${sdkmanDirEnv}/bin" as File srcDir = "${sdkmanDirEnv}/src" as File varDir = "${sdkmanDirEnv}/var" as File metadataDir = "${varDir}/metadata" as File etcDir = "${sdkmanDirEnv}/etc" as File extDir = "${sdkmanDirEnv}/ext" as File tmpDir = "${sdkmanDir}/tmp" as File healthcheckFile = new File(varDir, "healthcheck") candidatesFile = new File(varDir, "candidates") versionFile = new File(varDir, "version") initScript = new File(binDir, "sdkman-init.sh") localCandidates = ['groovy', 'grails', 'java', 'kotlin', 'scala'] bash = null if (!binding.hasVariable("wireMock")) { wireMock = WireMockServerProvider.wireMockServer() } addShutdownHook { wireMock.stop() } Before() { WireMock.reset() cleanUp() } private cleanUp() { sdkmanBaseDir.deleteDir() localGroovyCandidate.deleteDir() } After() { scenario -> def output = bash?.output if (output) { scenario.write("\nOutput: \n${output}") } bash?.stop() } ================================================ FILE: src/test/groovy/sdkman/steps/env_steps.groovy ================================================ package sdkman.steps import static cucumber.api.groovy.EN.And And(~/^the file "([^"]+)" exists and contains "([^"]+)"$/) { String filename, String content -> new File(sdkmanBaseEnv, filename).withWriter { it.writeLine(content) } } ================================================ FILE: src/test/groovy/sdkman/steps/flush_steps.groovy ================================================ package sdkman.steps import static cucumber.api.groovy.EN.And And(~'^the candidate "([^"]*)" is known locally$') { String candidate -> assert candidatesFile.text.contains(candidate) } And(~'^no candidates are know locally$') { -> assert !candidatesFile.exists() } And(~'^the file "([^"]*)" in temporary storage$') { String fileName -> new File(tmpDir, fileName).createNewFile() } And(~'^no "([^"]*)" file is present in temporary storage$') { String fileName -> assert !new File(tmpDir, fileName).exists() } And(~'^a prior version "([^"]*)" was detected$') { String version -> assert versionFile.exists() assert versionFile.text.contains(version) } And(~'^no version file can be found$') { -> assert !versionFile.exists() } And(~'^the Remote Version has been flushed$') { -> assert versionFile.delete() } And(~'^a headers file "([^"]*)" in metadata directory with checksum "([^"]*)" using algorithm "([^"]*)"$') { String fileName, String checksum, String algorithm -> new File(metadataDir, fileName).withWriter { out -> out.println "X-Sdkman-Checksum-${algorithm}: ${checksum}" } } And(~'^no metadata is cached$') { -> assert !metadataDir.listFiles() } ================================================ FILE: src/test/groovy/sdkman/steps/initialisation_steps.groovy ================================================ package sdkman.steps import sdkman.env.SdkmanBashEnvBuilder import sdkman.stubs.UnameStub import java.util.zip.ZipException import java.util.zip.ZipFile import static cucumber.api.groovy.EN.And import static sdkman.stubs.WebServiceStub.primeEndpointWithString import static sdkman.stubs.WebServiceStub.primeSelfupdate And(~'^the sdkman work folder is created$') { -> assert sdkmanDir.isDirectory(), "The SDKMAN directory does not exist." } And(~'^the "([^"]*)" folder exists in user home$') { String arg1 -> assert sdkmanDir.isDirectory(), "The SDKMAN directory does not exist." } And(~'^the archive for candidate "([^"]*)" version "([^"]*)" is corrupt$') { String candidate, String version -> try { new ZipFile(new File("src/test/resources/__files/${candidate}-${version}.zip")) assert false, "Archive was not corrupt!" } catch (ZipException ze) { //expected behaviour } } And(~'^the archive for candidate "([^"]*)" version "([^"]*)" is removed$') { String candidate, String version -> def archive = new File("${sdkmanDir}/tmp/${candidate}-${version}.zip") assert !archive.exists() } And(~'^the sdkman (.*) version "(.*)" is available for download$') { format, version -> primeEndpointWithString("/broker/version/sdkman/${format}/stable", version) } And(~'^the internet is reachable$') { -> primeEndpointWithString("/healthcheck", "12345") primeSelfupdate() offlineMode = false serviceUrlEnv = SERVICE_UP_URL javaHome = FAKE_JDK_PATH } And(~'^the internet is not reachable$') { -> offlineMode = false serviceUrlEnv = SERVICE_DOWN_URL javaHome = FAKE_JDK_PATH } And(~'^offline mode is disabled with reachable internet$') { -> primeEndpointWithString("/healthcheck", "12345") offlineMode = false serviceUrlEnv = SERVICE_UP_URL javaHome = FAKE_JDK_PATH } And(~'^offline mode is enabled with reachable internet$') { -> primeEndpointWithString("/healthcheck", "12345") offlineMode = true serviceUrlEnv = SERVICE_UP_URL javaHome = FAKE_JDK_PATH } And(~'^offline mode is enabled with unreachable internet$') { -> offlineMode = true serviceUrlEnv = SERVICE_DOWN_URL javaHome = FAKE_JDK_PATH } And(~'^an "(.*)" machine with "(.*)" installed$') { String machine, String kernel -> def binFolder = "$sdkmanBaseDir/bin" as File UnameStub.prepareIn(binFolder) .forKernel(kernel) .forMachine(machine) .build() } And(~'^an initialised environment$') { -> bash = SdkmanBashEnvBuilder.create(sdkmanBaseDir) .withOfflineMode(offlineMode) .withCandidatesApi(serviceUrlEnv) .withBrokerApi(serviceUrlEnv) .withJdkHome(javaHome) .withHttpProxy(HTTP_PROXY) .withScriptVersion(sdkmanScriptVersion) .withNativeVersion(sdkmanNativeVersion) .withCandidates(localCandidates) .build() } And(~'^an initialised environment without debug prints$') { -> bash = SdkmanBashEnvBuilder.create(sdkmanBaseDir) .withOfflineMode(offlineMode) .withCandidatesApi(serviceUrlEnv) .withBrokerApi(serviceUrlEnv) .withJdkHome(javaHome) .withHttpProxy(HTTP_PROXY) .withScriptVersion(sdkmanScriptVersion) .withNativeVersion(sdkmanNativeVersion) .withCandidates(localCandidates) .withDebugMode(false) .build() } And(~'^the system is bootstrapped$') { -> bash.start() bash.execute("source $sdkmanDirEnv/bin/sdkman-init.sh") } And(~'^the system is bootstrapped again$') { -> bash.execute("source $sdkmanDirEnv/bin/sdkman-init.sh") } And(~/^the sdkman scripts version is "([^"]*)"$/) { String version -> sdkmanScriptVersion = version } And(~/^the sdkman native version is "([^"]*)"$/) { String version -> sdkmanNativeVersion = version } And(~/^the candidates cache is initialised with "(.*)"$/) { String candidate -> localCandidates << candidate } And(~/^a project configuration is active$/) { -> bash.execute("SDKMAN_ENV=" + sdkmanBaseEnv) } And(~/^a project configuration is active but points to a directory without configuration$/) { -> def emptyDir = tmpDir.getPath() + "/empty" bash.execute("mkdir $emptyDir") bash.execute("SDKMAN_ENV=$emptyDir") } ================================================ FILE: src/test/groovy/sdkman/steps/installation_steps.groovy ================================================ package sdkman.steps import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import static cucumber.api.groovy.EN.And import static java.nio.file.Files.isSameFile import static java.nio.file.Files.isSymbolicLink import static sdkman.support.FilesystemUtils.prepareCandidateBinFolder import static sdkman.support.FilesystemUtils.prepareCandidateWithVersionFolder And(~'^the candidate "([^"]*)" version "([^"]*)" is installed$') { String candidate, String version -> def file = "${candidatesDir}/${candidate}/${version}" as File if (!file.exists()) println bash.output assert file.exists() } And(~'^the candidate "([^"]*)" version "([^"]*)" is not installed$') { String candidate, String version -> def directory = FileSystems.default.getPath("$candidatesDir/$candidate/$version") if (Files.exists(directory)) println bash.output assert !Files.exists(directory) } And(~'^the candidate "([^"]*)" version "([^"]*)" is already installed and default$') { String candidate, String version -> def candidateVersion = prepareCandidateWithVersionFolder("$candidatesDir", candidate, version) def currentLink = FileSystems.default.getPath("$candidatesDir/$candidate/current") Files.createSymbolicLink currentLink, candidateVersion } And(~'^the candidate "([^"]*)" version "([^"]*)" is the default$') { String candidate, String version -> def localVersion = FileSystems.default.getPath("$candidatesDir/$candidate/$version") def currentLink = FileSystems.default.getPath("$candidatesDir/$candidate/current") Files.createSymbolicLink currentLink, localVersion } And(~'^the candidate "([^"]*)" version "([^"]*)" is already installed but not default$') { String candidate, String version -> prepareCandidateWithVersionFolder "$candidatesDir", candidate, version } And(~'^I do not have a "([^"]*)" candidate installed$') { String candidate -> def candidateDir = FileSystems.default.getPath("${candidatesDir}/${candidate}") assert !candidateDir.toFile().listFiles() } And(~'^the candidate "([^"]*)" does not exist locally$') { String candidate -> def candidateDir = "${candidatesDir}/${candidate}" as File candidateDir.deleteDir() assert !candidateDir.exists() } And(~'^I have a local candidate "([^"]*)" version "([^"]*)" at "([^"]*)"$') { String candidate, String version, String directory -> prepareCandidateBinFolder directory, candidate, version } And(~'^I have a local candidate "([^"]*)" version "([^"]*)" at relative path "([^"]*)"$') { String candidate, String version, String relativePath -> def fullPath = new File(sdkmanBaseDir.absolutePath, relativePath) prepareCandidateBinFolder fullPath.absolutePath, candidate, version } And(~'^the candidate "([^"]*)" version "([^"]*)" is linked to the relative path "([^"]*)"$') { String candidate, String version, String relativePath -> def fullPath = new File(sdkmanBaseDir.absolutePath, relativePath) assertLinkedCandidate(fullPath.absolutePath, candidate, version) } And(~'^the candidate "([^"]*)" version "([^"]*)" is linked to "([^"]*)"$') { String candidate, String version, String directory -> assertLinkedCandidate(directory, candidate, version) } def assertLinkedCandidate(String directory, String candidate, String version) { Path versionFolder = Paths.get("$candidatesDir/$candidate/$version") assert isSymbolicLink(versionFolder) assert isSameFile(versionFolder, Paths.get(directory)) } And(~'^the candidate "([^"]*)" version "([^"]*)" is already linked to "([^"]*)"$') { String candidate, String version, String folder -> def fileSystem = FileSystems.default def candidateFolder = "$candidatesDir/$candidate" as File candidateFolder.mkdirs() def link = fileSystem.getPath("$candidatesDir/$candidate/$version") def target = prepareCandidateBinFolder(folder, candidate, version) Files.createSymbolicLink(link, target) } And(~'^I have configured "([^"]*)" to "([^"]*)"$') { String configName, String flag -> def configFile = new File("$sdkmanDir/etc/config") configFile.write "${configName}=${flag}" } And(~/^the exit code is (\d+)$/) { Integer rc -> assert bash.getStatus() == rc } And(~'^the response headers file is created for candidate "([^"]*)" and version "([^"]*)"$') { String candidate, String version -> def headersFile = "${metadataDir}/${candidate}-${version}.headers" as File assert headersFile.exists() } And(~'^no response headers are written for candidate "([^"]*)" and version "([^"]*)"$') { String candidate, String version -> def headersFile = "${metadataDir}/${candidate}-${version}.headers" as File assert !headersFile.exists() } ================================================ FILE: src/test/groovy/sdkman/steps/stub_steps.groovy ================================================ package sdkman.steps import io.cucumber.datatable.DataTable import sdkman.support.UnixUtils import static cucumber.api.groovy.EN.And import static sdkman.stubs.HookResponses.* import static sdkman.stubs.WebServiceStub.* import static sdkman.support.FilesystemUtils.readCurrentFromCandidateFolder import static sdkman.support.FilesystemUtils.readVersionsCsvFromCandidateFolder And(~'^the default "([^"]*)" version is "([^"]*)"$') { String candidate, String version -> primeEndpointWithString("/candidates/default/${candidate}", version) primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform()) primeEndpointWithString("/hooks/post/${candidate}/${version}/${UnixUtils.inferPlatform()}", postInstallationHookSuccess()) } And(~'^an available selfupdate endpoint$') { -> primeEndpointWithString("/selfupdate/stable/${UnixUtils.inferPlatform()}", 'echo "Successfully upgraded SDKMAN."') primeEndpointWithString("/selfupdate/beta/${UnixUtils.inferPlatform()}", 'echo "Successfully upgraded SDKMAN."') } And(~'^the candidate "([^"]*)" version "([^"]*)" is available for download$') { String candidate, String version -> primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "valid") primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform()) primeEndpointWithString("/hooks/post/${candidate}/${version}/${UnixUtils.inferPlatform()}", postInstallationHookSuccess()) } And(~'^the candidate "([^"]*)" version "([^"]*)" is available for download with checksum "([^"]*)" using algorithm "([^"]*)"$') { String candidate, String version, String checksum, String algorithm -> primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "valid") primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform(), ["X-Sdkman-Checksum-${algorithm}": "${checksum}"]) primeEndpointWithString("/hooks/post/${candidate}/${version}/${UnixUtils.inferPlatform()}", postInstallationHookSuccess()) } And(~/^the appropriate universal hook is available for "([^"]*)" version "([^"]*)" on "([^"]*)"$/) { String candidate, String version, String os -> String lcPlatform = UnixUtils.inferPlatform(os) primeUniversalHookFor(candidate, version, lcPlatform) } And(~/^the appropriate multi-platform hook is available for "([^"]*)" version "([^"]*)" on "([^"]*)" with architecture "(.*)"$/) { String candidate, String version, String os, String architecture -> String lcPlatform = UnixUtils.inferPlatform(os, architecture) primePlatformSpecificHookFor(candidate, version, lcPlatform) } And(~'^the candidate "([^"]*)" version "([^"]*)" is not available for download$') { String candidate, String version -> primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "invalid") } And(~/^the candidate "(.*)" version "(.*)" is available for download on "(.*)" with architecture "(.*)"$/) { String candidate, String version, String os, String architecture -> String lcPlatform = UnixUtils.inferPlatform(os, architecture) primeEndpointWithString("/candidates/validate/${candidate}/${version}/${lcPlatform}", "valid") primeDownloadFor(SERVICE_UP_URL, candidate, version, lcPlatform) } And(~/^a post-installation hook is served for "([^"]*)" "([^"]*)" on "([^"]*)" with architecture "([^"]*)" that returns successfully$/) { String candidate, String version, String os, String architecture -> String lcPlatform = UnixUtils.inferPlatform(os, architecture) primeEndpointWithString("/hooks/post/${candidate}/${version}/${lcPlatform}", postInstallationHookSuccess()) } And(~/^a post-installation hook is served for "([^"]*)" "([^"]*)" on "([^"]*)" with architecture "([^"]*)" that returns a failure$/) { String candidate, String version, String os, String architecture -> String lcPlatform = UnixUtils.inferPlatform(os, architecture) primeEndpointWithString("/hooks/post/${candidate}/${version}/${lcPlatform}", postInstallationHookFailure()) } And(~/^the candidate "(.*?)" version "(.*?)" is not available for download on "(.*?)"$/) { String candidate, String version, String os -> String lcPlatform = UnixUtils.inferPlatform(os) primeEndpointWithString("/candidates/validate/${candidate}/${version}/${lcPlatform}", "invalid") } And(~'^a "([^"]*)" list view is available for consumption$') { String candidate -> primeEndpointWithString("/candidates/${candidate}/${UnixUtils.inferPlatform()}/versions/list?current=&installed=", "Available ${candidate.capitalize()} Versions") } And(~'^the candidate "([^"]*)" version "([^"]*)" is a valid candidate version$') { String candidate, String version -> primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "valid") } And(~'^the candidate "([^"]*)" version "([^"]*)" is not a valid candidate version$') { String candidate, String version -> primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "invalid") } And(~/^the candidate "(.*?)" has a version list available$/) { String candidate -> def current = readCurrentFromCandidateFolder(candidatesDir, candidate) def versions = readVersionsCsvFromCandidateFolder(candidatesDir, candidate) def url = "/candidates/${candidate}/${UnixUtils.inferPlatform()}/versions/list?current=${current}&installed=${versions}" println("Priming url: $url") primeEndpointWithString(url, "Candidate: $candidate; Versions: $versions; Current: $current; Platform: ${UnixUtils.inferPlatform()}") } And(~/^The candidate list is available$/) { -> primeEndpointWithString("/candidates/list", "Candidate List") } And(~/^the following candidates are currently available from remote API:$/) { DataTable dt -> primeEndpointWithString("/candidates/all", dt.asList(String).drop(1).join(",")) } ================================================ FILE: src/test/groovy/sdkman/steps/update_steps.groovy ================================================ package sdkman.steps import io.cucumber.datatable.DataTable import static cucumber.api.groovy.EN.And And(~/^the following candidates are available for installation in local cache:$/) { DataTable dt -> localCandidates = dt.asList(String).drop(1) } And(~/^the Candidates cache should contain "(.*)"$/) { String candidates -> assert candidatesFile.text.trim() == candidates } ================================================ FILE: src/test/groovy/sdkman/steps/use_steps.groovy ================================================ package sdkman.steps import java.nio.file.FileSystems import java.nio.file.Files import static cucumber.api.groovy.EN.And And(~'^the candidate "([^"]*)" version "([^"]*)" is in use$') { String candidate, String version -> def directory = FileSystems.default.getPath("$candidatesDir/$candidate/$version") def current = FileSystems.default.getPath("$candidatesDir/$candidate/current") def symlinkFile = current.toFile() if (!symlinkFile.exists()) { assert Files.createSymbolicLink(current, directory) } } And(~'^the candidate "([^"]*)" version "([^"]*)" is not in use$') { String candidate, String version -> def directory = FileSystems.default.getPath("$candidatesDir/$candidate/$version") def current = FileSystems.default.getPath("$candidatesDir/$candidate/current") def symlinkFile = current.toFile() if (symlinkFile.exists()) { assert !Files.isSameFile(current, directory) } } And(~'^the candidate "([^"]*)" version "([^"]*)" should be in use$') { String candidate, String version -> bash.execute("$candidate --version") assert bash.output.contains(version) } And(~'^the candidate "([^"]*)" version "([^"]*)" should be the default$') { String candidate, String version -> def directory = FileSystems.default.getPath("$candidatesDir/$candidate/$version") def current = FileSystems.default.getPath("$candidatesDir/$candidate/current") assert Files.isSameFile(current, directory) } And(~'^the candidate "([^"]*)" version "([^"]*)" should not be the default$') { String candidate, String version -> def directory = FileSystems.default.getPath("$candidatesDir/$candidate/$version") def current = FileSystems.default.getPath("$candidatesDir/$candidate/current") assert (!Files.isSymbolicLink(current) || (Files.isSymbolicLink(current) && !Files.isSameFile(current, directory))) } And(~'^the candidate "([^"]*)" is no longer selected$') { String candidate -> def symlink = new File("$candidatesDir/$candidate/current") assert !symlink.exists() } ================================================ FILE: src/test/groovy/sdkman/stubs/CurlStub.groovy ================================================ package sdkman.stubs class CurlStub { private File file private commands = [:] static CurlStub prepareIn(File folder) { folder.mkdirs() def file = new File(folder, "curl") file.createNewFile() file.write "#!/usr/bin/env bash\n" file.executable = true new CurlStub(file: file) } CurlStub primeWith(String request, String snippet) { commands.put request, snippet this } void build() { commands.each { request, snippet -> //use last arg because we use curl with options file << 'if [[ "${@: -1}" == "' file << "$request" file << '" ]]; then\n' file << " $snippet\n" file << 'fi\n' } } } ================================================ FILE: src/test/groovy/sdkman/stubs/HookResponses.groovy ================================================ package sdkman.stubs class HookResponses { static postInstallationHookSuccess() { '''\ #!/usr/bin/env bash function __sdkman_post_installation_hook { mv -f $binary_input $zip_output echo "Post-installation hook success" return 0 } ''' } static postInstallationHookFailure() { '''\ #!/usr/bin/env bash function __sdkman_post_installation_hook { echo "Post-installation hook failure" return 1 } ''' } } ================================================ FILE: src/test/groovy/sdkman/stubs/UnameStub.groovy ================================================ package sdkman.stubs class UnameStub { private File file def kernel = "Linux" def machine = "x86_64" static UnameStub prepareIn(File folder) { folder.mkdirs() def file = new File(folder, "uname") file.createNewFile() file.write "#!/usr/bin/env bash\n" file.executable = true new UnameStub(file: file) } UnameStub forKernel(String kernel) { this.kernel = kernel this } UnameStub forMachine(String machine) { this.machine = machine this } void build() { file << """ |if [[ "\$1" == '-m' ]]; then | echo "$machine" |elif [[ "\$1" == '-s' ]]; then | echo "$kernel" |else | echo "$machine" |fi """.stripMargin('|') } } ================================================ FILE: src/test/groovy/sdkman/stubs/WebServiceStub.groovy ================================================ package sdkman.stubs import static com.github.tomakehurst.wiremock.client.WireMock.* class WebServiceStub { static primeEndpointWithString(String endpoint, String body) { stubFor(get(urlEqualTo(endpoint)).willReturn( aResponse() .withStatus(200) .withHeader("Content-Type", "text/plain") .withBody(body))) } static primeUniversalHookFor(String candidate, String version, String platform) { primeHookFor(candidate, version, platform, true) } static primePlatformSpecificHookFor(String candidate, String version, String platform) { primeHookFor(candidate, version, platform, false) } private static primeHookFor(String candidate, String version, String platform, boolean universal = true) { def hookFile = "hooks/post_hook_${candidate}_${version}_${universal ? 'universal' : platform}.sh" stubFor(get(urlEqualTo("/hooks/post/$candidate/$version/$platform")).willReturn( aResponse() .withStatus(200) .withHeader("Content-Type", "text/plain") .withBodyFile(hookFile))) } static primeDownloadFor(String host, String candidate, String version, String platform) { primeDownloadFor(host, candidate, version, platform, [:]) } static primeDownloadFor(String host, String candidate, String version, String platform, Map headers) { def binary = (candidate == "java") ? "jdk-${version}-${platform}.tar.gz" : "${candidate}-${version}.zip" def responseBuilder = aResponse() .withHeader("Location", "${host}/${binary}") .withStatus(302) headers.each { responseBuilder.withHeader(it.key, it.value) } stubFor(get(urlEqualTo("/download/${candidate}/${version}/${platform}")) .willReturn(responseBuilder)) stubFor(get(urlEqualTo("/$binary")).willReturn( aResponse() .withStatus(200) .withHeader("Content-Type", "application/zip") .withBodyFile(binary))) } static primeSelfupdate() { stubFor(get(urlEqualTo("/selfupdate")).willReturn( aResponse() .withStatus(200) .withHeader("Content-Type", "text/plain") .withBodyFile("selfupdate.sh"))) } } ================================================ FILE: src/test/groovy/sdkman/support/BashEnvSpecification.groovy ================================================ package sdkman.support import sdkman.env.BashEnv import spock.lang.Specification abstract class BashEnvSpecification extends Specification { BashEnv bash void cleanup() { println bash.output bash.stop() } } ================================================ FILE: src/test/groovy/sdkman/support/FilesystemUtils.groovy ================================================ package sdkman.support import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths class FilesystemUtils { static final DEFAULT_BASE_DIR = "/tmp/sdkman-test" static readVersionsCsvFromCandidateFolder(File baseDir, String candidate) { def versionFiles = new File(baseDir, candidate).listFiles() versionFiles?.findAll { it.name != "current" }?.sort()?.collect { it.name }?.join(",") ?: "" } static readCurrentFromCandidateFolder(File baseDir, String candidate) { def versionFiles = new File(baseDir, candidate).listFiles() def currentSymlinkPath = versionFiles.find { it.name == "current" }?.absolutePath currentSymlinkPath ? Files.readSymbolicLink(Paths.get(currentSymlinkPath)).fileName : "" } static prepareBaseDir() { def baseDir = "$DEFAULT_BASE_DIR/${UUID.randomUUID()}" as File baseDir.mkdirs() baseDir } static prepareCandidateWithVersionFolder(String baseDir, String candidate, String version) { def directory = "$baseDir/$candidate/$version" prepareCandidateBinFolder directory, candidate, version } static prepareCandidateBinFolder(String folder, String candidate, String version) { def fileSystem = FileSystems.default def binFolderPath = fileSystem.getPath("$folder/bin") Files.createDirectories binFolderPath prepareCandidateExecutable binFolderPath, candidate, version return fileSystem.getPath("$folder") } private static prepareCandidateExecutable(Path binFolder, String candidate, String version) { def candidateFile = new File("$binFolder/$candidate") candidateFile.write "echo ${candidate.capitalize()} Version: ${version}" candidateFile.executable = true } } ================================================ FILE: src/test/groovy/sdkman/support/SdkmanEnvSpecification.groovy ================================================ package sdkman.support import sdkman.env.SdkmanBashEnvBuilder import sdkman.stubs.CurlStub import sdkman.stubs.UnameStub import static sdkman.support.FilesystemUtils.prepareBaseDir abstract class SdkmanEnvSpecification extends BashEnvSpecification { SdkmanBashEnvBuilder sdkmanBashEnvBuilder CurlStub curlStub UnameStub unameStub File sdkmanBaseDirectory File sdkmanDotDirectory File candidatesDirectory String bootstrapScript def setup() { sdkmanBaseDirectory = prepareBaseDir() curlStub = CurlStub.prepareIn(new File(sdkmanBaseDirectory, "bin")) unameStub = UnameStub.prepareIn(new File(sdkmanBaseDirectory, "bin")) sdkmanBashEnvBuilder = SdkmanBashEnvBuilder .create(sdkmanBaseDirectory) .withUnameStub(unameStub) .withCurlStub(curlStub) sdkmanDotDirectory = new File(sdkmanBaseDirectory, ".sdkman") candidatesDirectory = new File(sdkmanDotDirectory, "candidates") bootstrapScript = "${sdkmanDotDirectory}/bin/sdkman-init.sh" } def cleanup() { assert sdkmanBaseDirectory.deleteDir() } } ================================================ FILE: src/test/groovy/sdkman/support/UnixUtils.groovy ================================================ package sdkman.support class UnixUtils { private static platforms = [ "Linux" : [ "x86_64": "linuxx64", "aarch64": "linuxarm64", ], "Darwin": [ "x86_64": "darwinx64", "arm64": "darwinarm64", ] ] static execute(String command) { command.execute().text.trim() } static osName() { execute("uname -s") } static osArch() { execute("uname -m") } static inferPlatform( String osName = osName(), String architecture = osArch()) { platforms[osName][architecture] ?: osName.toLowerCase() } } ================================================ FILE: src/test/groovy/sdkman/support/WireMockServerProvider.groovy ================================================ package sdkman.support import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.WireMock import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig class WireMockServerProvider { static SERVICE_UP_HOST = "localhost" static SERVICE_UP_PORT = 8080 static WireMockServer wireMockServer static def wireMockServer() { wireMockServer ?: createWireMockServer() } private static def createWireMockServer() { wireMockServer = new WireMockServer(wireMockConfig().port(SERVICE_UP_PORT)) wireMockServer.start() WireMock.configureFor(SERVICE_UP_HOST, SERVICE_UP_PORT) wireMockServer } } ================================================ FILE: src/test/jmeter/SDKman.jmx ================================================ false false continue false -1 1 1 1382782760000 1382782760000 false 0 100000 1 counter 00000 true host localhost = port 8080 = , candidates.csv false true shareMode.thread false candidate,version ${host} ${port} /alive GET false false true false Java false OK Assertion.response_data false 2 ${host} ${port} /candidates GET false false true false Java false groovy grails griffon gradle Assertion.response_data false 2 ${host} ${port} /candidates/${candidate}/${version}/validate GET false false true false Java false valid Assertion.response_data false 8 ${host} ${port} /api/version GET false false true false Java false 1.0.0 Assertion.response_data false 2 ${host} ${port} /candidates/${candidate}/default GET false false true false Java false ${version} Assertion.response_data false 2 false Linux = true platform false ${version} = true current false ${version},local${counter} = true installed ${host} ${port} /candidates/${candidate}/list GET false false true false Java false Available Assertion.response_data false 2 ${host} ${port} /api/broadcast GET false false true false Java false Report issues Assertion.response_data false 2 ${host} ${port} /candidates/${candidate} GET false false true false Java false ${version} Assertion.response_data false 2 ${host} ${port} /candidates/${candidate}/${version}/download GET false false true false Java false 302 Assertion.response_code false 8 false saveConfig true true true true true true true false true true false false false false false false false false 0 true false saveConfig true true true true true true true false true true false false false false false false false false 0 true 250 ================================================ FILE: src/test/jmeter/candidates.csv ================================================ gaiden,0.3 gradle,1.8 grails,2.3.1 griffon,1.4.0 groovy,2.1.8 groovyserv,0.13 lazybones,0.4 springboot,0.5.0.M5 ================================================ FILE: src/test/resources/__files/hooks/post_hook_java_8.0.101_linuxx64.sh ================================================ #!/usr/bin/env bash # failed download with non-zero exit code function __sdkman_post_installation_hook() { echo "POST: fails with non-zero exit code" echo "Cannot install java 8.0.101 at this time..." echo "Download has failed, aborting!" return 1 } ================================================ FILE: src/test/resources/__files/hooks/post_hook_java_8.0.111_linuxx64.sh ================================================ #!/usr/bin/env bash # convert tar.gz to zip function __sdkman_post_installation_hook() { echo "POST: converting $binary_input to $zip_output" mkdir -p "$SDKMAN_DIR/tmp/out" /usr/bin/env tar zxvf "$binary_input" -C "${SDKMAN_DIR}/tmp/out" cd "${SDKMAN_DIR}/tmp/out" /usr/bin/env zip -r "$zip_output" . } ================================================ FILE: src/test/resources/__files/hooks/post_hook_java_8.0.111_universal.sh ================================================ #!/usr/bin/env bash # passthrough used for zip binaries function __sdkman_post_installation_hook() { echo "POST: passthrough $binary_input to $zip_output" mv -f "$binary_input" "$zip_output" } ================================================ FILE: src/test/resources/features/checksum_verification.feature ================================================ Feature: Verify checksums Background: Given the internet is reachable And an initialised environment And I have configured "sdkman_checksum_enable" to "true" Scenario: Install a specific Version with a valid SHA-256 checksum Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download with checksum "1f9234c8e622ec46d33883ea45b39ede768b92d478fe08f6952548247f7fbb65" using algorithm "SHA-256" When I enter "sdk install grails 1.3.9" Then I see "Done installing!" And the candidate "grails" version "1.3.9" is installed And the response headers file is created for candidate "grails" and version "1.3.9" And the exit code is 0 Scenario: Install a specific Version with a valid SHA1 checksum Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download with checksum "c68e386a6deec9fc4c1e18df21f92739ba2ab36e" using algorithm "SHA1" When I enter "sdk install grails 1.3.9" Then I see "Done installing!" And the candidate "grails" version "1.3.9" is installed And the response headers file is created for candidate "grails" and version "1.3.9" And the exit code is 0 Scenario: Install a specific Version with a valid MD5 checksum Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download with checksum "1e87a7d982a2f41da96fdec289908552" using algorithm "MD5" When I enter "sdk install grails 1.3.9" Then I see "Done installing!" And the candidate "grails" version "1.3.9" is installed And the response headers file is created for candidate "grails" and version "1.3.9" And the exit code is 0 Scenario: Do not fail if an unknown algorithm is used Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download with checksum "abc-checksum-00000" using algorithm "ABC" When I enter "sdk install grails 1.3.9" Then I see "Done installing!" And the candidate "grails" version "1.3.9" is installed And the response headers file is created for candidate "grails" and version "1.3.9" And the exit code is 0 Scenario: Do not fail if no algorithm is detected Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download When I enter "sdk install grails 1.3.9" Then I see "Done installing!" And I do not see "Stop! An invalid checksum was detected and the archive removed! Please try re-installing." And the candidate "grails" version "1.3.9" is installed And the response headers file is created for candidate "grails" and version "1.3.9" And the exit code is 0 Scenario: Do not fail if checksums are disabled Given the system is bootstrapped And I have configured "sdkman_checksum_enable" to "false" And the candidate "grails" version "1.3.9" is available for download with checksum "abc-checksum-00000" using algorithm "SHA-256" When I enter "sdk install grails 1.3.9" Then I see "Checksums are disabled, skipping verification" And I see "Done installing!" And the candidate "grails" version "1.3.9" is installed And the response headers file is created for candidate "grails" and version "1.3.9" And the exit code is 0 @manual Scenario: Abort installation after download of a binary with invalid SHA checksum Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download with checksum "c68e386a6deec9fc4c1e18df21f927000000000e" using algorithm "SHA-256" When I enter "sdk install grails 1.3.9" Then I see "Stop! An invalid checksum was detected and the archive removed! Please try re-installing." And the candidate "grails" version "1.3.9" is not installed And the archive for candidate "grails" version "1.3.9" is removed And the exit code is 1 Scenario: Abort installation after download of a binary with invalid MD5 checksum Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download with checksum "1e87a7d982a2f41da96fdec289908533" using algorithm "MD5" When I enter "sdk install grails 1.3.9" Then I see "Stop! An invalid checksum was detected and the archive removed! Please try re-installing." And the candidate "grails" version "1.3.9" is not installed And the archive for candidate "grails" version "1.3.9" is removed And the exit code is 1 ================================================ FILE: src/test/resources/features/command_line_interop.feature ================================================ Feature: Command Line Interop Background: Given the internet is reachable And an initialised environment And the system is bootstrapped Scenario: Enter sdk When I enter "sdk" Then I see "Usage: sdk [candidate] [version]" And I see "sdk offline " Scenario: Ask for help When I enter "sdk help" Then I see "Usage: sdk [candidate] [version]" Scenario: Enter an invalid Command When I enter "sdk goopoo grails" Then I see "Invalid command: goopoo" And I see "Usage: sdk [candidate] [version]" Scenario: Enter an invalid Candidate When I enter "sdk install groffle" Then I see "Stop! groffle is not a valid candidate." ================================================ FILE: src/test/resources/features/current_candidate.feature ================================================ Feature: Current Candidate Background: Given the internet is reachable And an initialised environment Scenario: Display current candidate version in use Given the candidate "grails" version "1.3.9" is already installed and default And the system is bootstrapped When I enter "sdk current grails" Then I see "Using grails version 1.3.9" Scenario: Display current candidate version when none is in use Given the candidate "grails" version "1.3.9" is already installed but not default And the system is bootstrapped When I enter "sdk current grails" Then I see "Not using any version of grails" Scenario: Display current candidate versions when none is specified and none is in use Given the candidate "grails" version "1.3.9" is already installed but not default And the system is bootstrapped When I enter "sdk current" Then I see "No candidates are in use" Scenario: Display current candidate versions when none is specified and one is in use Given the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped When I enter "sdk current" Then I see "Using:" And I see "grails: 2.1.0" Scenario: Display current candidate versions when none is specified and multiple are in use Given the candidate "groovy" version "2.0.5" is already installed and default And the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped When I enter "sdk current" Then I see "Using:" And I see "grails: 2.1.0" And I see "groovy: 2.0.5" ================================================ FILE: src/test/resources/features/default_version.feature ================================================ Feature: Default Version Background: Given the internet is reachable And an initialised environment Scenario: Default a candidate version that is not installed Given the candidate "groovy" version "2.0.5" is a valid candidate version And the system is bootstrapped When I enter "sdk default groovy 2.0.5" Then I see "Stop! Candidate version is not installed." And I see "Tip: Run the following to install this version" And I see "$ sdk install groovy 2.0.5" Scenario: Default a candidate version that is installed and not default Given the candidate "groovy" version "2.0.5" is a valid candidate version And the candidate "groovy" version "2.0.5" is already installed but not default And the system is bootstrapped When I enter "sdk default groovy 2.0.5" Then I see "Default groovy version set to 2.0.5" And the candidate "groovy" version "2.0.5" should be the default Scenario: Default a candidate version that is installed and already default Given the candidate "groovy" version "2.0.5" is a valid candidate version And the candidate "groovy" version "2.0.5" is already installed and default And the system is bootstrapped When I enter "sdk default groovy 2.0.5" Then I see "Default groovy version set to 2.0.5" And the candidate "groovy" version "2.0.5" should be the default Scenario: Default a candidate version that does not exist Given the candidate "groovy" version "2.9.9" is not available for download And the system is bootstrapped When I enter "sdk default groovy 2.9.9" Then I see "Stop! groovy 2.9.9 is not available." ================================================ FILE: src/test/resources/features/flush.feature ================================================ Feature: Flush Background: Given the internet is reachable And an initialised environment And the system is bootstrapped Scenario: Clear out the temporary storage and metadata And the file "res-1.2.0.zip" in temporary storage And a headers file "grails-1.3.9.headers" in metadata directory with checksum "c68e386a6deec9fc4c1e18df21f92739ba2ab36e" using algorithm "SHA1" When I enter "sdk flush" And no "res-1.2.0.zip" file is present in temporary storage And no metadata is cached And I see "1 archive(s) flushed" And I see "1 archive(s) flushed" Scenario: Clean up the last known Remote Version Given a prior version "5.0.0" was detected When I enter "sdk flush version" Then no version file can be found And I see "Version file has been flushed." Scenario: Clear out the temporary storage Given the file "res-1.2.0.zip" in temporary storage When I enter "sdk flush temp" Then no "res-1.2.0.zip" file is present in temporary storage And I see "1 archive(s) flushed" Scenario: Clear out the metadata Given a headers file "grails-1.3.9.headers" in metadata directory with checksum "c68e386a6deec9fc4c1e18df21f92739ba2ab36e" using algorithm "SHA1" When I enter "sdk flush metadata" Then no metadata is cached And I see "1 archive(s) flushed" ================================================ FILE: src/test/resources/features/home.feature ================================================ Feature: Print home path * Print home directory * Printing the home directory does not require reaching out to the internet. * Printing the home directory also has the hard requirement of not printing anything else unless it is an actual error. Background: Given the internet is reachable And an initialised environment without debug prints Scenario: Home without providing a Candidate Given the system is bootstrapped When I enter "sdk home" Then I see "Usage: sdk [candidate] [version]" Scenario: Home for a candidate version that is installed Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" version "1.3.9" is a valid candidate version And the candidate "grails" version "1.3.9" is already installed but not default And the system is bootstrapped When I enter "sdk home grails 1.3.9" Then the home path ends with ".sdkman/candidates/grails/1.3.9" Scenario: Home for a candidate version that is not installed Given the candidate "grails" version "1.3.9" is available for download And the system is bootstrapped When I enter "sdk home grails 1.3.9" Then I see "Stop! Candidate version is not installed." And I see "Tip: Run the following to install this version" And I see "$ sdk install grails 1.3.9" And the exit code is 1 Scenario: Home for a candidate version that does not exist Given the candidate "groovy" version "1.9.9" is not available for download And the system is bootstrapped When I enter "sdk home groovy 1.9.9" Then I see "Stop! groovy 1.9.9 is not available." Scenario: Home for a candidate version that only exists locally Given the candidate "grails" version "2.0.0.M1" is not available for download And the candidate "grails" version "2.0.0.M1" is already installed but not default And the system is bootstrapped When I enter "sdk home grails 2.0.0.M1" Then the home path ends with ".sdkman/candidates/grails/2.0.0.M1" ================================================ FILE: src/test/resources/features/hooks.feature ================================================ Feature: Hooks We can safely remove this feature when `.tar.gz` and `.zip` are supported directly by the backend. Background: Given the internet is reachable And an initialised environment Scenario: Post-installation Hook returns successfully And an "x86_64" machine with "Linux" installed And the system is bootstrapped And the candidate "grails" version "2.1.0" is available for download on "Linux" with architecture "x86_64" And a post-installation hook is served for "grails" "2.1.0" on "Linux" with architecture "x86_64" that returns successfully When I enter "sdk install grails 2.1.0" And I see "Post-installation hook success" And the exit code is 0 Scenario: Post-install Hook returns a non-zero code And an "x86_64" machine with "Linux" installed And the system is bootstrapped And the candidate "grails" version "2.1.0" is available for download on "Linux" with architecture "x86_64" And a post-installation hook is served for "grails" "2.1.0" on "Linux" with architecture "x86_64" that returns a failure When I enter "sdk install grails 2.1.0" Then I see "Post-installation hook failure" And the exit code is 1 ================================================ FILE: src/test/resources/features/install_candidate.feature ================================================ Feature: Install Candidate Background: Given the internet is reachable And an initialised environment Scenario: Install a default Candidate and set to default Given the system is bootstrapped And the candidate "grails" version "2.1.0" is a valid candidate version And the default "grails" version is "2.1.0" When I enter "sdk install grails" Then I see "Done installing!" And I do not see "Do you want grails 2.1.0 to be set as default? (Y/n)" And the candidate "grails" version "2.1.0" is installed And the response headers file is created for candidate "grails" and version "2.1.0" And the exit code is 0 Scenario: Install a specific Candidate and set to default Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download When I enter "sdk install grails 1.3.9" Then I see "Done installing!" And I do not see "Do you want grails 1.3.9 to be set as default? (Y/n)" And the candidate "grails" version "1.3.9" is installed And the response headers file is created for candidate "grails" and version "1.3.9" And the exit code is 0 Scenario: Install a Candidate version that does not exist Given the system is bootstrapped And the candidate "grails" version "1.4.4" is not available for download When I enter "sdk install grails 1.4.4" Then I see "Stop! grails 1.4.4 is not available." And the exit code is 1 Scenario: Install a Candidate version that is already installed Given the system is bootstrapped And the candidate "grails" version "1.3.9" is available for download And the candidate "grails" version "1.3.9" is already installed and default When I enter "sdk install grails 1.3.9" Then I see "grails 1.3.9 is already installed." And no response headers are written for candidate "grails" and version "1.3.9" And the exit code is 0 Scenario: Install a candidate and auto-answer to make it default Given the system is bootstrapped And the candidate "grails" version "2.1.0" is available for download And I have configured "sdkman_auto_answer" to "true" When I enter "sdk install grails 2.1.0" Then the candidate "grails" version "2.1.0" is installed And the response headers file is created for candidate "grails" and version "2.1.0" And I do not see "Do you want grails 2.1.0 to be set as default?" And I see "Done installing!" And I see "Setting grails 2.1.0 as default." And the candidate "grails" version "2.1.0" should be the default And the exit code is 0 Scenario: Install a candidate and choose to make it default Given the candidate "grails" version "1.3.9" is already installed and default And the system is bootstrapped And the candidate "grails" version "2.1.0" is available for download When I enter "sdk install grails 2.1.0" and answer "Y" Then the candidate "grails" version "2.1.0" is installed And the response headers file is created for candidate "grails" and version "2.1.0" And I see "Done installing!" And I see "Do you want grails 2.1.0 to be set as default? (Y/n)" And I see "Setting grails 2.1.0 as default." And the candidate "grails" version "2.1.0" should be the default And the candidate "grails" version "1.3.9" should not be the default And the exit code is 0 Scenario: Install a candidate and choose not to make it default Given the candidate "grails" version "1.3.9" is already installed and default And the system is bootstrapped And the candidate "grails" version "2.1.0" is available for download When I enter "sdk install grails 2.1.0" and answer "n" Then the candidate "grails" version "2.1.0" is installed And the response headers file is created for candidate "grails" and version "2.1.0" And I see "Done installing!" And I see "Do you want grails 2.1.0 to be set as default? (Y/n)" And I do not see "Setting grails 2.1.0 as default." And the candidate "grails" version "2.1.0" should not be the default And the candidate "grails" version "1.3.9" should be the default And the exit code is 0 # revisit to redownload automatically Scenario: Abort installation on download of a corrupt Candidate archive Given the system is bootstrapped And the candidate "grails" version "1.3.6" is available for download And the archive for candidate "grails" version "1.3.6" is corrupt When I enter "sdk install grails 1.3.6" Then I see "Stop! The archive was corrupt and has been removed! Please try installing again." And the candidate "grails" version "1.3.6" is not installed And the archive for candidate "grails" version "1.3.6" is removed And the exit code is 1 ================================================ FILE: src/test/resources/features/install_sdkman.feature ================================================ @manual Feature: Install SDKMAN Platform defaults as follows: * Ubuntu: .profile, .bashrc * Fedora: .bash_profile, .bashrc * OS X: no skeleton files in user home (nice one Apple!) * Cygwin: .bash_profile, .profile and .bashrc * Solaris: .profile, .bashrc * FreeBSD: .profile with NO .bashrc or bash pre-installed Order of precedence: Login shells (all new terminals) are initialised with the first file found in this order: * .bash_profile * .bash_login * .profile Non-login shells (like xterm shells) read the .bashrc file. Also important to note that usually the .bash_profile or .profile login shell files will check for the .bashrc file and invoke if present! Background: Given a user home exists Scenario: Creates and initialises .bash_profile on absence of login shell dot files Given the user home directory contains no ".bash_profile" file And the user home directory contains no ".profile" file When I run the installation script Then the user home contains a ".bash_profile" file And the ".bash_profile" contains an Initialisation Snippet Scenario: Add Init Snippet to the .bash_profile if present Given the user home contains a ".bash_profile" file When I run the installation script Then the ".bash_profile" contains an Initialisation Snippet Scenario: Add Init Snippet to the .profile if present Given the user home contains a ".profile" file When I run the installation script Then the ".profile" contains an Initialisation Snippet Scenario: Creates and initialises .bashrc on absence of non-login dot files Given the user home directory contains no ".bashrc" file When I run the installation script Then the user home contains a ".bashrc" file And the ".bashrc" contains an Initialisation Snippet Scenario: Always adds Init Snippet to the .bashrc Given the user home contains a ".bashrc" file When I run the installation script Then the ".bashrc" contains an Initialisation Snippet Scenario: Creates and initialises .zshrc on absence of the file Given the user home directory contains no ".zshrc" file When I run the installation script Then the user home contains a ".zshrc" file And the ".zshrc" contains an Initialisation Snippet Scenario: Always adds Init Snippet to the .zshrc Given the user home contains a ".zshrc" file When I run the installation script Then the ".zshrc" contains an Initialisation Snippet Scenario: Source the Initialisation Script on first invokation of the Init Snippet Given the user home contains a ".bash_profile" file And the ".bash_profile" contains an Initialisation Snippet When I open a new Login Shell Then the "sdkman-init.sh" script is sourced once only Scenario: Do not Source the Initialisation Script on subsequent invocation of the Init Snippet Given the user home contains a ".bash_profile" file And the ".bash_profile" contains an Initialisation Snippet And the user home contains a ".bashrc" file And the ".bashrc" contains an Initialisation Snippet When I open a new Login Shell Then the "sdkman-init.sh" script is sourced once only Scenario: Upgrade an installation without configuration Given an uninitialised system And the configuration file has not been primed When I run the installation script Then the configuration file is present ================================================ FILE: src/test/resources/features/java_installation.feature ================================================ Feature: Java Multi Platform Binary Distribution Three versions of Java are used to test various installation scenarios. This feature uses real hooks found in the resources folder under /hooks. The following hooks are available: 8.0.111: post-hook prepared for successful installation 8.0.101: post-hook aborts with non-zero return code Background: Given the internet is reachable And an initialised environment Scenario: Platform is supported and a specific version of compatible binary is installed Given an "x86_64" machine with "Linux" installed And the system is bootstrapped And the candidate "java" version "8.0.111" is available for download on "Linux" with architecture "x86_64" And the appropriate multi-platform hook is available for "java" version "8.0.111" on "Linux" with architecture "x86_64" When I enter "sdk install java 8.0.111" And I see "Done installing!" And the candidate "java" version "8.0.111" is installed Scenario: Platform is supported and a default version of compatible binary is installed Given an "x86_64" machine with "Linux" installed And the system is bootstrapped And the default "java" version is "8.0.111" And the candidate "java" version "8.0.111" is available for download on "Linux" with architecture "x86_64" And the appropriate multi-platform hook is available for "java" version "8.0.111" on "Linux" with architecture "x86_64" When I enter "sdk install java" And I see "Done installing!" And the candidate "java" version "8.0.111" is installed Scenario: Platform is supported but download fails Given an "x86_64" machine with "Linux" installed And the system is bootstrapped And the candidate "java" version "8.0.101" is available for download on "Linux" with architecture "x86_64" And the appropriate multi-platform hook is available for "java" version "8.0.101" on "Linux" with architecture "x86_64" When I enter "sdk install java 8.0.101" And I see "Download has failed, aborting!" And the candidate "java" version "8.0.101" is not installed And I see "Cannot install java 8.0.101 at this time..." Scenario: Platform is not supported for specific version and user is notified And an "x86_64" machine with "Linux" installed And the system is bootstrapped And the candidate "java" version "8.0.111" is not available for download on "Linux" When I enter "sdk install java 8.0.111" Then I see "Stop! java 8.0.111 is not available. Possible causes:" And I see " * 8.0.111 is an invalid version" And I see " * java binaries are incompatible with your platform" And I see " * java has not been released yet" And I see "Tip: see all available versions for your platform:" And I see "$ sdk list java" And the candidate "java" version "8.0.111" is not installed Scenario: Platform is not supported for default version and user is notified And an "x86_64" machine with "Linux" installed And the system is bootstrapped And the default "java" version is "8.0.111" And the candidate "java" version "8.0.111" is not available for download on "Linux" When I enter "sdk install java" Then I see "Stop! java 8.0.111 is not available. Possible causes:" And I see " * 8.0.111 is an invalid version" And I see " * java binaries are incompatible with your platform" And I see " * java has not been released yet" And I see "Tip: see all available versions for your platform:" And I see "$ sdk list java" And the candidate "java" version "8.0.111" is not installed ================================================ FILE: src/test/resources/features/list_candidate_versions.feature ================================================ Feature: List Candidate Versions A dummy template to be served back that has the following information: * Candidate: grails * Current: 2.1.0 * Versions: 2.1.0,2.1.1,2.1.2 (CSV) Background: Given the internet is reachable And an initialised environment Scenario: List an uninstalled available Version Given I do not have a "grails" candidate installed And the candidate "grails" has a version list available And the system is bootstrapped When I enter "sdk list grails" Then I see "Candidate: grails" Scenario: List an installed available Version not in use Given the candidate "grails" version "2.1.0" is already installed but not default And the candidate "grails" has a version list available And the system is bootstrapped When I enter "sdk list grails" Then I see "Versions: 2.1.0" And I do not see "Current: 2.1.0" Scenario: List an installed available Version in use Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" has a version list available And the system is bootstrapped When I enter "sdk list grails" Then I see "Current: 2.1.0" And I see "Versions: 2.1.0" Scenario: List installed multiple Versions Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" version "2.0.9" is already installed but not default And the candidate "grails" has a version list available And the system is bootstrapped When I enter "sdk list grails" Then I see "Current: 2.1.0" And I see "Versions: 2.0.9,2.1.0" Scenario: List an installed local version not in use Given I have a local candidate "grails" version "2.3-SNAPSHOT" at "/tmp/groovy-core" And the candidate "groovy" version "2.3-SNAPSHOT" is already linked to "/tmp/groovy-core" And the candidate "groovy" has a version list available And the system is bootstrapped When I enter "sdk list groovy" Then I see "Versions: 2.3-SNAPSHOT" And I do not see "Current: 2.3-SNAPSHOT" Scenario: List an installed local Version in use Given I have a local candidate "groovy" version "2.2-SNAPSHOT" at "/tmp/groovy-core" And the candidate "groovy" version "2.2-SNAPSHOT" is already linked to "/tmp/groovy-core" And the candidate "groovy" version "2.2-SNAPSHOT" is the default And the candidate "groovy" has a version list available And the system is bootstrapped When I enter "sdk list groovy" Then I see "Current: 2.2-SNAPSHOT" And I see "Versions: 2.2-SNAPSHOT" ================================================ FILE: src/test/resources/features/list_candidates.feature ================================================ Feature: List Candidates Background: Given the internet is reachable And an initialised environment Scenario: A List of Available Candidates can be viewed Given the system is bootstrapped And The candidate list is available When I enter "sdk list" Then I see "Candidate List" ================================================ FILE: src/test/resources/features/local_developement_versions.feature ================================================ Feature: Local Development Versions Background: Given the internet is reachable And an initialised environment Scenario: Install a new local development version Given the candidate "groovy" version "2.1-SNAPSHOT" is not available for download And I have a local candidate "groovy" version "2.1-SNAPSHOT" at "/tmp/groovy-core" And the system is bootstrapped When I enter "sdk install groovy 2.1-SNAPSHOT /tmp/groovy-core" Then I see "Linking groovy 2.1-SNAPSHOT to /tmp/groovy-core" And the candidate "groovy" version "2.1-SNAPSHOT" is linked to "/tmp/groovy-core" Scenario: Attempt installing a local development version that already exists Given the candidate "groovy" version "2.1-SNAPSHOT" is not available for download And the candidate "groovy" version "2.1-SNAPSHOT" is already linked to "/tmp/groovy-core" And the system is bootstrapped When I enter "sdk install groovy 2.1-SNAPSHOT /tmp/groovy-core" Then I see "groovy 2.1-SNAPSHOT is already installed." And the exit code is 0 And the candidate "groovy" version "2.1-SNAPSHOT" is linked to "/tmp/groovy-core" Scenario: Uninstall a local development version Given the candidate "groovy" version "2.1-SNAPSHOT" is already linked to "/tmp/groovy-core" And the system is bootstrapped When I enter "sdk uninstall groovy 2.1-SNAPSHOT" Then I see "Uninstalling groovy 2.1-SNAPSHOT" And the candidate "groovy" version "2.1-SNAPSHOT" is not installed Scenario: Attempt uninstalling a local development version that is not installed Given the candidate "groovy" version "2.1-SNAPSHOT" is not installed And the system is bootstrapped When I enter "sdk uninstall groovy 2.1-SNAPSHOT" Then I see "groovy 2.1-SNAPSHOT is not installed." Scenario: Make the local development version the default for the candidate Given the candidate "groovy" version "2.0.6" is already installed and default And the candidate "groovy" version "2.1-SNAPSHOT" is not available for download And the candidate "groovy" version "2.1-SNAPSHOT" is already linked to "/tmp/groovy-core" And the system is bootstrapped When I enter "sdk default groovy 2.1-SNAPSHOT" Then I see "Default groovy version set to 2.1-SNAPSHOT" And the candidate "groovy" version "2.1-SNAPSHOT" should be the default Scenario: Use a local development version Given the candidate "groovy" version "2.0.6" is already installed and default And the candidate "groovy" version "2.1-SNAPSHOT" is not available for download And the candidate "groovy" version "2.1-SNAPSHOT" is already linked to "/tmp/groovy-core" And the system is bootstrapped When I enter "sdk use groovy 2.1-SNAPSHOT" Then I see "Using groovy version 2.1-SNAPSHOT in this shell" And the candidate "groovy" version "2.1-SNAPSHOT" should be in use Scenario: Install a local development version from a valid relative path Given the candidate "groovy" version "2.1-SNAPSHOT" is not available for download And I have a local candidate "groovy" version "2.1-SNAPSHOT" at relative path "some/relative/path/to/groovy" And the system is bootstrapped When I enter "sdk install groovy 2.1-SNAPSHOT some/relative/path/to/groovy" Then I see "Linking groovy 2.1-SNAPSHOT" And the candidate "groovy" version "2.1-SNAPSHOT" is linked to the relative path "some/relative/path/to/groovy" Scenario: Prevent installation of a local development version for an invalid path Given the candidate "groovy" version "2.1-SNAPSHOT" is not available for download And the system is bootstrapped When I enter "sdk install groovy 2.1-SNAPSHOT /some/bogus/path/to/groovy" Then I see "Invalid path! Refusing to link groovy 2.1-SNAPSHOT to /some/bogus/path/to/groovy." And the candidate "groovy" version "2.1-SNAPSHOT" is not installed Scenario: Prevent installation of a local development version for a long version Given the candidate "groovy" version "2.1-SNAPSHOTLONG" is not available for download And I have a local candidate "groovy" version "2.1-SNAPSHOTLONG" at relative path "some/relative/path/to/groovy" And the system is bootstrapped When I enter "sdk install groovy 2.1-SNAPSHOTLONG some/relative/path/to/groovy" Then I see "Invalid version! 2.1-SNAPSHOTLONG with length 16 exceeds max of 15!" And the candidate "groovy" version "2.1-SNAPSHOTLONG" is not installed Scenario: Allow installation of a local development version for longest possible version Given the candidate "groovy" version "2.1-SNAPSHOT-XX" is not available for download And I have a local candidate "groovy" version "2.1-SNAPSHOT-XX" at "/tmp/groovy-core" And the system is bootstrapped When I enter "sdk install groovy 2.1-SNAPSHOT-XX /tmp/groovy-core" Then I see "Linking groovy 2.1-SNAPSHOT-XX to /tmp/groovy-core" And the candidate "groovy" version "2.1-SNAPSHOT-XX" is linked to "/tmp/groovy-core" Scenario: Allow installation of a local development version for a short version Given the candidate "java" version "graal" is not available for download And I have a local candidate "java" version "graal" at "/tmp/graalvm-1.0.0-rc4-graal" And the system is bootstrapped When I enter "sdk install java graal /tmp/graalvm-1.0.0-rc4-graal" Then I see "Linking java graal to /tmp/graalvm-1.0.0-rc4-graal" And the candidate "java" version "graal" is linked to "/tmp/graalvm-1.0.0-rc4-graal" ================================================ FILE: src/test/resources/features/mnemonics.feature ================================================ Feature: Mnemonics Background: Given the internet is reachable And an initialised environment Scenario: Shortcut for listing an uninstalled available Version Given I do not have a "grails" candidate installed And a "grails" list view is available for consumption And the system is bootstrapped When I enter "sdk l grails" Then I see "Available Grails Versions" Scenario: Alternate shortcut for listing uninstalled available Version Given I do not have a "grails" candidate installed And a "grails" list view is available for consumption And the system is bootstrapped When I enter "sdk ls grails" Then I see "Available Grails Versions" Scenario: Shortcut for displaying current Candidate Version in use Given the candidate "grails" version "1.3.9" is already installed and default And the system is bootstrapped When I enter "sdk c grails" Then I see "Using grails version 1.3.9" Scenario: Shortcut for displaying current Candidate Versions Given the candidate "groovy" version "2.0.5" is already installed and default And the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped When I enter "sdk c" Then I see "Using:" And I see "grails: 2.1.0" And I see "groovy: 2.0.5" Scenario: Shortcut for displaying upgradable Candidate Version in use Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.4.4" And the system is bootstrapped When I enter "sdk ug grails" and answer "n" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.4.4)" Scenario: Shortcut for installing a Candidate Version Given the candidate "grails" version "2.1.0" is not installed And the candidate "grails" version "2.1.0" is available for download And the system is bootstrapped When I enter "sdk i grails 2.1.0" and answer "Y" Then I see "Installing: grails 2.1.0" And the candidate "grails" version "2.1.0" is installed Scenario: Shortcut for uninstalling a Candidate Version Given the candidate "groovy" version "2.0.5" is already installed and default And the system is bootstrapped When I enter "sdk rm groovy 2.0.5" Then I see "Uninstalling groovy 2.0.5" And the candidate "groovy" version "2.0.5" is not installed Scenario: Shortcut for showing the current Version of sdkman Given the system is bootstrapped When I enter "sdk v" Then I see "SDKMAN 5.0.0" Scenario: Shortcut for using a candidate version that is installed Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" version "2.1.0" is a valid candidate version And the candidate "grails" version "1.3.9" is already installed but not default And the candidate "grails" version "1.3.9" is a valid candidate version And the system is bootstrapped When I enter "sdk u grails 1.3.9" Then I see "Using grails version 1.3.9 in this shell." Then the candidate "grails" version "1.3.9" should be in use And the candidate "grails" version "2.1.0" should be the default Scenario: Shortcut for defaulting a Candidate Version that is installed and not default Given the candidate "groovy" version "2.0.5" is already installed but not default And the candidate "groovy" version "2.0.5" is a valid candidate version And the system is bootstrapped When I enter "sdk d groovy 2.0.5" Then I see "Default groovy version set to 2.0.5" And the candidate "groovy" version "2.0.5" should be the default Scenario: Shortcut for displaying Home directory Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" version "2.1.0" is a valid candidate version And the system is bootstrapped When I enter "sdk h grails 2.1.0" Then the home path ends with ".sdkman/candidates/grails/2.1.0" ================================================ FILE: src/test/resources/features/offline_mode.feature ================================================ Feature: Offline Mode # offline modes Scenario: Enter an invalid offline mode Given offline mode is disabled with reachable internet And an initialised environment And the system is bootstrapped When I enter "sdk offline grails" Then I see "Stop! grails is not a valid offline mode." Scenario: Issue Offline command without qualification Given offline mode is disabled with reachable internet And an initialised environment And the system is bootstrapped When I enter "sdk offline" Then I see "Offline mode enabled." Scenario: Enable Offline Mode with internet reachable Given offline mode is disabled with reachable internet And an initialised environment And the system is bootstrapped When I enter "sdk offline enable" Then I see "Offline mode enabled." And I do not see "INTERNET NOT REACHABLE!" When I enter "sdk install grails 2.1.0" Then I do not see "INTERNET NOT REACHABLE!" And I see "Stop! grails 2.1.0 is not available while offline." Scenario: Disable Offline Mode with internet reachable Given offline mode is enabled with reachable internet And the candidate "grails" version "2.1.0" is available for download And an initialised environment And the system is bootstrapped When I enter "sdk offline disable" Then I see "Online mode re-enabled!" When I enter "sdk install grails 2.1.0" and answer "Y" Then I see "Done installing!" And the candidate "grails" version "2.1.0" is installed Scenario: Disable Offline Mode with internet unreachable Given offline mode is enabled with unreachable internet And an initialised environment And the system is bootstrapped When I enter "sdk offline disable" Then I see "Online mode re-enabled!" When I enter "sdk install grails 2.1.0" Then I see "INTERNET NOT REACHABLE!" And I see "Stop! grails 2.1.0 is not available while offline." # sdk version Scenario: Determine the sdkman version while in Offline Mode Given offline mode is enabled with reachable internet And an initialised environment And the system is bootstrapped When I enter "sdk version" Then I see the current sdkman version # list candidate version Scenario: List candidate versions found while in Offline Mode Given offline mode is enabled with reachable internet And an initialised environment And the system is bootstrapped When I enter "sdk list grails" Then I see "Offline: only showing installed grails versions" # default version Scenario: Set the default to an uninstalled candidate version while in Offline Mode Given offline mode is enabled with reachable internet And the candidate "grails" version "1.3.9" is already installed and default And an initialised environment And the system is bootstrapped When I enter "sdk default grails 2.1.0" Then I see "Stop! grails 2.1.0 is not available while offline." # install command Scenario: Install a candidate version that is not installed while in Offline Mode Given offline mode is enabled with reachable internet And the candidate "grails" version "2.1.0" is not installed And an initialised environment And the system is bootstrapped When I enter "sdk install grails 2.1.0" Then I see "Stop! grails 2.1.0 is not available while offline." # uninstall command Scenario: Uninstall a candidate version while in Offline Mode Given offline mode is enabled with reachable internet And the candidate "grails" version "2.1.0" is already installed and default And an initialised environment And the system is bootstrapped When I enter "sdk uninstall grails 2.1.0" And the candidate "grails" version "2.1.0" is not installed # current command Scenario: Display the current version of a candidate while in Offline Mode Given offline mode is enabled with reachable internet And the candidate "grails" version "2.1.0" is already installed and default And an initialised environment And the system is bootstrapped When I enter "sdk current grails" Then I see "Using grails version 2.1.0" # help command Scenario: Request help while in Offline Mode Given offline mode is enabled with reachable internet And an initialised environment And the system is bootstrapped When I enter "sdk help" Then I see "Usage: sdk [candidate] [version]" # selfupdate command Scenario: Attempt self-update while in Offline Mode Given offline mode is enabled with reachable internet And an initialised environment And the system is bootstrapped When I enter "sdk selfupdate" Then I see "This command is not available while offline." ================================================ FILE: src/test/resources/features/path_initialisation.feature ================================================ Feature: Path Initialisation Background: Given the internet is reachable And an initialised environment Scenario: sdkman is initialised for the first time Given the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped When I enter "echo $PATH" Then I see a single occurrence of "grails" Scenario: sdkman is initialised a subsequent time Given the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped And the system is bootstrapped again And I enter "echo $PATH" Then I see a single occurrence of "grails" Scenario: sdkman is initialised without candidates Given the system is bootstrapped When I enter "echo $PATH" Then I see no occurrences of "grails" Scenario: Install a candidate and see it on the PATH And the candidate "grails" version "2.1.0" is available for download And the system is bootstrapped And I enter "sdk install grails 2.1.0" and answer "Y" When I enter "echo $PATH" Then I see a single occurrence of "grails" Scenario: Install multiple candidate versions and see it once on the PATH Given the candidate "grails" version "1.3.9" is available for download And the candidate "grails" version "2.1.0" is available for download And the system is bootstrapped And I enter "sdk install grails 1.3.9" and answer "Y" And I enter "sdk install grails 2.1.0" and answer "Y" When I enter "echo $PATH" Then I see a single occurrence of "grails" ================================================ FILE: src/test/resources/features/per_project_configuration.feature ================================================ Feature: Per-project configuration Background: Given the internet is reachable And an initialised environment Scenario: An sdkman project configuration is generated Given the system is bootstrapped When I enter "sdk env init" Then I see ".sdkmanrc created." Scenario: The env command is issued without an sdkman project configuration present Given the system is bootstrapped When I enter "sdk env" Then I see "Could not find .sdkmanrc in the current directory." And I see "Run 'sdk env init' to create it." And the exit code is 1 Scenario: The env command is issued with an sdkman project configuration present Given the file ".sdkmanrc" exists and contains "groovy=2.4.1" And the candidate "groovy" version "2.0.5" is already installed and default And the candidate "groovy" version "2.4.1" is a valid candidate version And the candidate "groovy" version "2.4.1" is already installed but not default And the system is bootstrapped When I enter "sdk env" Then I see "Using groovy version 2.4.1 in this shell." And the candidate "groovy" version "2.4.1" should be in use And the candidate "groovy" version "2.0.5" should be the default Scenario: The env install subcommand is issued with an sdkman project configuration present Given the system is bootstrapped And the file ".sdkmanrc" exists and contains "groovy=2.4.1" And the candidate "groovy" version "2.0.5" is already installed and default And the candidate "groovy" version "2.4.1" is available for download When I enter "sdk env install" Then I see "Done installing!" And the candidate "groovy" version "2.4.1" is installed And the candidate "groovy" version "2.4.1" is in use And the candidate "groovy" version "2.0.5" should be the default Scenario: The env install subcommand is issued without an sdkman project configuration present Given the system is bootstrapped When I enter "sdk env install" Then I see "Could not find .sdkmanrc in the current directory." And I see "Run 'sdk env init' to create it." And the exit code is 1 Scenario: The env clear subcommand is issued without an active project configuration Given the system is bootstrapped When I enter "sdk env clear" Then I see "No environment currently set!" And the exit code is 1 Scenario: The env clear subcommand is issued and the active project configuration is missing Given the system is bootstrapped And a project configuration is active but points to a directory without configuration When I enter "sdk env clear" Then I see "Could not find" And the exit code is 1 Scenario: The current project configuration is cleared and the default versions restored Given the file ".sdkmanrc" exists and contains "groovy=2.0.5" And the candidate "groovy" version "2.4.1" is already installed and default And the candidate "groovy" version "2.0.5" is in use And the system is bootstrapped And a project configuration is active When I enter "sdk env clear" Then I see "Restored groovy version to 2.4.1 (default)" And the candidate "groovy" version "2.4.1" should be in use ================================================ FILE: src/test/resources/features/self_update.feature ================================================ Feature: Self Update Background: Given the internet is reachable And the sdkman scripts version is "5.0.0" And the sdkman native version is "0.0.1" And an initialised environment And the system is bootstrapped And an available selfupdate endpoint Scenario: Attempt Self Update with out dated scripts components Given the sdkman script version "6.0.0" is available for download And the sdkman native version "0.0.1" is available for download When I enter "sdk selfupdate" Then I see "Successfully upgraded SDKMAN." Scenario: Attempt Self Update with out dated native components Given the sdkman script version "5.0.0" is available for download And the sdkman native version "0.0.2" is available for download When I enter "sdk selfupdate" Then I see "Successfully upgraded SDKMAN." Scenario: Attempt Self Update on an up to date system Given the sdkman script version "5.0.0" is available for download And the sdkman native version "0.0.1" is available for download When I enter "sdk selfupdate" Then I see "No update available at this time." Scenario: Force Self Update on an up to date system Given the sdkman script version "5.0.0" is available for download And the sdkman native version "0.0.1" is available for download When I enter "sdk selfupdate force" Then I see "Successfully upgraded SDKMAN." ================================================ FILE: src/test/resources/features/service_unavailable.feature ================================================ Feature: Service Unavailable Background: Given the internet is not reachable And an initialised environment # list commands Scenario: List candidate versions found while Offline Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" version "1.3.9" is already installed but not default And the system is bootstrapped When I enter "sdk list grails" Then I see "Offline: only showing installed grails versions" And I see "> 2.1.0" And I see "* 1.3.9" Scenario: List candidate versions not found while Offline Given the system is bootstrapped When I enter "sdk list grails" Then I see "Offline: only showing installed grails versions" And I see "None installed!" Scenario: List Available Candidates while Offline Given the system is bootstrapped When I enter "sdk list" Then I see "This command is not available while offline." # use command Scenario: Use an installed candidate version while Offline Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" version "1.3.9" is already installed but not default And the system is bootstrapped When I enter "sdk use grails 1.3.9" Then I see "Using grails version 1.3.9 in this shell." # default command Scenario: Set the default to an uninstalled candidate version while Offline Given the candidate "grails" version "1.3.9" is already installed and default And the system is bootstrapped When I enter "sdk default grails 2.1.0" Then I see "Stop! grails 2.1.0 is not available while offline." Scenario: Set the default to an invalid candidate version while Offline Given the candidate "grails" version "1.3.9" is already installed and default And the system is bootstrapped When I enter "sdk default grails 999" Then I see "Stop! grails 999 is not available while offline." Scenario: Set the default to an installed candidate version while Offline Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" version "1.3.9" is already installed but not default And the system is bootstrapped When I enter "sdk default grails 1.3.9" Then I see "Default grails version set to 1.3.9" # install command Scenario: Install a candidate version that is not installed while Offline Given the candidate "grails" version "2.1.0" is not installed And the system is bootstrapped When I enter "sdk install grails 2.1.0" Then I see "Stop! grails 2.1.0 is not available while offline." Scenario: Install a candidate version that is already installed while Offline Given the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped When I enter "sdk install grails 2.1.0" Then I see "grails 2.1.0 is already installed." And the exit code is 0 # uninstall command Scenario: Uninstall a candidate version while Offline Given the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped When I enter "sdk uninstall grails 2.1.0" Then I see "Deselecting grails 2.1.0..." And I see "Uninstalling grails 2.1.0..." And the candidate "grails" version "2.1.0" is not in use And the candidate "grails" version "2.1.0" is not installed Scenario: Uninstall a candidate version that is not installed while Offline Given the candidate "grails" version "2.1.0" is not installed And the system is bootstrapped When I enter "sdk uninstall grails 2.1.0" Then I see "grails 2.1.0 is not installed." # current command Scenario: Display the current version of a candidate while Offline Given the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped When I enter "sdk current grails" Then I see "Using grails version 2.1.0" Scenario: Display the current version of all candidates while Offline Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "groovy" version "2.0.5" is already installed and default And the system is bootstrapped When I enter "sdk current" Then I see "Using:" And I see "grails: 2.1.0" And I see "groovy: 2.0.5" # version command Scenario: Determine the sdkman version when Offline Given the system is bootstrapped When I enter "sdk version" Then I see the current sdkman version # help command Scenario: Request help while Offline Given the system is bootstrapped When I enter "sdk help" Then I see "Usage: sdk [candidate] [version]" # selfupdate command Scenario: Attempt self-update while Offline Given the system is bootstrapped When I enter "sdk selfupdate" Then I see "This command is not available while offline." ================================================ FILE: src/test/resources/features/uninstall_candidate.feature ================================================ Feature: Uninstall Candidate Background: Given the internet is reachable And an initialised environment Scenario: Uninstall an installed Candidate Version not in use Given the candidate "grails" version "2.1.0" is already installed but not default And the system is bootstrapped When I enter "sdk uninstall grails 2.1.0" Then I do not see "Deselecting grails 2.1.0" Then I see "Uninstalling grails 2.1.0" And the candidate "grails" version "2.1.0" is not installed Scenario: Uninstall a Candidate Version in use Given the candidate "grails" version "2.1.0" is already installed and default And the system is bootstrapped When I enter "sdk uninstall grails 2.1.0" Then I see "Deselecting grails 2.1.0" And I see "Uninstalling grails 2.1.0" And the candidate "grails" version "2.1.0" is not installed And the candidate "grails" is no longer selected Scenario: Attempt uninstalling a Candidate Version that is not installed Given the candidate "grails" version "1.3.9" is not installed And the system is bootstrapped When I enter "sdk uninstall grails 1.3.9" Then I see "grails 1.3.9 is not installed." Scenario: Attempt uninstalling with no Candidate specified Given the system is bootstrapped When I enter "sdk uninstall" Then I see "No candidate provided." Scenario: Attempt uninstalling with an invalid Candidate specified Given the system is bootstrapped When I enter "sdk uninstall groffle" Then I see "Stop! groffle is not a valid candidate." Scenario: Attempt uninstalling without a version provided Given the system is bootstrapped When I enter "sdk uninstall grails" Then I see "No candidate version provided." ================================================ FILE: src/test/resources/features/update.feature ================================================ Feature: Update Background: Given the internet is reachable And the following candidates are available for installation in local cache: | candidate | | activator | | groovy | | scala | And an initialised environment And the system is bootstrapped Scenario: A new candidate is available And the following candidates are currently available from remote API: | candidate | | activator | | groovy | | kotlin | | scala | When I enter "sdk update" Then I see "Adding new candidates(s): kotlin" And the Candidates cache should contain "activator,groovy,kotlin,scala" Scenario: A candidate has been removed And the following candidates are currently available from remote API: | candidate | | groovy | | scala | When I enter "sdk update" Then I see "Removing obsolete candidates(s): activator" And the Candidates cache should contain "groovy,scala" Scenario: A new candidate is available and a candidate has been removed And the following candidates are currently available from remote API: | candidate | | groovy | | kotlin | | scala | When I enter "sdk update" Then I see "Removing obsolete candidates(s): activator" And I see "Adding new candidates(s): kotlin" And the Candidates cache should contain "groovy,kotlin,scala" Scenario: No new candidate is available And the following candidates are currently available from remote API: | candidate | | activator | | groovy | | scala | When I enter "sdk update" Then I see "No new candidates found at this time." And the Candidates cache should contain "activator,groovy,scala" ================================================ FILE: src/test/resources/features/upgrade_candidate.feature ================================================ Feature: Upgrade Candidate Background: Given the internet is reachable And the candidates cache is initialised with "grails" And an initialised environment Scenario: Display upgradable candidate version in use when it is upgradable Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.4.4" And the system is bootstrapped When I enter "sdk upgrade grails" and answer "n" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.4.4)" Scenario: Display upgradable candidate version in use when it is not upgradable Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "1.3.9" And the system is bootstrapped When I enter "sdk upgrade grails" Then I see "grails is up-to-date" Scenario: Display upgradable candidate version when none is in use Given the candidate "grails" does not exist locally And the system is bootstrapped When I enter "sdk upgrade grails" Then I see "Not using any version of grails" Scenario: Display upgradable candidate versions when none is specified and none is in use Given the candidate "grails" does not exist locally And the system is bootstrapped When I enter "sdk upgrade" Then I see "No candidates are in use" Scenario: Display upgradable candidate versions when none is specified and one is in use Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.4.4" And the system is bootstrapped When I enter "sdk upgrade" and answer "n" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.4.4)" Scenario: Display upgradable candidate versions when none is specified and multiple are in use Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.4.4" And the candidate "groovy" version "2.0.5" is already installed and default And the default "groovy" version is "2.4.1" And the system is bootstrapped When I enter "sdk upgrade" and answer "n" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.4.4)" And I see "groovy (local: 2.0.5; default: 2.4.1)" Scenario: Display upgradable candidate versions when none specified and multiple in use but not upgradable Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "1.3.9" And the candidate "groovy" version "2.0.5" is already installed and default And the default "groovy" version is "2.0.5" And the system is bootstrapped When I enter "sdk upgrade" Then I see "All candidates are up-to-date" Scenario: Update all upgradable candidates versions and set them as default Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.1.0" And the candidate "grails" version "2.1.0" is available for download And the candidate "groovy" version "2.0.5" is already installed and default And the default "groovy" version is "2.4.1" And the candidate "groovy" version "2.4.1" is available for download And the system is bootstrapped When I enter "sdk upgrade" and answer "Y" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.1.0)" And I see "groovy (local: 2.0.5; default: 2.4.1)" And I see "Use prescribed default version(s)? (Y/n)" And I do not see "Do you want grails 2.1.0 to be set as default? (Y/n)" And I see "Setting grails 2.1.0 as default." And I do not see "Do you want groovy 2.4.1 to be set as default? (Y/n)" And I see "Setting groovy 2.4.1 as default." Then the candidate "grails" version "2.1.0" should be the default And the candidate "groovy" version "2.4.1" should be the default Scenario: Don't update all upgradable candidates versions and set them as default Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.1.0" And the candidate "grails" version "2.1.0" is available for download And the system is bootstrapped When I enter "sdk upgrade" and answer "N" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.1.0)" And I see "Use prescribed default version(s)? (Y/n)" Then the candidate "grails" version "1.3.9" should be the default Scenario: Update upgradable candidate version and set it as default Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.1.0" And the candidate "grails" version "2.1.0" is available for download And the system is bootstrapped When I enter "sdk upgrade grails" and answer "Y" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.1.0)" And I see "Use prescribed default version(s)? (Y/n): " And I do not see "Do you want grails 2.1.0 to be set as default? (Y/n)" And I see "Setting grails 2.1.0 as default." Then the candidate "grails" version "2.1.0" should be the default Scenario: Update upgradable candidate version and auto-answer to make it default Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.1.0" And the candidate "grails" version "2.1.0" is available for download And I have configured "sdkman_auto_answer" to "true" And the system is bootstrapped When I enter "sdk upgrade grails" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.1.0)" And I do not see "Use prescribed default version(s)? (Y/n): " And I do not see "Do you want grails 2.1.0 to be set as default? (Y/n)" And I see "Setting grails 2.1.0 as default." Then the candidate "grails" version "2.1.0" should be the default Scenario: Don't update upgradable candidate version and set it as default Given the candidate "grails" version "1.3.9" is already installed and default And the default "grails" version is "2.1.0" And the candidate "grails" version "2.1.0" is available for download And the system is bootstrapped When I enter "sdk upgrade grails" and answer "N" Then I see "Available defaults:" And I see "grails (local: 1.3.9; default: 2.1.0)" And I see "Use prescribed default version(s)? (Y/n)" Then the candidate "grails" version "1.3.9" should be the default ================================================ FILE: src/test/resources/features/use_version.feature ================================================ Feature: Use Version Background: Given the internet is reachable And an initialised environment Scenario: Use without providing a Candidate Given the system is bootstrapped When I enter "sdk use" Then I see "Usage: sdk [candidate] [version]" Scenario: Use a candidate version that is installed Given the candidate "grails" version "2.1.0" is already installed and default And the candidate "grails" version "1.3.9" is a valid candidate version And the candidate "grails" version "1.3.9" is already installed but not default And the system is bootstrapped When I enter "sdk use grails 1.3.9" Then I see "Using grails version 1.3.9 in this shell." Then the candidate "grails" version "1.3.9" should be in use And the candidate "grails" version "2.1.0" should be the default Scenario: Use a candidate version that is not installed Given the candidate "groovy" version "1.9.9" is not available for download And the system is bootstrapped When I enter "sdk use groovy 1.9.9" Then I see "Stop! Candidate version is not installed." And I see "Tip: Run the following to install this version" And I see "$ sdk install groovy 1.9.9" Scenario: Use a candidate version that only exists locally Given the candidate "grails" version "2.0.0.M1" is not available for download And the candidate "grails" version "2.0.0.M1" is already installed but not default And the system is bootstrapped When I enter "sdk use grails 2.0.0.M1" Then I see "Using grails version 2.0.0.M1 in this shell." # scenarios related to updating _HOME variable Scenario: Use an installed version of an installed candidate updates the candidate _HOME variable Given the candidate "grails" version "1.3.9" is already installed and default And the candidate "grails" version "2.1.0" is already installed but not default And the candidate "grails" version "2.1.0" is available for download And the system is bootstrapped And the "GRAILS_HOME" variable contains "grails/current" When I enter "sdk use grails 2.1.0" And I see "Using grails version 2.1.0 in this shell." Then the "GRAILS_HOME" variable contains "grails/2.1.0" ================================================ FILE: src/test/resources/features/version.feature ================================================ Feature: Version Scenario: Show the current version of sdkman Given the internet is reachable And the sdkman scripts version is "3.2.1" And an initialised environment And the system is bootstrapped When I enter "sdk version" Then I see "SDKMAN 3.2.1"