diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ed16c2e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,41 @@ +# Project Guidelines + +## Architecture +- This repo has two main testing domains: + - Ruby tooling code in `lib/` with tests in `test/` (Minitest). + - Dev Container Features in `features/src/*` with integration tests in `features/test/*` (devcontainer CLI + shell scripts/scenarios). +- Entry-point scripts in `bin/` usually orchestrate updates that must be validated by Ruby tests and feature tests. + +## Build and Test +- Prefer running the smallest relevant test set first, then broader suites. + +### Ruby tests (root) +- Install deps: `bundle install` +- Run all Ruby tests: `bundle exec rake test` +- Equivalent: `rake test` + +### Feature tests (from repo root) +- Install Node deps (if needed): `npm install` +- Run all feature tests via package script: `npm test` +- Preferred shortcut via Rake: `rake features:test` + +### Feature tests (targeted, from repo root) +- Prerequisite: install project deps from repo root (`npm install`) to get the devcontainer CLI from `package.json`. +- Autogenerated tests for one feature: + - `rake features:autogenerated FEATURE=ruby IMAGE=ubuntu:latest` +- Scenario tests for one feature: + - `rake features:scenarios FEATURE=ruby` +- Direct CLI equivalents (run from `features/`): + - `npx devcontainer features test --skip-scenarios -f ruby -i ubuntu:latest .` + - `npx devcontainer features test -f ruby --skip-autogenerated --skip-duplicated .` + +### Shell script linting (from `features/`) +- `find . -name "*.sh" -type f -exec shellcheck {} +` + +## Conventions +- Keep tests close to the affected area: + - `lib/**` changes should include/adjust `test/**/*_test.rb`. + - `features/src//**` changes should include/adjust `features/test//**`. +- Preserve current patterns in existing tests: + - Ruby tests use Minitest style and temp-directory isolation. + - Feature tests use `dev-container-features-test-lib` assertions (`check`, `reportResults`) and scenario matrices in `scenarios.json`. diff --git a/Rakefile b/Rakefile index 995b134..19d1239 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,7 @@ # frozen_string_literal: true require "rake/testtask" +require "shellwords" Rake::TestTask.new(:test) do |t| t.libs << "test" @@ -9,3 +10,27 @@ Rake::TestTask.new(:test) do |t| end task default: :test + +namespace :features do + desc "Run all devcontainer feature tests via npm" + task :test do + sh "npm test" + end + + desc "Run autogenerated tests for one feature (FEATURE=ruby IMAGE=ubuntu:latest)" + task :autogenerated do + feature = ENV["FEATURE"] + abort "Set FEATURE, for example: rake features:autogenerated FEATURE=ruby" if feature.nil? || feature.empty? + + image = ENV.fetch("IMAGE", "ubuntu:latest") + sh "cd features && npx devcontainer features test --skip-scenarios -f #{Shellwords.escape(feature)} -i #{Shellwords.escape(image)} ." + end + + desc "Run scenario tests for one feature (FEATURE=ruby)" + task :scenarios do + feature = ENV["FEATURE"] + abort "Set FEATURE, for example: rake features:scenarios FEATURE=ruby" if feature.nil? || feature.empty? + + sh "cd features && npx devcontainer features test -f #{Shellwords.escape(feature)} --skip-autogenerated --skip-duplicated ." + end +end diff --git a/features/src/ruby/NOTES.md b/features/src/ruby/NOTES.md index f058ff0..31f1198 100644 --- a/features/src/ruby/NOTES.md +++ b/features/src/ruby/NOTES.md @@ -21,3 +21,16 @@ This Feature should work on recent versions of Debian/Ubuntu-based distributions } } ``` + +## Opting In to Precompiled Rubies with mise + +```json +"features": { + "ghcr.io/rails/devcontainer/features/ruby:1": { + "versionManager": "mise", + "usePrecompiledRubies": true + } +} +``` + +When using `mise`, this feature keeps `ruby.compile=true` by default so Ruby is compiled from source. Set `usePrecompiledRubies` to `true` to make mise prefer precompiled Rubies when they are available. diff --git a/features/src/ruby/README.md b/features/src/ruby/README.md index d74040e..7690c14 100644 --- a/features/src/ruby/README.md +++ b/features/src/ruby/README.md @@ -36,12 +36,27 @@ Installs Ruby and a version manager (mise or rbenv) along with the dependencies } ``` +### Opting in to precompiled Rubies with mise + +```json +"features": { + "ghcr.io/rails/devcontainer/features/ruby:1": { + "version": "3.3.0", + "versionManager": "mise", + "usePrecompiledRubies": true + } +} +``` + +When using `mise`, this feature keeps `ruby.compile=true` by default so Ruby is compiled from source. Set `usePrecompiledRubies` to `true` to make mise prefer precompiled Rubies when they are available. + ## Options | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| | version | The version of ruby to be installed | string | 4.0.2 | | versionManager | The version manager to use for Ruby (mise or rbenv) | string | mise | +| usePrecompiledRubies | Use precompiled Rubies with mise when available | boolean | false | ## Customizations diff --git a/features/src/ruby/devcontainer-feature.json b/features/src/ruby/devcontainer-feature.json index 972f823..c919b7c 100644 --- a/features/src/ruby/devcontainer-feature.json +++ b/features/src/ruby/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "ruby", - "version": "2.1.4", + "version": "2.2.0", "name": "Ruby", "description": "Installs Ruby and a version manager (mise or rbenv) along with libraries needed to build Ruby.", "documentationURL": "https://github.com/rails/devcontainer/tree/main/features/src/ruby", @@ -30,6 +30,11 @@ ], "default": "mise", "description": "The version manager to use for Ruby (mise or rbenv)" + }, + "usePrecompiledRubies": { + "type": "boolean", + "default": false, + "description": "Use precompiled Rubies with mise when available" } } } diff --git a/features/src/ruby/install.sh b/features/src/ruby/install.sh index 36972d2..db1ed07 100755 --- a/features/src/ruby/install.sh +++ b/features/src/ruby/install.sh @@ -3,6 +3,7 @@ set -e USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" VERSION_MANAGER="${VERSIONMANAGER:-"mise"}" +USE_PRECOMPILED_RUBIES="${USEPRECOMPILEDRUBIES:-"false"}" # Function to install dependencies needed for building Ruby install_dependencies() { @@ -83,8 +84,20 @@ install_ruby_rbenv() { # Function to setup mise setup_mise() { _user="$1" + _use_precompiled_rubies="$2" + + if [ "$_user" = "root" ]; then + _home_dir="/root" + else + _home_dir="/home/$_user" + fi su "$_user" -c "curl https://mise.run | sh" + if [ "$_use_precompiled_rubies" = "true" ]; then + su "$_user" -c "$_home_dir/.local/bin/mise settings ruby.compile=false" + else + su "$_user" -c "$_home_dir/.local/bin/mise settings ruby.compile=true" + fi # shellcheck disable=SC2016 add_to_shell_init "$_user" 'eval "$(~/.local/bin/mise activate bash)"' 'eval "$(~/.local/bin/mise activate zsh)"' @@ -113,7 +126,7 @@ if [ "$VERSION_MANAGER" = "rbenv" ]; then setup_rbenv "$USERNAME" install_ruby_rbenv "$USERNAME" "$VERSION" else - setup_mise "$USERNAME" + setup_mise "$USERNAME" "$USE_PRECOMPILED_RUBIES" install_ruby_mise "$USERNAME" "$VERSION" fi diff --git a/features/test/ruby/scenarios.json b/features/test/ruby/scenarios.json index 9a9d151..88dcc0a 100644 --- a/features/test/ruby/scenarios.json +++ b/features/test/ruby/scenarios.json @@ -3,7 +3,8 @@ "image": "mcr.microsoft.com/devcontainers/base:2-trixie", "features": { "ruby": { - "version": "3.3.0" + "version": "3.3.0", + "usePrecompiledRubies": true } } }, @@ -14,5 +15,13 @@ "versionManager": "rbenv" } } + }, + "with_precompiled_rubies": { + "image": "mcr.microsoft.com/devcontainers/base:2-trixie", + "features": { + "ruby": { + "usePrecompiledRubies": true + } + } } } diff --git a/features/test/ruby/test.sh b/features/test/ruby/test.sh index 73aa0a1..738605a 100644 --- a/features/test/ruby/test.sh +++ b/features/test/ruby/test.sh @@ -6,6 +6,7 @@ source dev-container-features-test-lib check "mise is installed" bash -c "mise --version" check "mise init is sourced in the bashrc" bash -c "grep 'eval \"\$(~/.local/bin/mise activate bash)\"' $HOME/.bashrc" +check "mise is configured to compile Ruby from source by default" bash -c "mise settings | grep ruby.compile | grep true" check "mise idiomatic version file is enabled for ruby" bash -c "mise settings | grep idiomatic_version_file_enable_tools | grep ruby" check "Ruby is installed with YJIT" bash -c "RUBY_YJIT_ENABLE=1 ruby -v | grep +YJIT" check "Ruby version is set to 4.0.2" bash -c "mise use -g ruby | grep 4.0.2" diff --git a/features/test/ruby/version_3_3_0.sh b/features/test/ruby/version_3_3_0.sh index 5857ed7..ed5e312 100644 --- a/features/test/ruby/version_3_3_0.sh +++ b/features/test/ruby/version_3_3_0.sh @@ -7,6 +7,7 @@ source dev-container-features-test-lib check "mise is installed" bash -c "mise --version" check "mise init is sourced in the bashrc" bash -c "grep 'eval \"\$(~/.local/bin/mise activate bash)\"' $HOME/.bashrc" check "mise init is sourced in the zshrc" bash -c "grep 'eval \"\$(~/.local/bin/mise activate zsh)\"' $HOME/.zshrc" +check "mise uses precompiled Rubies when enabled" bash -c "mise settings | grep ruby.compile | grep false" check "mise idiomatic version file is enabled for ruby" bash -c "mise settings | grep idiomatic_version_file_enable_tools | grep ruby" check "Ruby is installed with YJIT" bash -c "RUBY_YJIT_ENABLE=1 ruby -v | grep +YJIT" check "Ruby version is set to 3.3.0" bash -c "mise use -g ruby | grep 3.3.0" diff --git a/features/test/ruby/with_precompiled_rubies.sh b/features/test/ruby/with_precompiled_rubies.sh new file mode 100644 index 0000000..269c7e5 --- /dev/null +++ b/features/test/ruby/with_precompiled_rubies.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +# shellcheck source=/dev/null +source dev-container-features-test-lib + +check "mise is installed" bash -c "mise --version" +check "mise uses precompiled Rubies when enabled" bash -c "mise settings | grep ruby.compile | grep false" +check "mise idiomatic version file is enabled for ruby" bash -c "mise settings | grep idiomatic_version_file_enable_tools | grep ruby" +check "Ruby is installed with YJIT" bash -c "RUBY_YJIT_ENABLE=1 ruby -v | grep +YJIT" +check "Ruby version is set to 4.0.2" bash -c "mise use -g ruby | grep 4.0.2" + +reportResults \ No newline at end of file diff --git a/images/ruby/.devcontainer/devcontainer.json b/images/ruby/.devcontainer/devcontainer.json index 0edde11..033472b 100644 --- a/images/ruby/.devcontainer/devcontainer.json +++ b/images/ruby/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ "ppa": "false" }, "ghcr.io/rails/devcontainer/features/ruby": { - "version": "${localEnv:RUBY_VERSION}" + "version": "${localEnv:RUBY_VERSION}", + "usePrecompiledRubies": "true" } }, // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. diff --git a/package-lock.json b/package-lock.json index 15a28bc..14dc319 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,55 +8,20 @@ "name": "ruby-version-script", "version": "1.0.0", "devDependencies": { - "@devcontainers/cli": "^0.56.0", - "js-yaml": "^4.1.0", - "semver": "^7.5.4" + "@devcontainers/cli": ">= 0.84" } }, "node_modules/@devcontainers/cli": { - "version": "0.56.2", - "resolved": "https://registry.npmjs.org/@devcontainers/cli/-/cli-0.56.2.tgz", - "integrity": "sha512-GI/d2tgaf4zFQJ5kbEZ/3Aci8NfkXzsk6gYUqQB8cUrJ+K/xPw6KZfbA4G6HabGky6eSpVdc4HMzea3kWZj9Jw==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@devcontainers/cli/-/cli-0.84.1.tgz", + "integrity": "sha512-r+JR/4R8lznPQNwLyHPIzHJ1mj3p2l5lGyHeq2FetEfpe6s6BVLE9mFl7MxQI4wKNqfWCIO7DSokoCWRlzQSIg==", "dev": true, "license": "MIT", "bin": { "devcontainer": "devcontainer.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "node": ">=20.0.0" } } } diff --git a/package.json b/package.json index b4e096d..506d05c 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,9 @@ { - "name": "ruby-version-script", + "name": "rails-devcontainer-features", "version": "1.0.0", - "description": "Script dependencies for adding Ruby versions", + "description": "Rails DevContainer features and images", "devDependencies": { - "js-yaml": "^4.1.0", - "semver": "^7.5.4", - "@devcontainers/cli": "^0.56.0" + "@devcontainers/cli": ">= 0.84" }, "scripts": { "test": "devcontainer features test features -i ubuntu:noble" diff --git a/test/commands/add_ruby_version_test.rb b/test/commands/add_ruby_version_test.rb index 8885a4b..8fa163a 100644 --- a/test/commands/add_ruby_version_test.rb +++ b/test/commands/add_ruby_version_test.rb @@ -162,6 +162,11 @@ def create_feature_json(version: "2.0.0", default_ruby: "3.3.0") "type" => "string", "default" => default_ruby, "description" => "The ruby version to be installed" + }, + "usePrecompiledRubies" => { + "type" => "boolean", + "default" => false, + "description" => "Use precompiled Rubies with mise when available" } } } @@ -184,6 +189,7 @@ def create_readme(default_version: "3.3.0") |-----|-----|-----|-----| | version | The version of ruby to be installed | string | #{default_version} | | versionManager | The version manager to use | string | mise | + | usePrecompiledRubies | Use precompiled Rubies with mise when available | boolean | false | README File.write(File.join(@temp_dir, "features/src/ruby/README.md"), content) end diff --git a/test/ruby_version_adder_test.rb b/test/ruby_version_adder_test.rb index b07cf45..5573033 100644 --- a/test/ruby_version_adder_test.rb +++ b/test/ruby_version_adder_test.rb @@ -431,6 +431,11 @@ def create_feature_json(version: "2.0.0", default_ruby: "3.3.0") "type" => "string", "default" => default_ruby, "description" => "The ruby version to be installed" + }, + "usePrecompiledRubies" => { + "type" => "boolean", + "default" => false, + "description" => "Use precompiled Rubies with mise when available" } } } @@ -453,6 +458,7 @@ def create_readme(default_version: "3.3.0") |-----|-----|-----|-----| | version | The version of ruby to be installed | string | #{default_version} | | versionManager | The version manager to use | string | mise | + | usePrecompiledRubies | Use precompiled Rubies with mise when available | boolean | false | README File.write(File.join(@temp_dir, "features/src/ruby/README.md"), content) end