|
| 1 | +import execa from 'execa'; |
| 2 | +import chalk from 'chalk'; |
| 3 | + |
| 4 | +import {logger, findProjectRoot} from '@react-native-community/cli-tools'; |
| 5 | + |
1 | 6 | import versionRanges from '../versionRanges';
|
2 | 7 | import {doesSoftwareNeedToBeFixed} from '../checkInstallation';
|
3 | 8 | import {HealthCheckInterface} from '../../types';
|
| 9 | +import {inline} from './common'; |
| 10 | + |
| 11 | +// Exposed for testing only |
| 12 | +export const output = { |
| 13 | + OK: 'Ok', |
| 14 | + NO_GEMFILE: 'No Gemfile', |
| 15 | + NO_RUBY: 'No Ruby', |
| 16 | + BUNDLE_INVALID_RUBY: 'Bundle invalid Ruby', |
| 17 | + UNKNOWN: 'Unknown', |
| 18 | +} as const; |
| 19 | + |
| 20 | +// The Change: |
| 21 | +// ----------- |
| 22 | +// |
| 23 | +// React Native 0.72 primarily defines the compatible version of Ruby in the |
| 24 | +// project's Gemfile [1]. It does this because it allows for ranges instead of |
| 25 | +// pinning to a version of Ruby. |
| 26 | +// |
| 27 | +// In previous versions the .ruby-version file defined the compatible version, |
| 28 | +// and it was derived in the Gemfile [2]: |
| 29 | +// |
| 30 | +// > ruby File.read(File.join(__dir__, '.ruby-version')).strip |
| 31 | +// |
| 32 | +// Why all of the changes with Ruby? |
| 33 | +// --------------------------------- |
| 34 | +// |
| 35 | +// React Native has had to weigh up a couple of concerns: |
| 36 | +// |
| 37 | +// - Cocoapods: we don't control the minimum supported version, although that |
| 38 | +// was defined almost a decade ago [3]. Practically system Ruby on macOS works |
| 39 | +// for our users. |
| 40 | +// |
| 41 | +// - Apple may drop support for scripting language runtimes in future version of |
| 42 | +// macOS [4]. Ruby 2.7 is effectively EOL, which means many supporting tools and |
| 43 | +// developer environments _may_ not support it going forward, and 3.0 is becoming |
| 44 | +// the default in, for example, places like our CI. Some users may be unable to |
| 45 | +// install Ruby 2.7 on their devices as a matter of policy. |
| 46 | +// |
| 47 | +// - Our Codegen is extensively built in Ruby 2.7. |
| 48 | +// |
| 49 | +// - A common pain-point for users (old and new) setting up their environment is |
| 50 | +// configuring a Ruby version manager or managing multiple Ruby versions on their |
| 51 | +// device. This occurs so frequently that we've removed the step from our docs [6] |
| 52 | +// |
| 53 | +// After users suggested bumping Ruby to 3.1.3 [5], a discussion concluded that |
| 54 | +// allowing a range of version of Ruby (>= 2.6.10) was the best way forward. This |
| 55 | +// balanced the need to make the platform easier to start with, but unblocked more |
| 56 | +// sophisticated users. |
| 57 | +// |
| 58 | +// [1] https://github.com/facebook/react-native/pull/36281 |
| 59 | +// [2] https://github.com/facebook/react-native/blob/v0.71.3/Gemfile#L4 |
| 60 | +// [3] https://github.com/CocoaPods/guides.cocoapods.org/commit/30881800ac2bd431d9c5d7ee74404b13e7f43888 |
| 61 | +// [4] https://developer.apple.com/documentation/macos-release-notes/macos-catalina-10_15-release-notes#Scripting-Language-Runtimes |
| 62 | +// [5] https://github.com/facebook/react-native/pull/36074 |
| 63 | +// [6] https://github.com/facebook/react-native-website/commit/8db97602347a8623f21e3e516245d04bdf6f1a29 |
| 64 | + |
| 65 | +async function checkRubyGemfileRequirement( |
| 66 | + projectRoot: string, |
| 67 | +): Promise<[string, string?]> { |
| 68 | + const evaluateGemfile = inline` |
| 69 | + require "Bundler" |
| 70 | + gemfile = Bundler::Definition.build("Gemfile", nil, {}) |
| 71 | + version = gemfile.ruby_version.engine_versions.join(", ") |
| 72 | + begin |
| 73 | + gemfile.validate_runtime! |
| 74 | + rescue Bundler::GemfileNotFound |
| 75 | + puts "${output.NO_GEMFILE}" |
| 76 | + exit 1 |
| 77 | + rescue Bundler::RubyVersionMismatch |
| 78 | + puts "${output.BUNDLE_INVALID_RUBY}" |
| 79 | + STDERR.puts version |
| 80 | + exit 2 |
| 81 | + rescue => e |
| 82 | + STDERR e.message |
| 83 | + exit 3 |
| 84 | + else |
| 85 | + puts "${output.OK}" |
| 86 | + STDERR.puts version |
| 87 | + end`; |
| 88 | + |
| 89 | + try { |
| 90 | + await execa('ruby', ['-e', evaluateGemfile], { |
| 91 | + cwd: projectRoot, |
| 92 | + }); |
| 93 | + return [output.OK]; |
| 94 | + } catch (e) { |
| 95 | + switch (e.code) { |
| 96 | + case 'ENOENT': |
| 97 | + return [output.NO_RUBY]; |
| 98 | + case 1: |
| 99 | + return [output.NO_GEMFILE]; |
| 100 | + case 2: |
| 101 | + return [output.BUNDLE_INVALID_RUBY, e.stderr]; |
| 102 | + default: |
| 103 | + return [output.UNKNOWN, e.message]; |
| 104 | + } |
| 105 | + } |
| 106 | +} |
4 | 107 |
|
5 | 108 | export default {
|
6 | 109 | label: 'Ruby',
|
7 | 110 | isRequired: false,
|
8 | 111 | description: 'Required for installing iOS dependencies',
|
9 |
| - getDiagnostics: async ({Managers}) => ({ |
10 |
| - needsToBeFixed: doesSoftwareNeedToBeFixed({ |
11 |
| - version: Managers.RubyGems.version, |
| 112 | + getDiagnostics: async ({Languages}) => { |
| 113 | + let projectRoot; |
| 114 | + try { |
| 115 | + projectRoot = findProjectRoot(); |
| 116 | + } catch (e) { |
| 117 | + logger.debug(e.message); |
| 118 | + } |
| 119 | + |
| 120 | + const fallbackResult = { |
| 121 | + needsToBeFixed: doesSoftwareNeedToBeFixed({ |
| 122 | + version: Languages.Ruby.version, |
| 123 | + versionRange: versionRanges.RUBY, |
| 124 | + }), |
| 125 | + version: Languages.Ruby.version, |
12 | 126 | versionRange: versionRanges.RUBY,
|
13 |
| - }), |
14 |
| - version: Managers.RubyGems.version, |
15 |
| - versionRange: versionRanges.RUBY, |
16 |
| - }), |
| 127 | + description: '', |
| 128 | + }; |
| 129 | + |
| 130 | + // No guidance from the project, so we make the best guess |
| 131 | + if (!projectRoot) { |
| 132 | + return fallbackResult; |
| 133 | + } |
| 134 | + |
| 135 | + // Gemfile |
| 136 | + let [code, versionOrError] = await checkRubyGemfileRequirement(projectRoot); |
| 137 | + switch (code) { |
| 138 | + case output.OK: { |
| 139 | + return { |
| 140 | + needsToBeFixed: false, |
| 141 | + version: Languages.Ruby.version, |
| 142 | + versionRange: versionOrError, |
| 143 | + }; |
| 144 | + } |
| 145 | + case output.BUNDLE_INVALID_RUBY: |
| 146 | + return { |
| 147 | + needsToBeFixed: true, |
| 148 | + version: Languages.Ruby.version, |
| 149 | + versionRange: versionOrError, |
| 150 | + }; |
| 151 | + case output.NO_RUBY: |
| 152 | + return { |
| 153 | + needsToBeFixed: true, |
| 154 | + description: 'Cannot find a working copy of Ruby.', |
| 155 | + }; |
| 156 | + case output.NO_GEMFILE: |
| 157 | + fallbackResult.description = `Could not find the project ${chalk.bold( |
| 158 | + 'Gemfile', |
| 159 | + )} in your project folder (${chalk.dim( |
| 160 | + projectRoot, |
| 161 | + )}), guessed using my built-in version.`; |
| 162 | + break; |
| 163 | + default: |
| 164 | + if (versionOrError) { |
| 165 | + logger.warn(versionOrError); |
| 166 | + } |
| 167 | + break; |
| 168 | + } |
| 169 | + |
| 170 | + return fallbackResult; |
| 171 | + }, |
17 | 172 | runAutomaticFix: async ({loader, logManualInstallation}) => {
|
18 | 173 | loader.fail();
|
19 | 174 |
|
|
0 commit comments