ruflet 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -40
- data/lib/ruflet/cli/build_command.rb +124 -697
- data/lib/ruflet/cli/extra_command.rb +3 -15
- data/lib/ruflet/cli/flutter_sdk.rb +9 -91
- data/lib/ruflet/cli/new_command.rb +7 -36
- data/lib/ruflet/cli/run_command.rb +97 -186
- data/lib/ruflet/cli/templates.rb +8 -9
- data/lib/ruflet/cli.rb +0 -13
- data/lib/ruflet/version.rb +1 -1
- metadata +1 -3
- data/lib/ruflet/cli/android_sdk.rb +0 -306
- data/lib/ruflet/cli/environment_setup.rb +0 -241
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "open3"
|
|
5
|
-
require "rbconfig"
|
|
6
|
-
require "tmpdir"
|
|
7
|
-
|
|
8
|
-
module Ruflet
|
|
9
|
-
module CLI
|
|
10
|
-
# Provisions the Android toolchain so `ruflet build apk|aab|android`
|
|
11
|
-
# works on a fresh machine:
|
|
12
|
-
#
|
|
13
|
-
# * a JDK (Gradle itself is bootstrapped by the project's Gradle
|
|
14
|
-
# wrapper, but the wrapper needs Java)
|
|
15
|
-
# * the Android SDK command-line tools
|
|
16
|
-
# * platform-tools / a platform / build-tools via sdkmanager
|
|
17
|
-
# * accepted SDK licenses
|
|
18
|
-
#
|
|
19
|
-
# An existing SDK (ANDROID_HOME, ANDROID_SDK_ROOT, or the standard
|
|
20
|
-
# per-OS install location) is always preferred; otherwise a Ruflet-managed
|
|
21
|
-
# SDK is created under ~/.ruflet/android-sdk.
|
|
22
|
-
module AndroidSdk
|
|
23
|
-
CMDLINE_TOOLS_VERSION = "11076708"
|
|
24
|
-
CMDLINE_TOOLS_BASE = "https://dl.google.com/android/repository".freeze
|
|
25
|
-
ANDROID_PLATFORM = "android-35"
|
|
26
|
-
ANDROID_BUILD_TOOLS = "35.0.0"
|
|
27
|
-
MINIMUM_JAVA_MAJOR = 17
|
|
28
|
-
|
|
29
|
-
JDK_PACKAGES = {
|
|
30
|
-
apt: "openjdk-17-jdk",
|
|
31
|
-
dnf: "java-17-openjdk-devel",
|
|
32
|
-
pacman: "jdk17-openjdk",
|
|
33
|
-
zypper: "java-17-openjdk-devel",
|
|
34
|
-
apk: "openjdk17",
|
|
35
|
-
brew: "openjdk@17",
|
|
36
|
-
winget: "EclipseAdoptium.Temurin.17.JDK",
|
|
37
|
-
choco: "temurin17"
|
|
38
|
-
}.freeze
|
|
39
|
-
|
|
40
|
-
def android_environment_setup!(fix: false, verbose: false)
|
|
41
|
-
issues = []
|
|
42
|
-
|
|
43
|
-
java = ensure_java!(fix: fix, verbose: verbose)
|
|
44
|
-
if java
|
|
45
|
-
puts " Java: #{java[:version]} (#{java[:path]})"
|
|
46
|
-
else
|
|
47
|
-
issues << "missing JDK #{MINIMUM_JAVA_MAJOR}+"
|
|
48
|
-
warn " Java: missing — Android builds need a JDK (Gradle wrapper requirement)."
|
|
49
|
-
warn " #{jdk_install_hint}"
|
|
50
|
-
return issues unless fix
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
sdk_root = detect_android_sdk_root
|
|
54
|
-
if sdk_root
|
|
55
|
-
puts " Android SDK: #{sdk_root}"
|
|
56
|
-
elsif fix
|
|
57
|
-
unless java
|
|
58
|
-
warn " Android SDK: skipped (sdkmanager needs Java)"
|
|
59
|
-
return issues
|
|
60
|
-
end
|
|
61
|
-
sdk_root = install_managed_android_sdk(java, verbose: verbose)
|
|
62
|
-
unless sdk_root
|
|
63
|
-
issues << "Android SDK install failed"
|
|
64
|
-
warn " Android SDK: install failed"
|
|
65
|
-
return issues
|
|
66
|
-
end
|
|
67
|
-
puts " Android SDK: #{sdk_root} (installed)"
|
|
68
|
-
else
|
|
69
|
-
issues << "missing Android SDK"
|
|
70
|
-
warn " Android SDK: missing — run `ruflet doctor --fix` to install it under #{managed_android_sdk_root}."
|
|
71
|
-
return issues
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
if fix && java
|
|
75
|
-
ensure_android_packages(sdk_root, java, verbose: verbose)
|
|
76
|
-
accept_android_licenses(sdk_root, java, verbose: verbose)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
issues
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Environment for invoking Flutter/Gradle Android builds.
|
|
83
|
-
def android_build_env(env)
|
|
84
|
-
merged = env.dup
|
|
85
|
-
sdk_root = detect_android_sdk_root
|
|
86
|
-
if sdk_root
|
|
87
|
-
merged["ANDROID_HOME"] ||= sdk_root
|
|
88
|
-
merged["ANDROID_SDK_ROOT"] ||= sdk_root
|
|
89
|
-
platform_tools = File.join(sdk_root, "platform-tools")
|
|
90
|
-
if Dir.exist?(platform_tools)
|
|
91
|
-
merged["PATH"] = "#{platform_tools}#{File::PATH_SEPARATOR}#{merged.fetch('PATH', ENV.fetch('PATH', ''))}"
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
java_home = detect_java_home
|
|
95
|
-
merged["JAVA_HOME"] ||= java_home if java_home
|
|
96
|
-
merged
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def detect_android_sdk_root
|
|
100
|
-
candidates = [
|
|
101
|
-
ENV["ANDROID_HOME"],
|
|
102
|
-
ENV["ANDROID_SDK_ROOT"],
|
|
103
|
-
default_android_sdk_location,
|
|
104
|
-
managed_android_sdk_root
|
|
105
|
-
]
|
|
106
|
-
candidates.compact.find { |root| android_sdk_present?(root) }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def managed_android_sdk_root
|
|
110
|
-
File.join(Dir.home, ".ruflet", "android-sdk")
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
private
|
|
114
|
-
|
|
115
|
-
def android_sdk_present?(root)
|
|
116
|
-
return false if root.to_s.strip.empty?
|
|
117
|
-
|
|
118
|
-
Dir.exist?(File.join(root, "platform-tools")) ||
|
|
119
|
-
Dir.exist?(File.join(root, "cmdline-tools")) ||
|
|
120
|
-
Dir.exist?(File.join(root, "platforms"))
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def default_android_sdk_location
|
|
124
|
-
if windows_host?
|
|
125
|
-
local = ENV["LOCALAPPDATA"].to_s
|
|
126
|
-
return File.join(local, "Android", "Sdk") unless local.empty?
|
|
127
|
-
|
|
128
|
-
nil
|
|
129
|
-
elsif macos_host?
|
|
130
|
-
File.join(Dir.home, "Library", "Android", "sdk")
|
|
131
|
-
else
|
|
132
|
-
File.join(Dir.home, "Android", "Sdk")
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def ensure_java!(fix:, verbose: false)
|
|
137
|
-
java = current_java_info
|
|
138
|
-
return java if java && java[:major] >= MINIMUM_JAVA_MAJOR
|
|
139
|
-
|
|
140
|
-
return nil unless fix
|
|
141
|
-
|
|
142
|
-
manager = system_package_manager
|
|
143
|
-
package = manager && JDK_PACKAGES[manager[:id]]
|
|
144
|
-
return nil unless package
|
|
145
|
-
|
|
146
|
-
if manager[:id] == :winget
|
|
147
|
-
run_privileged_command(*manager[:install], package, verbose: verbose)
|
|
148
|
-
else
|
|
149
|
-
run_privileged_command(*manager[:install], package, verbose: verbose)
|
|
150
|
-
end
|
|
151
|
-
current_java_info
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def current_java_info
|
|
155
|
-
java = which_command("java") || bundled_java_candidate
|
|
156
|
-
return nil unless java
|
|
157
|
-
|
|
158
|
-
output, status = Open3.capture2e(java, "-version")
|
|
159
|
-
return nil unless status.success?
|
|
160
|
-
|
|
161
|
-
major = parse_java_major(output)
|
|
162
|
-
return nil unless major
|
|
163
|
-
|
|
164
|
-
{ path: java, major: major, version: output.lines.first.to_s.strip }
|
|
165
|
-
rescue StandardError
|
|
166
|
-
nil
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def bundled_java_candidate
|
|
170
|
-
if macos_host?
|
|
171
|
-
brew_java = "/opt/homebrew/opt/openjdk@17/bin/java"
|
|
172
|
-
return brew_java if File.executable?(brew_java)
|
|
173
|
-
end
|
|
174
|
-
nil
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def parse_java_major(output)
|
|
178
|
-
match = output[/version "(\d+)(?:\.(\d+))?/, 1]
|
|
179
|
-
return nil unless match
|
|
180
|
-
|
|
181
|
-
major = match.to_i
|
|
182
|
-
# "1.8" style versions report the major in the second group.
|
|
183
|
-
major = output[/version "1\.(\d+)/, 1].to_i if major == 1
|
|
184
|
-
major
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def detect_java_home
|
|
188
|
-
if macos_host?
|
|
189
|
-
output, status = Open3.capture2e("/usr/libexec/java_home", "-v", MINIMUM_JAVA_MAJOR.to_s)
|
|
190
|
-
return output.strip if status.success? && !output.strip.empty?
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
java = which_command("java")
|
|
194
|
-
return nil unless java
|
|
195
|
-
|
|
196
|
-
resolved = File.realpath(java) rescue java
|
|
197
|
-
home = File.expand_path("../..", resolved)
|
|
198
|
-
File.directory?(File.join(home, "bin")) ? home : nil
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def install_managed_android_sdk(java, verbose: false)
|
|
202
|
-
sdk_root = managed_android_sdk_root
|
|
203
|
-
tools_dir = File.join(sdk_root, "cmdline-tools", "latest")
|
|
204
|
-
return sdk_root if File.executable?(sdkmanager_bin(sdk_root))
|
|
205
|
-
|
|
206
|
-
archive_name = "commandlinetools-#{cmdline_tools_os}-#{CMDLINE_TOOLS_VERSION}_latest.zip"
|
|
207
|
-
FileUtils.mkdir_p(sdk_root)
|
|
208
|
-
Dir.mktmpdir("ruflet-android-") do |tmp|
|
|
209
|
-
archive = File.join(tmp, archive_name)
|
|
210
|
-
puts " Downloading Android command-line tools (#{archive_name})"
|
|
211
|
-
download_file("#{CMDLINE_TOOLS_BASE}/#{archive_name}", archive)
|
|
212
|
-
extract_archive(archive, tmp)
|
|
213
|
-
extracted = File.join(tmp, "cmdline-tools")
|
|
214
|
-
raise "cmdline-tools missing from archive" unless Dir.exist?(extracted)
|
|
215
|
-
|
|
216
|
-
FileUtils.rm_rf(tools_dir)
|
|
217
|
-
FileUtils.mkdir_p(File.dirname(tools_dir))
|
|
218
|
-
FileUtils.mv(extracted, tools_dir)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
File.executable?(sdkmanager_bin(sdk_root)) ? sdk_root : nil
|
|
222
|
-
rescue StandardError => e
|
|
223
|
-
warn " Android SDK download failed: #{e.class}: #{e.message}"
|
|
224
|
-
nil
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def cmdline_tools_os
|
|
228
|
-
return "win" if windows_host?
|
|
229
|
-
return "mac" if macos_host?
|
|
230
|
-
|
|
231
|
-
"linux"
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def sdkmanager_bin(sdk_root)
|
|
235
|
-
name = windows_host? ? "sdkmanager.bat" : "sdkmanager"
|
|
236
|
-
File.join(sdk_root, "cmdline-tools", "latest", "bin", name)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def ensure_android_packages(sdk_root, java, verbose: false)
|
|
240
|
-
sdkmanager = sdkmanager_bin(sdk_root)
|
|
241
|
-
return unless File.executable?(sdkmanager)
|
|
242
|
-
|
|
243
|
-
packages = ["platform-tools", "platforms;#{ANDROID_PLATFORM}", "build-tools;#{ANDROID_BUILD_TOOLS}"]
|
|
244
|
-
missing = packages.reject { |package| android_package_installed?(sdk_root, package) }
|
|
245
|
-
return if missing.empty?
|
|
246
|
-
|
|
247
|
-
puts " Installing Android packages: #{missing.join(', ')}"
|
|
248
|
-
env = sdkmanager_env(java)
|
|
249
|
-
# sdkmanager downloads sizeable platform/build-tool archives; stream so
|
|
250
|
-
# the install reports progress instead of appearing to hang.
|
|
251
|
-
system(env, sdkmanager, "--sdk_root=#{sdk_root}", *missing, out: $stdout, err: $stderr)
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def android_package_installed?(sdk_root, package)
|
|
255
|
-
case package
|
|
256
|
-
when "platform-tools"
|
|
257
|
-
Dir.exist?(File.join(sdk_root, "platform-tools"))
|
|
258
|
-
when /\Aplatforms;(.+)\z/
|
|
259
|
-
Dir.exist?(File.join(sdk_root, "platforms", Regexp.last_match(1)))
|
|
260
|
-
when /\Abuild-tools;(.+)\z/
|
|
261
|
-
Dir.exist?(File.join(sdk_root, "build-tools", Regexp.last_match(1)))
|
|
262
|
-
else
|
|
263
|
-
false
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def accept_android_licenses(sdk_root, java, verbose: false)
|
|
268
|
-
sdkmanager = sdkmanager_bin(sdk_root)
|
|
269
|
-
return unless File.executable?(sdkmanager)
|
|
270
|
-
|
|
271
|
-
puts " Accepting Android SDK licenses"
|
|
272
|
-
env = sdkmanager_env(java)
|
|
273
|
-
Open3.popen2e(env, sdkmanager, "--sdk_root=#{sdk_root}", "--licenses") do |stdin, stdout, wait|
|
|
274
|
-
begin
|
|
275
|
-
stdin.write("y\n" * 64)
|
|
276
|
-
stdin.close
|
|
277
|
-
rescue Errno::EPIPE
|
|
278
|
-
nil
|
|
279
|
-
end
|
|
280
|
-
stdout.each_line { |line| puts line if verbose }
|
|
281
|
-
wait.value
|
|
282
|
-
end
|
|
283
|
-
rescue StandardError => e
|
|
284
|
-
warn " License acceptance failed: #{e.class}: #{e.message}"
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
def sdkmanager_env(java)
|
|
288
|
-
env = {}
|
|
289
|
-
java_home = detect_java_home
|
|
290
|
-
env["JAVA_HOME"] = java_home if java_home
|
|
291
|
-
if java && java[:path]
|
|
292
|
-
env["PATH"] = "#{File.dirname(java[:path])}#{File::PATH_SEPARATOR}#{ENV.fetch('PATH', '')}"
|
|
293
|
-
end
|
|
294
|
-
env
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
def jdk_install_hint
|
|
298
|
-
manager = system_package_manager
|
|
299
|
-
package = manager && JDK_PACKAGES[manager[:id]]
|
|
300
|
-
return "#{(sudo_prefix + manager[:install]).join(' ')} #{package}" if package
|
|
301
|
-
|
|
302
|
-
"install a JDK #{MINIMUM_JAVA_MAJOR}+ (e.g. Temurin: https://adoptium.net)"
|
|
303
|
-
end
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
end
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rbconfig"
|
|
4
|
-
|
|
5
|
-
module Ruflet
|
|
6
|
-
module CLI
|
|
7
|
-
# Provisions the host so `ruflet build` can run: checks (and with
|
|
8
|
-
# `ruflet doctor --fix`, installs) the system tools Flutter requires.
|
|
9
|
-
#
|
|
10
|
-
# Coverage:
|
|
11
|
-
# Linux apt (Ubuntu, Debian, Kali, Mint), dnf (Fedora, RHEL),
|
|
12
|
-
# pacman (Arch, Omarchy, Manjaro), zypper (openSUSE)
|
|
13
|
-
# macOS Homebrew where available, plus Xcode Command Line Tools and
|
|
14
|
-
# CocoaPods guidance
|
|
15
|
-
# Windows winget or Chocolatey
|
|
16
|
-
#
|
|
17
|
-
# Anything that cannot be installed unattended (Xcode CLT, Visual Studio
|
|
18
|
-
# Build Tools) is reported with the exact command the developer must run.
|
|
19
|
-
module EnvironmentSetup
|
|
20
|
-
Tool = Struct.new(:name, :probe, :purpose, :packages, :manual_hint, keyword_init: true)
|
|
21
|
-
|
|
22
|
-
LINUX_PACKAGE_MANAGERS = [
|
|
23
|
-
{ id: :apt, probe: "apt-get", install: %w[apt-get install -y], update: %w[apt-get update] },
|
|
24
|
-
{ id: :dnf, probe: "dnf", install: %w[dnf install -y], update: nil },
|
|
25
|
-
{ id: :pacman, probe: "pacman", install: %w[pacman -S --noconfirm --needed], update: nil },
|
|
26
|
-
{ id: :zypper, probe: "zypper", install: %w[zypper --non-interactive install], update: nil },
|
|
27
|
-
{ id: :apk, probe: "apk", install: %w[apk add], update: nil }
|
|
28
|
-
].freeze
|
|
29
|
-
|
|
30
|
-
def environment_setup!(fix: false, verbose: false)
|
|
31
|
-
issues = []
|
|
32
|
-
tools = required_system_tools
|
|
33
|
-
|
|
34
|
-
unless flutter_host
|
|
35
|
-
issues << unsupported_host_message
|
|
36
|
-
warn " System: #{unsupported_host_message}"
|
|
37
|
-
return issues
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
manager = system_package_manager
|
|
41
|
-
missing = tools.reject { |tool| tool_present?(tool) }
|
|
42
|
-
|
|
43
|
-
if missing.empty?
|
|
44
|
-
puts " System tools: ok (#{tools.map(&:name).join(', ')})"
|
|
45
|
-
return issues
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
puts " System tools missing: #{missing.map(&:name).join(', ')}"
|
|
49
|
-
|
|
50
|
-
unless fix
|
|
51
|
-
missing.each { |tool| warn " #{tool.name}: #{manual_hint_for(tool, manager)}" }
|
|
52
|
-
warn " Run `ruflet doctor --fix` to install them."
|
|
53
|
-
return missing.map { |tool| "missing #{tool.name}" }
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
auto, manual = missing.partition { |tool| installable?(tool, manager) }
|
|
57
|
-
|
|
58
|
-
if auto.any?
|
|
59
|
-
if manager
|
|
60
|
-
install_system_packages(manager, auto, verbose: verbose)
|
|
61
|
-
end
|
|
62
|
-
still_missing = auto.reject { |tool| tool_present?(tool) }
|
|
63
|
-
still_missing.each do |tool|
|
|
64
|
-
issues << "missing #{tool.name}"
|
|
65
|
-
warn " #{tool.name}: install failed — #{manual_hint_for(tool, manager)}"
|
|
66
|
-
end
|
|
67
|
-
installed = auto - still_missing
|
|
68
|
-
puts " Installed: #{installed.map(&:name).join(', ')}" if installed.any?
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
manual.each do |tool|
|
|
72
|
-
issues << "missing #{tool.name}"
|
|
73
|
-
warn " #{tool.name}: requires a manual step — #{manual_hint_for(tool, manager)}"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
issues
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def required_system_tools
|
|
80
|
-
case flutter_host
|
|
81
|
-
when "linux" then linux_required_tools
|
|
82
|
-
when "macos", "macos_arm64" then macos_required_tools
|
|
83
|
-
when "windows" then windows_required_tools
|
|
84
|
-
else []
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def system_package_manager
|
|
89
|
-
if windows_host?
|
|
90
|
-
return { id: :winget, install: %w[winget install --accept-source-agreements --accept-package-agreements -e --id] } if which_command("winget")
|
|
91
|
-
return { id: :choco, install: %w[choco install -y] } if which_command("choco")
|
|
92
|
-
return nil
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
if macos_host?
|
|
96
|
-
return { id: :brew, install: %w[brew install] } if which_command("brew")
|
|
97
|
-
return nil
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
LINUX_PACKAGE_MANAGERS.each do |manager|
|
|
101
|
-
next unless which_command(manager[:probe])
|
|
102
|
-
|
|
103
|
-
return manager
|
|
104
|
-
end
|
|
105
|
-
nil
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
private
|
|
109
|
-
|
|
110
|
-
def macos_host?
|
|
111
|
-
RbConfig::CONFIG["host_os"].match?(/darwin/i)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def linux_required_tools
|
|
115
|
-
[
|
|
116
|
-
Tool.new(name: "git", probe: "git", purpose: "required by the Flutter tool itself",
|
|
117
|
-
packages: { apt: "git", dnf: "git", pacman: "git", zypper: "git", apk: "git" }),
|
|
118
|
-
Tool.new(name: "curl", probe: "curl", purpose: "SDK downloads",
|
|
119
|
-
packages: { apt: "curl", dnf: "curl", pacman: "curl", zypper: "curl", apk: "curl" }),
|
|
120
|
-
Tool.new(name: "unzip", probe: "unzip", purpose: "extracting Flutter artifacts",
|
|
121
|
-
packages: { apt: "unzip", dnf: "unzip", pacman: "unzip", zypper: "unzip", apk: "unzip" }),
|
|
122
|
-
Tool.new(name: "xz", probe: "xz", purpose: "extracting the Flutter SDK (.tar.xz)",
|
|
123
|
-
packages: { apt: "xz-utils", dnf: "xz", pacman: "xz", zypper: "xz", apk: "xz" }),
|
|
124
|
-
Tool.new(name: "zip", probe: "zip", purpose: "packaging app bundles",
|
|
125
|
-
packages: { apt: "zip", dnf: "zip", pacman: "zip", zypper: "zip", apk: "zip" }),
|
|
126
|
-
Tool.new(name: "clang", probe: "clang", purpose: "Linux desktop builds",
|
|
127
|
-
packages: { apt: "clang", dnf: "clang", pacman: "clang", zypper: "clang", apk: "clang" }),
|
|
128
|
-
Tool.new(name: "cmake", probe: "cmake", purpose: "Linux desktop builds",
|
|
129
|
-
packages: { apt: "cmake", dnf: "cmake", pacman: "cmake", zypper: "cmake", apk: "cmake" }),
|
|
130
|
-
Tool.new(name: "ninja", probe: "ninja", purpose: "Linux desktop builds",
|
|
131
|
-
packages: { apt: "ninja-build", dnf: "ninja-build", pacman: "ninja", zypper: "ninja", apk: "ninja" }),
|
|
132
|
-
Tool.new(name: "pkg-config", probe: "pkg-config", purpose: "Linux desktop builds",
|
|
133
|
-
packages: { apt: "pkg-config", dnf: "pkgconf-pkg-config", pacman: "pkgconf", zypper: "pkg-config", apk: "pkgconf" }),
|
|
134
|
-
Tool.new(name: "GTK 3 headers", probe: nil, purpose: "Linux desktop builds",
|
|
135
|
-
packages: { apt: "libgtk-3-dev", dnf: "gtk3-devel", pacman: "gtk3", zypper: "gtk3-devel", apk: "gtk+3.0-dev" })
|
|
136
|
-
]
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def macos_required_tools
|
|
140
|
-
[
|
|
141
|
-
Tool.new(name: "git", probe: "git", purpose: "required by the Flutter tool itself",
|
|
142
|
-
packages: { brew: "git" },
|
|
143
|
-
manual_hint: "install Xcode Command Line Tools: xcode-select --install"),
|
|
144
|
-
Tool.new(name: "curl", probe: "curl", purpose: "SDK downloads", packages: { brew: "curl" }),
|
|
145
|
-
Tool.new(name: "unzip", probe: "unzip", purpose: "extracting Flutter artifacts", packages: { brew: "unzip" }),
|
|
146
|
-
Tool.new(name: "Xcode Command Line Tools", probe: nil, purpose: "macOS/iOS builds",
|
|
147
|
-
packages: {},
|
|
148
|
-
manual_hint: "run `xcode-select --install` (or install Xcode from the App Store)"),
|
|
149
|
-
Tool.new(name: "CocoaPods", probe: "pod", purpose: "macOS/iOS plugin dependencies",
|
|
150
|
-
packages: { brew: "cocoapods" },
|
|
151
|
-
manual_hint: "run `brew install cocoapods` or `sudo gem install cocoapods`")
|
|
152
|
-
]
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def windows_required_tools
|
|
156
|
-
[
|
|
157
|
-
Tool.new(name: "git", probe: "git", purpose: "required by the Flutter tool itself",
|
|
158
|
-
packages: { winget: "Git.Git", choco: "git" }),
|
|
159
|
-
Tool.new(name: "Visual Studio Build Tools", probe: nil, purpose: "Windows desktop builds",
|
|
160
|
-
packages: {},
|
|
161
|
-
manual_hint: "winget install --id Microsoft.VisualStudio.2022.BuildTools (select the " \
|
|
162
|
-
"\"Desktop development with C++\" workload)")
|
|
163
|
-
]
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def tool_present?(tool)
|
|
167
|
-
case tool.name
|
|
168
|
-
when "GTK 3 headers"
|
|
169
|
-
return true if which_command("pkg-config") &&
|
|
170
|
-
system("pkg-config", "--exists", "gtk+-3.0", out: File::NULL, err: File::NULL)
|
|
171
|
-
|
|
172
|
-
false
|
|
173
|
-
when "Xcode Command Line Tools"
|
|
174
|
-
system("xcode-select", "-p", out: File::NULL, err: File::NULL)
|
|
175
|
-
else
|
|
176
|
-
tool.probe ? !which_command(tool.probe).nil? : false
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def installable?(tool, manager)
|
|
181
|
-
return false unless manager
|
|
182
|
-
|
|
183
|
-
tool.packages.key?(manager[:id])
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def manual_hint_for(tool, manager)
|
|
187
|
-
return tool.manual_hint if tool.manual_hint && !installable?(tool, manager)
|
|
188
|
-
return "no supported package manager found; install #{tool.name} manually" unless manager
|
|
189
|
-
|
|
190
|
-
package = tool.packages[manager[:id]]
|
|
191
|
-
return tool.manual_hint || "install #{tool.name} manually" unless package
|
|
192
|
-
|
|
193
|
-
"#{(sudo_prefix + manager[:install]).join(' ')} #{package}"
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def install_system_packages(manager, tools, verbose: false)
|
|
197
|
-
packages = tools.map { |tool| tool.packages[manager[:id]] }.compact.uniq
|
|
198
|
-
return if packages.empty?
|
|
199
|
-
|
|
200
|
-
if manager[:update]
|
|
201
|
-
run_privileged_command(*manager[:update], verbose: verbose)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
if manager[:id] == :winget
|
|
205
|
-
# winget installs one id at a time.
|
|
206
|
-
packages.each { |package| run_privileged_command(*manager[:install], package, verbose: verbose) }
|
|
207
|
-
else
|
|
208
|
-
run_privileged_command(*manager[:install], *packages, verbose: verbose)
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def run_privileged_command(*command, verbose: false)
|
|
213
|
-
full = sudo_prefix + command
|
|
214
|
-
puts " $ #{full.join(' ')}"
|
|
215
|
-
# Package installs (apt/dnf/pacman...) routinely download hundreds of
|
|
216
|
-
# megabytes and take minutes. Always stream their output so the user
|
|
217
|
-
# can see progress instead of a frozen-looking prompt; `verbose` is
|
|
218
|
-
# left for callers that want to add their own detail elsewhere.
|
|
219
|
-
system(*full, out: $stdout, err: $stderr)
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
def sudo_prefix
|
|
223
|
-
return [] if windows_host? || macos_host?
|
|
224
|
-
return [] if Process.respond_to?(:uid) && Process.uid.zero?
|
|
225
|
-
return ["sudo"] if which_command("sudo")
|
|
226
|
-
|
|
227
|
-
[]
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def unsupported_host_message
|
|
231
|
-
os = RbConfig::CONFIG["host_os"]
|
|
232
|
-
if os.match?(/linux/i) && machine_arch.match?(/arm|aarch64/)
|
|
233
|
-
"Flutter publishes no official Linux #{machine_arch} build; use an x64 machine " \
|
|
234
|
-
"or build Flutter from source (https://github.com/flutter/flutter)."
|
|
235
|
-
else
|
|
236
|
-
"unsupported host platform: #{os}"
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
end
|