├── .github ├── codeql │ └── extensions │ │ └── homebrew-actions.yml ├── dependabot.yml ├── workflows │ ├── actionlint.yml │ ├── autobump.yml │ ├── build.yml │ ├── release.yml │ ├── stale-issues.yml │ └── tests.yml └── zizmor.yml ├── .rubocop.yml ├── .ruby-version ├── Abstract └── portable-formula.rb ├── Formula ├── portable-libffi.rb ├── portable-libxcrypt.rb ├── portable-libyaml.rb ├── portable-openssl.rb ├── portable-ruby.rb └── portable-zlib.rb ├── LICENSE.txt ├── README.md └── cmd ├── portable-package.rb └── portable-package.rbi /.github/codeql/extensions/homebrew-actions.yml: -------------------------------------------------------------------------------- 1 | extensions: 2 | - addsTo: 3 | pack: codeql/actions-all 4 | extensible: trustedActionsOwnerDataModel 5 | data: 6 | - ["Homebrew"] 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # This file is synced from the `.github` repository, do not modify it directly. 2 | --- 3 | version: 2 4 | updates: 5 | - package-ecosystem: github-actions 6 | directory: "/" 7 | schedule: 8 | interval: weekly 9 | day: friday 10 | time: '08:00' 11 | timezone: Etc/UTC 12 | allow: 13 | - dependency-type: all 14 | groups: 15 | github-actions: 16 | patterns: 17 | - "*" 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/actionlint.yml: -------------------------------------------------------------------------------- 1 | # This file is synced from the `.github` repository, do not modify it directly. 2 | name: Actionlint 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | pull_request: 10 | 11 | defaults: 12 | run: 13 | shell: bash -xeuo pipefail {0} 14 | 15 | concurrency: 16 | group: "actionlint-${{ github.ref }}" 17 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 18 | 19 | env: 20 | HOMEBREW_DEVELOPER: 1 21 | HOMEBREW_NO_AUTO_UPDATE: 1 22 | HOMEBREW_NO_ENV_HINTS: 1 23 | 24 | permissions: {} 25 | 26 | jobs: 27 | workflow_syntax: 28 | if: github.repository_owner == 'Homebrew' 29 | runs-on: ubuntu-latest 30 | permissions: 31 | contents: read 32 | steps: 33 | - name: Set up Homebrew 34 | id: setup-homebrew 35 | uses: Homebrew/actions/setup-homebrew@master 36 | with: 37 | core: false 38 | cask: false 39 | test-bot: false 40 | 41 | - name: Install tools 42 | run: brew install actionlint shellcheck zizmor 43 | 44 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 45 | with: 46 | persist-credentials: false 47 | 48 | - run: zizmor --format sarif . > results.sarif 49 | 50 | - name: Upload SARIF file 51 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 52 | # We can't use the SARIF file when triggered by `merge_group` so we don't upload it. 53 | if: always() && github.event_name != 'merge_group' 54 | with: 55 | name: results.sarif 56 | path: results.sarif 57 | 58 | - name: Set up actionlint 59 | run: echo "::add-matcher::$(brew --repository)/.github/actionlint-matcher.json" 60 | 61 | - run: actionlint 62 | 63 | upload_sarif: 64 | needs: workflow_syntax 65 | # We want to always upload this even if `actionlint` failed. 66 | # This is only available on public repositories. 67 | if: > 68 | always() && 69 | !contains(fromJSON('["cancelled", "skipped"]'), needs.workflow_syntax.result) && 70 | !github.event.repository.private && 71 | github.event_name != 'merge_group' 72 | runs-on: ubuntu-latest 73 | permissions: 74 | contents: read 75 | security-events: write 76 | steps: 77 | - name: Download SARIF file 78 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 79 | with: 80 | name: results.sarif 81 | path: results.sarif 82 | 83 | - name: Upload SARIF file 84 | uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 85 | with: 86 | sarif_file: results.sarif 87 | category: zizmor 88 | -------------------------------------------------------------------------------- /.github/workflows/autobump.yml: -------------------------------------------------------------------------------- 1 | name: Bump formulae on schedule or request 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - .github/workflows/autobump.yml 9 | workflow_dispatch: 10 | inputs: 11 | formulae: 12 | description: Custom list of portable-ruby formulae to livecheck and bump if outdated 13 | required: false 14 | schedule: 15 | # Daily at midnight 16 | - cron: "0 0 * * *" 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | autobump: 23 | if: github.repository_owner == 'Homebrew' 24 | runs-on: ubuntu-latest 25 | container: 26 | image: ghcr.io/homebrew/ubuntu22.04:master 27 | steps: 28 | - name: Set up Homebrew 29 | id: set-up-homebrew 30 | uses: Homebrew/actions/setup-homebrew@master 31 | with: 32 | core: false 33 | cask: false 34 | test-bot: false 35 | 36 | - name: Configure Git user 37 | uses: Homebrew/actions/git-user-config@master 38 | with: 39 | username: ${{ (github.event_name == 'workflow_dispatch' && github.actor) || 'BrewTestBot' }} 40 | 41 | - name: Get list of autobump portable-ruby formulae 42 | id: autobump 43 | run: echo "autobump_list=$(brew tap-info --json "${GITHUB_REPOSITORY}" | jq -r '.[].formula_names|join(" ")')" >> "$GITHUB_OUTPUT" 44 | 45 | - name: Bump portable-ruby formulae 46 | uses: Homebrew/actions/bump-packages@master 47 | continue-on-error: true 48 | with: 49 | token: ${{ secrets.HOMEBREW_PORTABLE_RUBY_REPO_WORKFLOW_TOKEN }} 50 | formulae: ${{ github.event.inputs.formulae || steps.autobump.outputs.autobump_list }} 51 | env: 52 | HOMEBREW_TEST_BOT_AUTOBUMP: 1 53 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_call: 5 | 6 | permissions: 7 | contents: read 8 | 9 | env: 10 | HOMEBREW_DEVELOPER: 1 11 | HOMEBREW_NO_AUTO_UPDATE: 1 12 | HOMEBREW_ARM64_TESTING: 1 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | include: 19 | - os: 10.11-cross-${{github.run_id}} 20 | - os: 11-arm64-cross-${{github.run_id}} 21 | - os: ubuntu-latest 22 | container: '{"image": "ghcr.io/homebrew/ubuntu22.04:master", "options": "--user=linuxbrew"}' 23 | workdir: /github/home 24 | - os: ubuntu-22.04-arm 25 | container: '{"image": "ghcr.io/homebrew/ubuntu22.04:master", "options": "--user=linuxbrew"}' 26 | workdir: /github/home 27 | fail-fast: false 28 | runs-on: ${{matrix.os}} 29 | container: ${{matrix.container && fromJSON(matrix.container) || ''}} 30 | defaults: 31 | run: 32 | working-directory: ${{matrix.workdir || github.workspace}} 33 | steps: 34 | - name: Set up Homebrew 35 | uses: Homebrew/actions/setup-homebrew@master 36 | 37 | - run: brew test-bot --only-cleanup-before 38 | 39 | - run: brew test-bot --only-setup 40 | 41 | - name: Build Portable Ruby 42 | run: | 43 | mkdir -p bottle/ 44 | cd bottle 45 | brew portable-package --verbose portable-ruby 46 | 47 | - name: Upload Portable Ruby 48 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 49 | with: 50 | name: bottles_${{matrix.os}} 51 | path: ${{matrix.workdir || github.workspace}}/bottle 52 | 53 | - name: Test Portable Ruby 54 | run: | 55 | mkdir -p portable-ruby/ 56 | tar --strip-components 2 -C portable-ruby -xf bottle/portable-ruby--*.tar.gz 57 | export PATH="${PWD}/portable-ruby/bin:${PATH}" 58 | export HOMEBREW_USE_RUBY_FROM_PATH=1 59 | rm -rf "$(brew --repo)/Library/Homebrew/vendor/portable-ruby" 60 | brew config | awk -v s="${PWD}/portable-ruby/bin/ruby" '$0~s{r=1} 1; END{exit(!r)}' 61 | 62 | - name: Post cleanup 63 | if: always() 64 | run: | 65 | brew test-bot --only-cleanup-after 66 | rm -rvf bottle portable-ruby 67 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | HOMEBREW_DEVELOPER: 1 8 | HOMEBREW_NO_AUTO_UPDATE: 1 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | determine-tag: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | tag: ${{ steps.determine-tag.outputs.tag }} 18 | steps: 19 | - name: Set up Homebrew 20 | uses: Homebrew/actions/setup-homebrew@master 21 | with: 22 | test-bot: false 23 | 24 | - name: Determine tag 25 | id: determine-tag 26 | run: | 27 | brew ruby -e "$(cat< 28 | github.repository_owner == 'Homebrew' && ( 29 | github.event_name != 'issue_comment' || ( 30 | contains(github.event.issue.labels.*.name, 'stale') || 31 | contains(github.event.pull_request.labels.*.name, 'stale') 32 | ) 33 | ) 34 | runs-on: ubuntu-latest 35 | permissions: 36 | contents: write 37 | issues: write 38 | pull-requests: write 39 | steps: 40 | - name: Mark/Close Stale Issues and Pull Requests 41 | uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 42 | with: 43 | repo-token: ${{ secrets.GITHUB_TOKEN }} 44 | days-before-stale: 21 45 | days-before-close: 7 46 | stale-issue-message: > 47 | This issue has been automatically marked as stale because it has not had 48 | recent activity. It will be closed if no further activity occurs. 49 | stale-pr-message: > 50 | This pull request has been automatically marked as stale because it has not had 51 | recent activity. It will be closed if no further activity occurs. 52 | exempt-issue-labels: "gsoc-outreachy,help wanted,in progress" 53 | exempt-pr-labels: "gsoc-outreachy,help wanted,in progress" 54 | delete-branch: true 55 | 56 | bump-pr-stale: 57 | if: > 58 | github.repository_owner == 'Homebrew' && ( 59 | github.event_name != 'issue_comment' || ( 60 | contains(github.event.issue.labels.*.name, 'stale') || 61 | contains(github.event.pull_request.labels.*.name, 'stale') 62 | ) 63 | ) 64 | runs-on: ubuntu-latest 65 | permissions: 66 | contents: write 67 | issues: write 68 | pull-requests: write 69 | steps: 70 | - name: Mark/Close Stale `bump-formula-pr` and `bump-cask-pr` Pull Requests 71 | uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 72 | with: 73 | repo-token: ${{ secrets.GITHUB_TOKEN }} 74 | days-before-stale: 2 75 | days-before-close: 1 76 | stale-pr-message: > 77 | This pull request has been automatically marked as stale because it has not had 78 | recent activity. It will be closed if no further activity occurs. To keep this 79 | pull request open, add a `help wanted` or `in progress` label. 80 | exempt-pr-labels: "help wanted,in progress" 81 | any-of-labels: "bump-formula-pr,bump-cask-pr" 82 | delete-branch: true 83 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: master 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | 11 | concurrency: 12 | group: "${{ github.ref }}" 13 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 14 | 15 | jobs: 16 | syntax: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Set up Homebrew 20 | uses: Homebrew/actions/setup-homebrew@master 21 | 22 | - run: brew test-bot --only-tap-syntax 23 | 24 | build: 25 | needs: syntax 26 | if: github.event_name != 'push' 27 | uses: ./.github/workflows/build.yml 28 | 29 | # This job is used as a required status check, instead of requiring each build matrix element. 30 | # When using ephemeral runners, the name of those runs change every time so they cannot be set as required. 31 | conclusion: 32 | needs: build 33 | runs-on: ubuntu-latest 34 | if: github.event_name != 'push' && !cancelled() 35 | steps: 36 | - name: Result 37 | env: 38 | RESULT: ${{ needs.build.result }} 39 | run: | 40 | [[ "${RESULT}" == success ]] 41 | -------------------------------------------------------------------------------- /.github/zizmor.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | unpinned-uses: 3 | config: 4 | policies: 5 | Homebrew/actions/*: ref-pin 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # This file is synced from `Homebrew/brew` by the `.github` repository, do not modify it directly. 2 | --- 3 | AllCops: 4 | ParserEngine: parser_prism 5 | TargetRubyVersion: 3.3 6 | NewCops: enable 7 | Include: 8 | - "**/*.rbi" 9 | Exclude: 10 | - Homebrew/sorbet/rbi/{annotations,dsl,gems}/**/*.rbi 11 | - Homebrew/sorbet/rbi/parser*.rbi 12 | - Homebrew/bin/* 13 | - Homebrew/vendor/**/* 14 | - Taps/*/*/vendor/**/* 15 | - "**/vendor/**/*" 16 | SuggestExtensions: 17 | rubocop-minitest: false 18 | Layout/ArgumentAlignment: 19 | Exclude: 20 | - Taps/*/*/*.rb 21 | - "/**/Formula/**/*.rb" 22 | - "**/Formula/**/*.rb" 23 | Layout/CaseIndentation: 24 | EnforcedStyle: end 25 | Layout/FirstArrayElementIndentation: 26 | EnforcedStyle: consistent 27 | Layout/FirstHashElementIndentation: 28 | EnforcedStyle: consistent 29 | Layout/EndAlignment: 30 | EnforcedStyleAlignWith: start_of_line 31 | Layout/HashAlignment: 32 | EnforcedHashRocketStyle: table 33 | EnforcedColonStyle: table 34 | Layout/LeadingCommentSpace: 35 | Exclude: 36 | - Taps/*/*/cmd/*.rb 37 | Layout/LineLength: 38 | Max: 118 39 | AllowedPatterns: 40 | - "#: " 41 | - ' url "' 42 | - ' mirror "' 43 | - " plist_options " 44 | - ' executable: "' 45 | - ' font "' 46 | - ' homepage "' 47 | - ' name "' 48 | - ' pkg "' 49 | - ' pkgutil: "' 50 | - " sha256 cellar: " 51 | - " sha256 " 52 | - "#{language}" 53 | - "#{version." 54 | - ' "/Library/Application Support/' 55 | - "\"/Library/Caches/" 56 | - "\"/Library/PreferencePanes/" 57 | - ' "~/Library/Application Support/' 58 | - "\"~/Library/Caches/" 59 | - "\"~/Library/Containers" 60 | - "\"~/Application Support" 61 | - " was verified as official when first introduced to the cask" 62 | Layout/SpaceAroundOperators: 63 | Enabled: false 64 | Layout/SpaceBeforeBrackets: 65 | Exclude: 66 | - "**/*_spec.rb" 67 | - Taps/*/*/*.rb 68 | - "/**/{Formula,Casks}/**/*.rb" 69 | - "**/{Formula,Casks}/**/*.rb" 70 | Lint/AmbiguousBlockAssociation: 71 | Enabled: false 72 | Lint/DuplicateBranch: 73 | Exclude: 74 | - Taps/*/*/*.rb 75 | - "/**/{Formula,Casks}/**/*.rb" 76 | - "**/{Formula,Casks}/**/*.rb" 77 | Lint/ParenthesesAsGroupedExpression: 78 | Exclude: 79 | - Taps/*/*/*.rb 80 | - "/**/Formula/**/*.rb" 81 | - "**/Formula/**/*.rb" 82 | Lint/UnusedMethodArgument: 83 | AllowUnusedKeywordArguments: true 84 | Metrics: 85 | Enabled: false 86 | Naming/BlockForwarding: 87 | Enabled: false 88 | Naming/FileName: 89 | Regex: !ruby/regexp /^[\w\@\-\+\.]+(\.rb)?$/ 90 | Naming/HeredocDelimiterNaming: 91 | ForbiddenDelimiters: 92 | - END, EOD, EOF 93 | Naming/InclusiveLanguage: 94 | CheckStrings: true 95 | FlaggedTerms: 96 | slave: 97 | AllowedRegex: 98 | - gitslave 99 | - log_slave 100 | - ssdb_slave 101 | - var_slave 102 | - patches/13_fix_scope_for_show_slave_status_data.patch 103 | Naming/MethodName: 104 | AllowedPatterns: 105 | - "\\A(fetch_)?HEAD\\?\\Z" 106 | Naming/MethodParameterName: 107 | inherit_mode: 108 | merge: 109 | - AllowedNames 110 | Naming/VariableNumber: 111 | Enabled: false 112 | Style/AndOr: 113 | EnforcedStyle: always 114 | Style/ArgumentsForwarding: 115 | Enabled: false 116 | Style/AutoResourceCleanup: 117 | Enabled: true 118 | Style/BarePercentLiterals: 119 | EnforcedStyle: percent_q 120 | Style/BlockDelimiters: 121 | BracesRequiredMethods: 122 | - sig 123 | Style/ClassAndModuleChildren: 124 | Exclude: 125 | - "**/*.rbi" 126 | Style/CollectionMethods: 127 | Enabled: true 128 | Style/DisableCopsWithinSourceCodeDirective: 129 | Enabled: true 130 | Include: 131 | - Taps/*/*/*.rb 132 | - "/**/{Formula,Casks}/**/*.rb" 133 | - "**/{Formula,Casks}/**/*.rb" 134 | Style/Documentation: 135 | Exclude: 136 | - Taps/**/* 137 | - "/**/{Formula,Casks}/**/*.rb" 138 | - "**/{Formula,Casks}/**/*.rb" 139 | - "**/*.rbi" 140 | Style/EmptyMethod: 141 | Exclude: 142 | - "**/*.rbi" 143 | Style/FetchEnvVar: 144 | Exclude: 145 | - Taps/*/*/*.rb 146 | - "/**/Formula/**/*.rb" 147 | - "**/Formula/**/*.rb" 148 | Style/FrozenStringLiteralComment: 149 | EnforcedStyle: always 150 | Exclude: 151 | - Taps/*/*/*.rb 152 | - "/**/{Formula,Casks}/**/*.rb" 153 | - "**/{Formula,Casks}/**/*.rb" 154 | - Homebrew/test/**/Casks/**/*.rb 155 | - "**/*.rbi" 156 | - "**/Brewfile" 157 | Style/GuardClause: 158 | Exclude: 159 | - Taps/*/*/*.rb 160 | - "/**/{Formula,Casks}/**/*.rb" 161 | - "**/{Formula,Casks}/**/*.rb" 162 | Style/HashAsLastArrayItem: 163 | Exclude: 164 | - Taps/*/*/*.rb 165 | - "/**/Formula/**/*.rb" 166 | - "**/Formula/**/*.rb" 167 | Style/InverseMethods: 168 | InverseMethods: 169 | :blank?: :present? 170 | Style/InvertibleUnlessCondition: 171 | Enabled: true 172 | InverseMethods: 173 | :==: :!= 174 | :zero?: 175 | :blank?: :present? 176 | Style/MutableConstant: 177 | EnforcedStyle: strict 178 | Style/NumericLiteralPrefix: 179 | EnforcedOctalStyle: zero_only 180 | Style/NumericLiterals: 181 | MinDigits: 11 182 | Strict: true 183 | Style/OpenStructUse: 184 | Exclude: 185 | - Taps/**/* 186 | Style/OptionalBooleanParameter: 187 | AllowedMethods: 188 | - respond_to? 189 | - respond_to_missing? 190 | Style/RedundantLineContinuation: 191 | Enabled: false 192 | Style/RescueStandardError: 193 | EnforcedStyle: implicit 194 | Style/ReturnNil: 195 | Enabled: true 196 | Style/StderrPuts: 197 | Enabled: false 198 | Style/StringConcatenation: 199 | Exclude: 200 | - Taps/*/*/*.rb 201 | - "/**/{Formula,Casks}/**/*.rb" 202 | - "**/{Formula,Casks}/**/*.rb" 203 | Style/StringLiterals: 204 | EnforcedStyle: double_quotes 205 | Style/StringLiteralsInInterpolation: 206 | EnforcedStyle: double_quotes 207 | Style/StringMethods: 208 | Enabled: true 209 | Style/SuperWithArgsParentheses: 210 | Enabled: false 211 | Style/SymbolArray: 212 | EnforcedStyle: brackets 213 | Style/TernaryParentheses: 214 | EnforcedStyle: require_parentheses_when_complex 215 | Style/TopLevelMethodDefinition: 216 | Enabled: true 217 | Exclude: 218 | - Taps/**/* 219 | Style/TrailingCommaInArguments: 220 | EnforcedStyleForMultiline: comma 221 | Style/TrailingCommaInArrayLiteral: 222 | EnforcedStyleForMultiline: comma 223 | Style/TrailingCommaInHashLiteral: 224 | EnforcedStyleForMultiline: comma 225 | Style/UnlessLogicalOperators: 226 | Enabled: true 227 | EnforcedStyle: forbid_logical_operators 228 | Style/WordArray: 229 | MinSize: 4 230 | 231 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.4 2 | -------------------------------------------------------------------------------- /Abstract/portable-formula.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PortableFormulaMixin 4 | if OS.mac? 5 | if Hardware::CPU.arm? 6 | TARGET_MACOS = :big_sur 7 | TARGET_DARWIN_VERSION = Version.new("20.1.0").freeze 8 | else 9 | TARGET_MACOS = :el_capitan 10 | TARGET_DARWIN_VERSION = Version.new("15.0.0").freeze 11 | end 12 | 13 | CROSS_COMPILING = OS.kernel_version.major != TARGET_DARWIN_VERSION.major 14 | end 15 | 16 | def portable_configure_args 17 | # Allow cross-compile between Darwin versions (used by our fake El Capitan on High Sierra setup) 18 | if OS.mac? && CROSS_COMPILING 19 | cpu = if Hardware::CPU.arm? 20 | "aarch64" 21 | else 22 | "x86_64" 23 | end 24 | %W[ 25 | --build=#{cpu}-apple-darwin#{OS.kernel_version} 26 | --host=#{cpu}-apple-darwin#{TARGET_DARWIN_VERSION} 27 | ] 28 | else 29 | [] 30 | end 31 | end 32 | 33 | def install 34 | if OS.mac? 35 | if OS::Mac.version > TARGET_MACOS 36 | target_macos_humanized = TARGET_MACOS.to_s.tr("_", " ").split.map(&:capitalize).join(" ") 37 | 38 | opoo <<~EOS 39 | You are building portable formula on #{OS::Mac.version}. 40 | As result, formula won't be able to work on older macOS versions. 41 | It's recommended to build this formula on macOS #{target_macos_humanized} 42 | (the oldest version that can run Homebrew). 43 | EOS 44 | end 45 | 46 | # Always prefer to linking to portable libs. 47 | ENV.append "LDFLAGS", "-Wl,-search_paths_first" 48 | elsif OS.linux? 49 | # reset Linuxbrew env, because we want to build formula against 50 | # libraries offered by system (CentOS docker) rather than Linuxbrew. 51 | ENV.delete "LDFLAGS" 52 | ENV.delete "LIBRARY_PATH" 53 | ENV.delete "LD_RUN_PATH" 54 | ENV.delete "LD_LIBRARY_PATH" 55 | ENV.delete "TERMINFO_DIRS" 56 | ENV.delete "HOMEBREW_RPATH_PATHS" 57 | ENV.delete "HOMEBREW_DYNAMIC_LINKER" 58 | 59 | # https://github.com/Homebrew/homebrew-portable-ruby/issues/118 60 | ENV.append_to_cflags "-fPIC" 61 | end 62 | 63 | super 64 | end 65 | 66 | def test 67 | refute_match(/Homebrew libraries/, 68 | shell_output("#{HOMEBREW_BREW_FILE} linkage #{full_name}")) 69 | 70 | super 71 | end 72 | end 73 | 74 | class PortableFormula < Formula 75 | desc "Abstract portable formula" 76 | homepage "https://github.com/Homebrew/homebrew-portable-ruby" 77 | 78 | def self.inherited(subclass) 79 | subclass.class_eval do 80 | super 81 | 82 | keg_only "portable formulae are keg-only" 83 | 84 | on_linux do 85 | on_intel do 86 | depends_on "glibc@2.13" => :build 87 | end 88 | on_arm do 89 | depends_on "glibc@2.17" => :build 90 | end 91 | depends_on "linux-headers@4.4" => :build 92 | end 93 | 94 | prepend PortableFormulaMixin 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /Formula/portable-libffi.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../Abstract/portable-formula", __dir__) 2 | 3 | class PortableLibffi < PortableFormula 4 | desc "Portable Foreign Function Interface library" 5 | homepage "https://sourceware.org/libffi/" 6 | url "https://github.com/libffi/libffi/releases/download/v3.5.1/libffi-3.5.1.tar.gz" 7 | sha256 "f99eb68a67c7d54866b7706af245e87ba060d419a062474b456d3bc8d4abdbd1" 8 | license "MIT" 9 | 10 | livecheck do 11 | formula "libffi" 12 | end 13 | 14 | def install 15 | system "./configure", *portable_configure_args, 16 | *std_configure_args, 17 | "--enable-static", 18 | "--disable-shared", 19 | "--disable-docs" 20 | system "make", "install" 21 | end 22 | 23 | test do 24 | (testpath/"closure.c").write <<~EOS 25 | #include 26 | #include 27 | /* Acts like puts with the file given at time of enclosure. */ 28 | void puts_binding(ffi_cif *cif, unsigned int *ret, void* args[], 29 | FILE *stream) 30 | { 31 | *ret = fputs(*(char **)args[0], stream); 32 | } 33 | int main() 34 | { 35 | ffi_cif cif; 36 | ffi_type *args[1]; 37 | ffi_closure *closure; 38 | int (*bound_puts)(char *); 39 | int rc; 40 | /* Allocate closure and bound_puts */ 41 | closure = ffi_closure_alloc(sizeof(ffi_closure), &bound_puts); 42 | if (closure) 43 | { 44 | /* Initialize the argument info vectors */ 45 | args[0] = &ffi_type_pointer; 46 | /* Initialize the cif */ 47 | if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, 48 | &ffi_type_uint, args) == FFI_OK) 49 | { 50 | /* Initialize the closure, setting stream to stdout */ 51 | if (ffi_prep_closure_loc(closure, &cif, puts_binding, 52 | stdout, bound_puts) == FFI_OK) 53 | { 54 | rc = bound_puts("Hello World!"); 55 | /* rc now holds the result of the call to fputs */ 56 | } 57 | } 58 | } 59 | /* Deallocate both closure, and bound_puts */ 60 | ffi_closure_free(closure); 61 | return 0; 62 | } 63 | EOS 64 | 65 | flags = ["-L#{lib}", "-lffi", "-I#{include}"] 66 | system ENV.cc, "-o", "closure", "closure.c", *(flags + ENV.cflags.to_s.split) 67 | system "./closure" 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /Formula/portable-libxcrypt.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../Abstract/portable-formula", __dir__) 2 | 3 | class PortableLibxcrypt < PortableFormula 4 | desc "Extended crypt library for descrypt, md5crypt, bcrypt, and others" 5 | homepage "https://github.com/besser82/libxcrypt" 6 | url "https://github.com/besser82/libxcrypt/releases/download/v4.4.38/libxcrypt-4.4.38.tar.xz" 7 | sha256 "80304b9c306ea799327f01d9a7549bdb28317789182631f1b54f4511b4206dd6" 8 | license "LGPL-2.1-or-later" 9 | 10 | livecheck do 11 | formula "libxcrypt" 12 | end 13 | 14 | def install 15 | system "./configure", *portable_configure_args, 16 | *std_configure_args, 17 | "--enable-static", 18 | "--disable-shared", 19 | "--disable-obsolete-api", 20 | "--disable-xcrypt-compat-files", 21 | "--disable-failure-tokens", 22 | "--disable-valgrind" 23 | system "make", "install" 24 | end 25 | 26 | test do 27 | (testpath/"test.c").write <<~EOS 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | int main() 34 | { 35 | char *hash = crypt("abc", "$2b$05$abcdefghijklmnopqrstuu"); 36 | 37 | if (errno) { 38 | fprintf(stderr, "Received error: %s", strerror(errno)); 39 | return errno; 40 | } 41 | if (hash == NULL) { 42 | fprintf(stderr, "Hash is NULL"); 43 | return -1; 44 | } 45 | if (strcmp(hash, "$2b$05$abcdefghijklmnopqrstuuRWUgMyyCUnsDr8evYotXg5ZXVF/HhzS")) { 46 | fprintf(stderr, "Unexpected hash output"); 47 | return -1; 48 | } 49 | 50 | return 0; 51 | } 52 | EOS 53 | system ENV.cc, "test.c", "-I#{include}", "-L#{lib}", "-lcrypt", "-o", "test" 54 | system "./test" 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /Formula/portable-libyaml.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../Abstract/portable-formula", __dir__) 2 | 3 | class PortableLibyaml < PortableFormula 4 | desc "YAML Parser" 5 | homepage "https://github.com/yaml/libyaml" 6 | url "https://github.com/yaml/libyaml/releases/download/0.2.5/yaml-0.2.5.tar.gz" 7 | sha256 "c642ae9b75fee120b2d96c712538bd2cf283228d2337df2cf2988e3c02678ef4" 8 | license "MIT" 9 | 10 | livecheck do 11 | formula "libyaml" 12 | end 13 | 14 | def install 15 | system "./configure", *portable_configure_args, 16 | "--disable-dependency-tracking", 17 | "--prefix=#{prefix}", 18 | "--enable-static", 19 | "--disable-shared" 20 | system "make", "install" 21 | end 22 | 23 | test do 24 | (testpath/"test.c").write <<~EOS 25 | #include 26 | 27 | int main() 28 | { 29 | yaml_parser_t parser; 30 | yaml_parser_initialize(&parser); 31 | yaml_parser_delete(&parser); 32 | return 0; 33 | } 34 | EOS 35 | system ENV.cc, "test.c", "-I#{include}", "-L#{lib}", "-lyaml", "-o", "test" 36 | system "./test" 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /Formula/portable-openssl.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../Abstract/portable-formula", __dir__) 2 | 3 | class PortableOpenssl < PortableFormula 4 | desc "Cryptography and SSL/TLS Toolkit" 5 | homepage "https://openssl.org/" 6 | url "https://github.com/openssl/openssl/releases/download/openssl-3.4.1/openssl-3.4.1.tar.gz" 7 | mirror "https://www.openssl.org/source/openssl-3.4.1.tar.gz" 8 | mirror "http://fresh-center.net/linux/misc/openssl-3.4.1.tar.gz" 9 | sha256 "002a2d6b30b58bf4bea46c43bdd96365aaf8daa6c428782aa4feee06da197df3" 10 | license "Apache-2.0" 11 | 12 | livecheck do 13 | url :stable 14 | strategy :github_releases do |json, regex| 15 | json.filter_map do |release| 16 | next if release["draft"] || release["prerelease"] 17 | 18 | match = release["tag_name"]&.match(regex) 19 | next if match.blank? 20 | 21 | version = Version.new(match[1]) 22 | next if version.patch.to_i.zero? 23 | 24 | version 25 | end 26 | end 27 | end 28 | 29 | resource "cacert" do 30 | # https://curl.se/docs/caextract.html 31 | url "https://curl.se/ca/cacert-2025-05-20.pem" 32 | sha256 "ab3ee3651977a4178a702b0b828a4ee7b2bbb9127235b0ab740e2e15974bf5db" 33 | 34 | livecheck do 35 | url "https://curl.se/ca/cadate.t" 36 | regex(/^#define\s+CA_DATE\s+(.+)$/) 37 | strategy :page_match do |page, regex| 38 | match = page.match(regex) 39 | next if match.blank? 40 | 41 | Date.parse(match[1]).iso8601 42 | end 43 | end 44 | end 45 | 46 | def openssldir 47 | libexec/"etc/openssl" 48 | end 49 | 50 | def arch_args 51 | if OS.mac? 52 | %W[darwin64-#{Hardware::CPU.arch}-cc enable-ec_nistp_64_gcc_128] 53 | elsif Hardware::CPU.intel? 54 | if Hardware::CPU.is_64_bit? 55 | ["linux-x86_64"] 56 | else 57 | ["linux-elf"] 58 | end 59 | elsif Hardware::CPU.arm? 60 | if Hardware::CPU.is_64_bit? 61 | ["linux-aarch64"] 62 | else 63 | ["linux-armv4"] 64 | end 65 | end 66 | end 67 | 68 | def configure_args 69 | %W[ 70 | --prefix=#{prefix} 71 | --openssldir=#{openssldir} 72 | --libdir=#{lib} 73 | no-legacy 74 | no-module 75 | no-shared 76 | no-engine 77 | ] 78 | end 79 | 80 | def install 81 | # OpenSSL is not fully portable and certificate paths are backed into the library. 82 | # We therefore need to set the certificate path at runtime via an environment variable. 83 | # We however don't want to touch _other_ OpenSSL usages, so we change the variable name to differ. 84 | inreplace "include/internal/common.h", "\"SSL_CERT_FILE\"", "\"PORTABLE_RUBY_SSL_CERT_FILE\"" 85 | 86 | openssldir.mkpath 87 | system "perl", "./Configure", *(configure_args + arch_args) 88 | system "make" 89 | system "make", "test" 90 | 91 | system "make", "install_dev" 92 | 93 | # Ruby doesn't support passing --static to pkg-config. 94 | # Unfortunately, this means we need to modify the OpenSSL pc file. 95 | # This is a Ruby bug - not an OpenSSL one. 96 | inreplace lib/"pkgconfig/libcrypto.pc", "\nLibs.private:", "" 97 | 98 | cacert = resource("cacert") 99 | filename = Pathname.new(cacert.url).basename 100 | openssldir.install cacert.files(filename => "cert.pem") 101 | end 102 | 103 | test do 104 | (testpath/"test.c").write <<~EOS 105 | #include 106 | #include 107 | #include 108 | 109 | int main(int argc, char *argv[]) 110 | { 111 | if (argc < 2) 112 | return -1; 113 | 114 | unsigned char md[EVP_MAX_MD_SIZE]; 115 | unsigned int size; 116 | 117 | if (!EVP_Digest(argv[1], strlen(argv[1]), md, &size, EVP_sha256(), NULL)) 118 | return 1; 119 | 120 | for (unsigned int i = 0; i < size; i++) 121 | printf("%02x", md[i]); 122 | return 0; 123 | } 124 | EOS 125 | system ENV.cc, "test.c", "-L#{lib}", "-lcrypto", "-o", "test" 126 | assert_equal "717ac506950da0ccb6404cdd5e7591f72018a20cbca27c8a423e9c9e5626ac61", 127 | shell_output("./test 'This is a test string'") 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /Formula/portable-ruby.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../Abstract/portable-formula", __dir__) 2 | 3 | class PortableRuby < PortableFormula 4 | desc "Powerful, clean, object-oriented scripting language" 5 | homepage "https://www.ruby-lang.org/" 6 | url "https://cache.ruby-lang.org/pub/ruby/3.4/ruby-3.4.4.tar.gz" 7 | sha256 "a0597bfdf312e010efd1effaa8d7f1d7833146fdc17950caa8158ffa3dcbfa85" 8 | license "Ruby" 9 | 10 | # This regex restricts matching to versions other than X.Y.0. 11 | livecheck do 12 | formula "ruby" 13 | regex(/href=.*?ruby[._-]v?(\d+\.\d+\.(?:(?!0)\d+)(?:\.\d+)*)\.t/i) 14 | end 15 | 16 | depends_on "pkgconf" => :build 17 | depends_on "portable-libyaml" => :build 18 | depends_on "portable-openssl" => :build 19 | 20 | on_linux do 21 | depends_on "portable-libffi" => :build 22 | depends_on "portable-libxcrypt" => :build 23 | depends_on "portable-zlib" => :build 24 | end 25 | 26 | resource "msgpack" do 27 | url "https://rubygems.org/downloads/msgpack-1.8.0.gem" 28 | sha256 "e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732" 29 | 30 | livecheck do 31 | url "https://rubygems.org/api/v1/versions/msgpack.json" 32 | strategy :json do |json| 33 | json.first["number"] 34 | end 35 | end 36 | end 37 | 38 | resource "bootsnap" do 39 | url "https://rubygems.org/downloads/bootsnap-1.18.6.gem" 40 | sha256 "0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00" 41 | 42 | livecheck do 43 | url "https://rubygems.org/api/v1/versions/bootsnap.json" 44 | strategy :json do |json| 45 | json.first["number"] 46 | end 47 | end 48 | end 49 | 50 | # Fix compile on macOS 10.11 51 | patch do 52 | url "https://github.com/Bo98/ruby/commit/7aec5ca6e8ec13d92307615c32a511e02437d7de.patch?full_index=1" 53 | sha256 "644f706bbbb708c2e1d32de65138c335c3710e6d47f86624f9dd98806627e83f" 54 | end 55 | 56 | def install 57 | # Remove almost all bundled gems and replace with our own set. 58 | rm_r ".bundle" 59 | allowed_gems = ["debug"] 60 | bundled_gems = File.foreach("gems/bundled_gems").select do |line| 61 | line.blank? || line.start_with?("#") || allowed_gems.any? { |gem| line.match?(/\A#{Regexp.escape(gem)}\s/) } 62 | end 63 | rm_r(Dir["gems/*.gem"].reject do |gem_path| 64 | gem_basename = File.basename(gem_path) 65 | allowed_gems.any? { |gem| gem_basename.match?(/\A#{Regexp.escape(gem)}-\d/) } 66 | end) 67 | resources.each do |resource| 68 | resource.stage "gems" 69 | bundled_gems << "#{resource.name} #{resource.version}\n" 70 | end 71 | File.write("gems/bundled_gems", bundled_gems.join) 72 | 73 | libyaml = Formula["portable-libyaml"] 74 | libxcrypt = Formula["portable-libxcrypt"] 75 | openssl = Formula["portable-openssl"] 76 | libffi = Formula["portable-libffi"] 77 | zlib = Formula["portable-zlib"] 78 | 79 | args = portable_configure_args + %W[ 80 | --prefix=#{prefix} 81 | --enable-load-relative 82 | --with-static-linked-ext 83 | --with-baseruby=#{RbConfig.ruby} 84 | --with-out-ext=win32,win32ole 85 | --without-gmp 86 | --disable-install-doc 87 | --disable-install-rdoc 88 | --disable-dependency-tracking 89 | ] 90 | 91 | # We don't specify OpenSSL as we want it to use the pkg-config, which `--with-openssl-dir` will disable 92 | args += %W[ 93 | --with-libyaml-dir=#{libyaml.opt_prefix} 94 | ] 95 | 96 | if OS.linux? 97 | ENV["XCFLAGS"] = "-I#{libxcrypt.opt_include}" 98 | ENV["XLDFLAGS"] = "-L#{libxcrypt.opt_lib}" 99 | 100 | args += %W[ 101 | --with-libffi-dir=#{libffi.opt_prefix} 102 | --with-zlib-dir=#{zlib.opt_prefix} 103 | ] 104 | 105 | # Ensure compatibility with older Ubuntu when built with Ubuntu 22.04 106 | args << "MKDIR_P=/bin/mkdir -p" 107 | 108 | # Don't make libruby link to zlib as it means all extensions will require it 109 | # It's also not used with the older glibc we use anyway 110 | args << "ac_cv_lib_z_uncompress=no" 111 | end 112 | 113 | # Append flags rather than override 114 | ENV["cflags"] = ENV.delete("CFLAGS") 115 | ENV["cppflags"] = ENV.delete("CPPFLAGS") 116 | ENV["cxxflags"] = ENV.delete("CXXFLAGS") 117 | 118 | system "./configure", *args 119 | system "make", "extract-gems" 120 | system "make" 121 | 122 | # Add a helper load path file so bundled gems can be easily used (used by brew's standalone/init.rb) 123 | system "make", "ruby.pc" 124 | arch = Utils.safe_popen_read("pkg-config", "--variable=arch", "./ruby-#{version.major_minor}.pc").chomp 125 | mkdir_p "lib/#{arch}" 126 | File.open("lib/#{arch}/portable_ruby_gems.rb", "w") do |file| 127 | (Dir["extensions/*/*/*", base: ".bundle"] + Dir["gems/*/lib", base: ".bundle"]).each do |require_path| 128 | file.write <<~RUBY 129 | $:.unshift "\#{RbConfig::CONFIG["rubylibprefix"]}/gems/\#{RbConfig::CONFIG["ruby_version"]}/#{require_path}" 130 | RUBY 131 | end 132 | end 133 | 134 | system "make", "install" 135 | 136 | abi_version = `#{bin}/ruby -rrbconfig -e 'print RbConfig::CONFIG["ruby_version"]'` 137 | abi_arch = `#{bin}/ruby -rrbconfig -e 'print RbConfig::CONFIG["arch"]'` 138 | 139 | if OS.linux? 140 | # Don't restrict to a specific GCC compiler binary we used (e.g. gcc-5). 141 | inreplace lib/"ruby/#{abi_version}/#{abi_arch}/rbconfig.rb" do |s| 142 | s.gsub! ENV.cxx, "c++" 143 | s.gsub! ENV.cc, "cc" 144 | # Change e.g. `CONFIG["AR"] = "gcc-ar-11"` to `CONFIG["AR"] = "ar"` 145 | s.gsub!(/(CONFIG\[".+"\] = )"gcc-(.*)-\d+"/, '\\1"\\2"') 146 | # C++ compiler might have been disabled because we break it with glibc@* builds 147 | s.sub!(/(CONFIG\["CXX"\] = )"false"/, '\\1"c++"') 148 | end 149 | 150 | # Ship libcrypt.a so that building native gems doesn't need system libcrypt installed. 151 | cp libxcrypt.lib/"libcrypt.a", lib/"libcrypt.a" 152 | end 153 | 154 | libexec.mkpath 155 | cp openssl.libexec/"etc/openssl/cert.pem", libexec/"cert.pem" 156 | openssl_rb = lib/"ruby/#{abi_version}/openssl.rb" 157 | inreplace openssl_rb, "require 'openssl.so'", <<~EOS.chomp 158 | ENV["PORTABLE_RUBY_SSL_CERT_FILE"] = ENV["SSL_CERT_FILE"] || File.expand_path("../../libexec/cert.pem", RbConfig.ruby) 159 | \\0 160 | EOS 161 | end 162 | 163 | test do 164 | cp_r Dir["#{prefix}/*"], testpath 165 | ENV["PATH"] = "/usr/bin:/bin" 166 | ruby = (testpath/"bin/ruby").realpath 167 | assert_equal version.to_s.split("-").first, shell_output("#{ruby} -e 'puts RUBY_VERSION'").chomp 168 | assert_equal ruby.to_s, shell_output("#{ruby} -e 'puts RbConfig.ruby'").chomp 169 | assert_equal "3632233996", 170 | shell_output("#{ruby} -rzlib -e 'puts Zlib.crc32(\"test\")'").chomp 171 | assert_equal " \t\n`><=;|&{(", 172 | shell_output("#{ruby} -rreadline -e 'puts Readline.basic_word_break_characters'").chomp 173 | assert_equal '{"a" => "b"}', 174 | shell_output("#{ruby} -ryaml -e 'puts YAML.load(\"a: b\")'").chomp 175 | assert_equal "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 176 | shell_output("#{ruby} -ropenssl -e 'puts OpenSSL::Digest::SHA256.hexdigest(\"\")'").chomp 177 | assert_match "200", 178 | shell_output("#{ruby} -ropen-uri -e 'URI.open(\"https://google.com\") { |f| puts f.status.first }'").chomp 179 | system ruby, "-rrbconfig", "-e", <<~EOS 180 | Gem.discover_gems_on_require = false 181 | require "portable_ruby_gems" 182 | require "debug" 183 | require "fiddle" 184 | require "bootsnap" 185 | EOS 186 | system testpath/"bin/gem", "environment" 187 | system testpath/"bin/bundle", "init" 188 | # install gem with native components 189 | system testpath/"bin/gem", "install", "byebug" 190 | assert_match "byebug", 191 | shell_output("#{testpath}/bin/byebug --version") 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /Formula/portable-zlib.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../Abstract/portable-formula", __dir__) 2 | 3 | class PortableZlib < PortableFormula 4 | desc "General-purpose lossless data-compression library" 5 | homepage "https://zlib.net/" 6 | url "https://zlib.net/zlib-1.3.1.tar.gz" 7 | mirror "https://downloads.sourceforge.net/project/libpng/zlib/1.3.1/zlib-1.3.1.tar.gz" 8 | mirror "http://fresh-center.net/linux/misc/zlib-1.3.1.tar.gz" 9 | mirror "http://fresh-center.net/linux/misc/legacy/zlib-1.3.1.tar.gz" 10 | sha256 "9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23" 11 | license "Zlib" 12 | 13 | livecheck do 14 | formula "zlib" 15 | end 16 | 17 | # https://zlib.net/zlib_how.html 18 | resource "test_artifact" do 19 | url "https://zlib.net/zpipe.c" 20 | version "20051211" 21 | sha256 "68140a82582ede938159630bca0fb13a93b4bf1cb2e85b08943c26242cf8f3a6" 22 | 23 | livecheck do 24 | skip "Static test artifact" 25 | end 26 | end 27 | 28 | def install 29 | system "./configure", "--static", "--prefix=#{prefix}" 30 | system "make", "install" 31 | end 32 | 33 | test do 34 | testpath.install resource("test_artifact") 35 | system ENV.cc, "zpipe.c", "-I#{include}", "-L#{lib}", "-lz", "-o", "zpipe" 36 | 37 | touch "foo.txt" 38 | output = "./zpipe < foo.txt > foo.txt.z" 39 | system output 40 | assert File.exist?("foo.txt.z") 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright Homebrew contributors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Homebrew Portable Ruby 2 | 3 | Formulae and tools to build versions of Ruby that can be installed and run from anywhere on the filesystem. 4 | 5 | ## How do I install these formulae 6 | 7 | Just `brew install homebrew/portable-ruby/`. 8 | 9 | ## How do I build packages for these formulae 10 | 11 | Homebrew Portable Ruby is designed only for usage internally to Homebrew. If Portable Ruby isn't available for your platform, it is recommended you instead use Ruby from your system's package manager (if available) or rbenv/ruby-build. Usage of Portable Ruby outside of Homebrew, such as embedding into your own apps, is not a goal for this project. 12 | 13 | ## How do I issue a new release 14 | 15 | [An automated release workflow is available to use](https://github.com/Homebrew/homebrew-portable-ruby/actions/workflows/release.yml). 16 | Dispatch the workflow and all steps of building, tagging and uploading should be handled automatically. 17 | 18 |
19 | Manual steps are documented below. 20 | 21 | ### Build 22 | 23 | Run `brew portable-package ruby`. For macOS, this should ideally be inside an OS X 10.11 VM (so it is compatible with all working Homebrew macOS versions). 24 | 25 | ### Upload 26 | 27 | Copy the bottle `bottle*.tar.gz` and `bottle*.json` files into a directory on your local machine. 28 | 29 | Upload these files to GitHub Packages with: 30 | 31 | ```sh 32 | brew pr-upload --upload-only --root-url=https://ghcr.io/v2/homebrew/portable-ruby 33 | ``` 34 | 35 | And to GitHub releases: 36 | 37 | ```sh 38 | brew pr-upload --upload-only --root-url=https://github.com/Homebrew/homebrew-portable-ruby/releases/download/$VERSION 39 | ``` 40 | 41 | where `$VERSION` is the new package version. 42 |
43 | 44 | ## Current Status 45 | 46 | Used in production for Homebrew/brew. 47 | 48 | ## License 49 | 50 | Code is under the [BSD 2-Clause "Simplified" License](https://github.com/Homebrew/homebrew-portable-ruby/blob/master/LICENSE.txt). 51 | -------------------------------------------------------------------------------- /cmd/portable-package.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | require "abstract_command" 5 | require "development_tools" 6 | require "dependency" 7 | 8 | module Homebrew 9 | module Cmd 10 | class PortablePackageCmd < AbstractCommand 11 | cmd_args do 12 | usage_banner <<~EOS 13 | `portable-package` 14 | 15 | Build and package portable formulae. 16 | EOS 17 | switch "--no-uninstall-deps", 18 | description: "Don't uninstall all dependencies of portable formulae before testing." 19 | switch "-v", "--verbose", 20 | description: "Pass `--verbose` to `brew` commands." 21 | named_args :formula, min: 1 22 | end 23 | 24 | sig { override.void } 25 | def run 26 | ENV["HOMEBREW_DEVELOPER"] = "1" 27 | 28 | verbose = [] 29 | verbose << "--verbose" if args.verbose? 30 | verbose << "--debug" if args.debug? 31 | 32 | # If test-bot cleanup is performed and auto-updates are disabled, this might not already be installed. 33 | unless DevelopmentTools.ca_file_handles_most_https_certificates? 34 | safe_system HOMEBREW_BREW_FILE, "install", "ca-certificates" 35 | end 36 | 37 | args.named.each do |name| 38 | name = "portable-#{name}" unless name.start_with? "portable-" 39 | begin 40 | # On Linux, install glibc and linux-headers from bottles and don't install their build dependencies. 41 | bottled_dep_allowlist = /\A(?:glibc|linux-headers)@/ 42 | deps = Dependency.expand(Formula[name], cache_key: "portable-package-#{name}") do |_dependent, dep| 43 | Dependency.prune if dep.test? || dep.optional? 44 | 45 | next unless bottled_dep_allowlist.match?(dep.name) 46 | 47 | Dependency.keep_but_prune_recursive_deps 48 | end.map(&:name) 49 | 50 | bottled_deps, deps = deps.partition { |dep| bottled_dep_allowlist.match?(dep) } 51 | 52 | safe_system HOMEBREW_BREW_FILE, "install", *verbose, *bottled_deps if bottled_deps.present? 53 | 54 | # Build bottles for all other dependencies. 55 | safe_system HOMEBREW_BREW_FILE, "install", "--build-bottle", *verbose, *deps 56 | 57 | safe_system HOMEBREW_BREW_FILE, "install", "--build-bottle", *verbose, name 58 | unless args.no_uninstall_deps? 59 | safe_system HOMEBREW_BREW_FILE, "uninstall", "--force", "--ignore-dependencies", *verbose, *deps 60 | end 61 | safe_system HOMEBREW_BREW_FILE, "test", *verbose, name 62 | puts "Linkage information:" 63 | safe_system HOMEBREW_BREW_FILE, "linkage", *verbose, name 64 | bottle_args = %w[ 65 | --skip-relocation 66 | --root-url=https://ghcr.io/v2/homebrew/portable-ruby 67 | --json 68 | --no-rebuild 69 | ] 70 | safe_system HOMEBREW_BREW_FILE, "bottle", *verbose, *bottle_args, name 71 | rescue => e 72 | ofail e 73 | end 74 | end 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /cmd/portable-package.rbi: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | class Homebrew::Cmd::PortablePackageCmd 4 | sig { returns(Homebrew::Cmd::PortablePackageCmd::Args) } 5 | def args; end 6 | end 7 | 8 | class Homebrew::Cmd::PortablePackageCmd::Args < Homebrew::CLI::Args 9 | sig { returns(T::Boolean) } 10 | def no_uninstall_deps?; end 11 | end 12 | --------------------------------------------------------------------------------