-
Notifications
You must be signed in to change notification settings - Fork 256
/
msys2_installation.rb
353 lines (307 loc) · 10.7 KB
/
msys2_installation.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
require "rbconfig"
module RubyInstaller
module Build # Use for: Build, Runtime
# :nodoc:
class Msys2Installation
MSYS2_INSTALL_KEY = "SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/"
MSYS2_INSTALL_KEY_WOW = "SOFTWARE/WOW6432Node/Microsoft/Windows/CurrentVersion/Uninstall/"
class MsysNotFound < RuntimeError
end
class CommandError < RuntimeError
end
attr_reader :mingwdir
attr_reader :mingwarch
attr_reader :mingw_package_prefix
attr_reader :ruby_bin_dir
def initialize(msys_path: nil, mingwarch: nil, mingw_package_prefix: nil, ruby_bin_dir: nil)
@msys_path = msys_path
@msys_path_fixed = msys_path ? true : false
@mingwdir = nil
@mingwarch = mingwarch || (
case RUBY_PLATFORM
when /x64.*ucrt/ then 'ucrt64'
when /x64.*mingw32/ then 'mingw64'
when /i386.*mingw32/ then 'mingw32'
else raise "unsupported ruby platform #{RUBY_PLATFORM.inspect}"
end
)
@mingw_package_prefix = mingw_package_prefix || begin
case @mingwarch
when 'mingw32' then "mingw-w64-i686"
when 'mingw64' then "mingw-w64-x86_64"
when 'ucrt64' then "mingw-w64-ucrt-x86_64"
else raise "unknown mingwarch #{@mingwarch.inspect}"
end
end
@ruby_bin_dir = ruby_bin_dir || File.join(RbConfig::TOPDIR, "bin")
end
def reset_cache
@msys_path = nil unless @msys_path_fixed
end
def iterate_msys_paths
# Prefer MSYS2_PATH if ENV set
if ENV["MSYS2_PATH"]
yield ENV["MSYS2_PATH"]
end
# Prefer MSYS2 when installed within the ruby directory.
yield File.join(RbConfig::TOPDIR, "msys64")
yield File.join(RbConfig::TOPDIR, "msys32")
# Then try MSYS2 next to the ruby directory.
yield File.join(File.dirname(RbConfig::TOPDIR), "msys64")
yield File.join(File.dirname(RbConfig::TOPDIR), "msys32")
# If msys2 is installed at the default location
yield "c:/msys64"
yield "c:/msys32"
# If msys2 is installed per installer.exe
require "win32/registry"
[
[Win32::Registry::HKEY_CURRENT_USER, MSYS2_INSTALL_KEY],
[Win32::Registry::HKEY_CURRENT_USER, MSYS2_INSTALL_KEY_WOW],
[Win32::Registry::HKEY_LOCAL_MACHINE, MSYS2_INSTALL_KEY],
[Win32::Registry::HKEY_LOCAL_MACHINE, MSYS2_INSTALL_KEY_WOW],
].each do |reg_root, base_key|
begin
reg_root.open(backslachs(base_key)) do |reg|
reg.each_key do |subkey|
subreg = reg.open(subkey)
begin
if subreg['DisplayName'] =~ /^MSYS2 / && File.directory?(il=subreg['InstallLocation'])
yield il
end
rescue Encoding::InvalidByteSequenceError, Win32::Registry::Error
# Ignore entries without valid installer data or broken character encoding
end
end
end
rescue Win32::Registry::Error
end
end
ENV['PATH'] && ENV['PATH'].split(";").each do |path|
# If /path/to/msys64 is in the PATH (e.g. Chocolatey)
yield path
end
# If msys2 is installed by scoop package manager
begin
yield IO.popen(["scoop", "prefix", "msys2"], &:read).strip
rescue SystemCallError
end
raise MsysNotFound, "MSYS2 could not be found"
end
def msys_path
@msys_path ||= begin
iterate_msys_paths do |path|
if File.exist?(File.join(path, "usr/bin/msys-2.0.dll"))
break backslachs(path)
end
end
end
end
def msys_bin_path
backslachs( File.join(msys_path, "/usr/bin") )
end
def mingw_bin_path
backslachs( File.join(msys_path, mingwarch, "bin") )
end
def mingw_prefix
"/#{mingwarch}"
end
def enable_dll_search_paths
@mingwdir ||= begin
DllDirectory.set_defaults
path = mingw_bin_path
DllDirectory.new(path) if File.directory?(path)
rescue MsysNotFound
# We silently ignore this error to allow Ruby installations without MSYS2.
end
end
def disable_dll_search_paths
@mingwdir.remove if @mingwdir
@mingwdir = nil
end
private def backslachs(path)
path.gsub("/", "\\")
end
private def msys_apps_envvars
vars = {}
msys_bin = msys_bin_path
mingw_bin = mingw_bin_path
ruby_bin = backslachs( ruby_bin_dir )
ridkusepath = ENV["RIDK_USE_PATH"]
vars['PATH'] = [ridkusepath, ruby_bin, mingw_bin, msys_bin].compact.join(";")
vars['RI_DEVKIT'] = msys_path
vars['MSYSTEM'] = mingwarch.upcase
vars['PKG_CONFIG_PATH'] = "#{mingw_prefix}/lib/pkgconfig:#{mingw_prefix}/share/pkgconfig"
vars['ACLOCAL_PATH'] = "#{mingw_prefix}/share/aclocal:/usr/share/aclocal"
vars['MANPATH'] = "#{mingw_prefix}/share/man"
vars['MINGW_PACKAGE_PREFIX'] = mingw_package_prefix
if ENV['TMP'] !~ /\A[ -~]*\z/
# TMP has some Unicode characters -> use MSYS2's /tmp to work around incompat with gcc's temporary files
vars['TMP'] = backslachs( msys_path + "/tmp" )
end
case mingwarch
when 'mingw32'
vars['MSYSTEM_PREFIX'] = '/mingw32'
vars['MSYSTEM_CARCH'] = 'i686'
vars['MSYSTEM_CHOST'] = 'i686-w64-mingw32'
vars['MINGW_CHOST'] = vars['MSYSTEM_CHOST']
vars['MINGW_PREFIX'] = vars['MSYSTEM_PREFIX']
when 'mingw64'
vars['MSYSTEM_PREFIX'] = '/mingw64'
vars['MSYSTEM_CARCH'] = 'x86_64'
vars['MSYSTEM_CHOST'] = 'x86_64-w64-mingw32'
vars['MINGW_CHOST'] = vars['MSYSTEM_CHOST']
vars['MINGW_PREFIX'] = vars['MSYSTEM_PREFIX']
when 'ucrt64'
vars['MSYSTEM_PREFIX'] = '/ucrt64'
vars['MSYSTEM_CARCH'] = 'x86_64'
vars['MSYSTEM_CHOST'] = 'x86_64-w64-mingw32'
vars['MINGW_CHOST'] = vars['MSYSTEM_CHOST']
vars['MINGW_PREFIX'] = vars['MSYSTEM_PREFIX']
else raise "unknown mingwarch #{@mingwarch.inspect}"
end
begin
locale = IO.popen([File.join(msys_bin, "locale"), "-uU"], &:read)
rescue SystemCallError
else
vars['LANG'] = locale=~/UTF-8/ ? locale.to_s.strip : 'C'
end
vars
end
private def with_msys_install_hint(if_no_msys = :hint)
case if_no_msys
when :hint
begin
yield
rescue MsysNotFound
$stderr.puts "MSYS2 could not be found. Please run 'ridk install'"
$stderr.puts "or download and install MSYS2 manually from https://msys2.github.io/"
exit 1
end
when :raise
yield
else
raise ArgumentError, "invalid value #{if_no_msys.inspect} for variable if_no_msys"
end
end
def enable_msys_apps(if_no_msys: :hint, for_gem_install: false)
vars = with_msys_install_hint(if_no_msys) do
msys_apps_envvars
end
changed = false
if (path=vars.delete("PATH")) && !ENV['PATH'].include?(path)
phrase = "Temporarily enhancing PATH for MSYS/MINGW..."
if for_gem_install && defined?(Gem)
Gem.ui.say(phrase) if Gem.configuration.verbose
elsif $DEBUG
$stderr.puts phrase
end
changed = true
ENV['PATH'] = path + ";" + ENV['PATH']
end
vars.each do |key, val|
changed = true if ENV[key] != val
ENV[key] = val
end
changed
end
def disable_msys_apps(if_no_msys: :hint)
vars = with_msys_install_hint(if_no_msys) do
msys_apps_envvars
end
changed = false
if path=vars.delete("PATH")
old_path = ENV['PATH']
ENV['PATH'] = old_path.gsub(path + ";", "")
changed = ENV['PATH'] != old_path
end
vars.each do |key, val|
changed = true if ENV[key]
ENV.delete(key)
end
changed
end
def with_msys_apps_enabled(**args)
changed = enable_msys_apps(**args)
begin
yield
ensure
disable_msys_apps(**args) if changed
end
end
def with_msys_apps_disabled(**args)
changed = disable_msys_apps(**args)
begin
yield
ensure
enable_msys_apps(**args) if changed
end
end
# This method is used for the ridk command.
def enable_msys_apps_per_cmd
vars = with_msys_install_hint{ msys_apps_envvars }
if (path=vars.delete("PATH")) && !ENV['PATH'].include?(path)
vars['PATH'] = path + ";" + ENV['PATH']
end
vars.map do |key, val|
"#{key}=#{val}"
end.join("\n")
end
# This method is used for the ridk command.
def disable_msys_apps_per_cmd
vars = with_msys_install_hint{ msys_apps_envvars }
str = "".dup
if path=vars.delete("PATH")
str << "PATH=#{ ENV['PATH'].gsub(path + ";", "") }\n"
end
str << vars.map do |key, val|
"#{key}="
end.join("\n")
end
# This method is used for the ridk command.
def enable_msys_apps_per_ps1
vars = with_msys_install_hint{ msys_apps_envvars }
if (path=vars.delete("PATH")) && !ENV['PATH'].include?(path)
vars['PATH'] = path + ";" + ENV['PATH']
end
vars.map do |key, val|
"$env:#{key}=\"#{val.gsub('"', '`"')}\""
end.join(";")
end
# This method is used for the ridk command.
def disable_msys_apps_per_ps1
vars = with_msys_install_hint{ msys_apps_envvars }
str = "".dup
if path=vars.delete("PATH")
str << "$env:PATH=\"#{ ENV['PATH'].gsub(path + ";", "").gsub('"', '`"') }\";"
end
str << vars.map do |key, val|
"$env:#{key}=''"
end.join(";")
end
def install_packages(packages, verbose: false)
return if packages.empty?
with_msys_apps_enabled do
# Find packages that are already installed
skips, installs = packages.partition do |package|
IO.popen(["pacman", "-Q", package], err: :out, &:read)
$?.success?
end
Gem.ui.say("Using msys2 packages: #{skips.join(" ")}") if verbose && skips.any?
# Install required packages
if installs.any?
Gem.ui.say("Installing required msys2 packages: #{installs.join(" ")}") if verbose
args = ["pacman", "-S", "--needed", "--noconfirm", *installs]
Gem.ui.say("> #{args.join(" ")}") if verbose==1
res = IO.popen(args, &:read)
raise CommandError, "pacman failed with the following output:\n#{res}" if !$? || $?.exitstatus != 0
Gem.ui.say(res) if verbose==1
end
end
end
def install_mingw_packages(packages, verbose: false)
packages = packages.map{|pack| "#{mingw_package_prefix}-#{pack}" }
install_packages(packages, verbose: verbose)
end
end
end
end