From 0b4309d266a10a5e44810c54596c3ade21d3a3bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 17 Mar 2026 06:48:21 +0000 Subject: [PATCH 1/2] Add opt-in precompiled Ruby installs via mise Add a usePrecompiledRubies feature option for the Ruby feature and wire it into the mise install path via `ruby.compile` settings. Keep source builds as the feature default for the next release, opt the Ruby image into precompiled Rubies explicitly, and update the Ruby feature docs, scenarios, and fixture tests accordingly. Also refresh the devcontainer CLI dependency and package metadata used by the feature test workflow. --- features/src/ruby/NOTES.md | 13 ++++++ features/src/ruby/README.md | 15 +++++++ features/src/ruby/devcontainer-feature.json | 7 ++- features/src/ruby/install.sh | 15 ++++++- features/test/ruby/scenarios.json | 11 ++++- features/test/ruby/test.sh | 1 + features/test/ruby/version_3_3_0.sh | 1 + features/test/ruby/with_precompiled_rubies.sh | 13 ++++++ images/ruby/.devcontainer/devcontainer.json | 3 +- package-lock.json | 45 +++---------------- package.json | 8 ++-- test/commands/add_ruby_version_test.rb | 6 +++ test/ruby_version_adder_test.rb | 6 +++ 13 files changed, 95 insertions(+), 49 deletions(-) create mode 100644 features/test/ruby/with_precompiled_rubies.sh 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 From dbbdffaf8ad1b91dd8d439cfa402453ecc72523a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 17 Mar 2026 07:20:15 +0000 Subject: [PATCH 2/2] Add AGENTS.md with project guidelines and update Rakefile with feature test tasks --- AGENTS.md | 41 +++++++++++++++++++++++++++++++++++++++++ Rakefile | 25 +++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 AGENTS.md 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