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
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
|
require 'pathname'
require 'mach'
require 'resource'
require 'metafiles'
# we enhance pathname to make our code more readable
class Pathname
include MachO
BOTTLE_EXTNAME_RX = /(\.[a-z0-9_]+\.bottle\.(\d+\.)?tar\.gz)$/
def install *sources
sources.each do |src|
case src
when Resource
src.stage(self)
when Resource::Partial
src.resource.stage { install(*src.files) }
when Array
if src.empty?
opoo "tried to install empty array to #{self}"
return
end
src.each { |s| install_p(s, File.basename(s)) }
when Hash
if src.empty?
opoo "tried to install empty hash to #{self}"
return
end
src.each { |s, new_basename| install_p(s, new_basename) }
else
install_p(src, File.basename(src))
end
end
end
def install_p(src, new_basename)
raise Errno::ENOENT, src.to_s unless File.symlink?(src) || File.exist?(src)
src = Pathname(src)
dst = join(new_basename)
dst = yield(src, dst) if block_given?
mkpath
# Use FileUtils.mv over File.rename to handle filesystem boundaries. If src
# is a symlink, and its target is moved first, FileUtils.mv will fail:
# https://bugs.ruby-lang.org/issues/7707
# In that case, use the system "mv" command.
if src.symlink?
raise unless Kernel.system 'mv', src, dst
else
FileUtils.mv src, dst
end
end
private :install_p
# Creates symlinks to sources in this folder.
def install_symlink *sources
sources.each do |src|
case src
when Array
src.each { |s| install_symlink_p(s, File.basename(s)) }
when Hash
src.each { |s, new_basename| install_symlink_p(s, new_basename) }
else
install_symlink_p(src, File.basename(src))
end
end
end
def install_symlink_p(src, new_basename)
src = Pathname(src).expand_path(self)
dst = join(new_basename)
mkpath
FileUtils.ln_sf(src.relative_path_from(dst.parent), dst)
end
private :install_symlink_p
# we assume this pathname object is a file obviously
alias_method :old_write, :write if method_defined?(:write)
def write(content, *open_args)
raise "Will not overwrite #{to_s}" if exist?
dirname.mkpath
open("w", *open_args) { |f| f.write(content) }
end
def binwrite(contents, *open_args)
open("wb", *open_args) { |f| f.write(contents) }
end unless method_defined?(:binwrite)
def binread(*open_args)
open("rb", *open_args) { |f| f.read }
end unless method_defined?(:binread)
# NOTE always overwrites
def atomic_write content
require "tempfile"
tf = Tempfile.new(basename.to_s, dirname)
begin
tf.binmode
tf.write(content)
begin
old_stat = stat
rescue Errno::ENOENT
old_stat = default_stat
end
uid = Process.uid
gid = Process.groups.delete(old_stat.gid) { Process.gid }
begin
tf.chown(uid, gid)
tf.chmod(old_stat.mode)
rescue Errno::EPERM
end
File.rename(tf.path, self)
ensure
tf.close!
end
end
def default_stat
sentinel = parent.join(".brew.#{Process.pid}.#{rand(Time.now.to_i)}")
sentinel.open("w") { }
sentinel.stat
ensure
sentinel.unlink
end
private :default_stat
def cp dst
opoo "Pathname#cp is deprecated, use FileUtils.cp"
if file?
FileUtils.cp to_s, dst
else
FileUtils.cp_r to_s, dst
end
return dst
end
def cp_path_sub pattern, replacement
raise "#{self} does not exist" unless self.exist?
dst = sub(pattern, replacement)
raise "#{self} is the same file as #{dst}" if self == dst
if directory?
dst.mkpath
else
dst.dirname.mkpath
dst = yield(self, dst) if block_given?
FileUtils.cp(self, dst)
end
end
# extended to support common double extensions
alias extname_old extname
def extname(path=to_s)
BOTTLE_EXTNAME_RX.match(path)
return $1 if $1
/(\.(tar|cpio|pax)\.(gz|bz2|lz|xz|Z))$/.match(path)
return $1 if $1
return File.extname(path)
end
# for filetypes we support, basename without extension
def stem
File.basename((path = to_s), extname(path))
end
# I don't trust the children.length == 0 check particularly, not to mention
# it is slow to enumerate the whole directory just to see if it is empty,
# instead rely on good ol' libc and the filesystem
def rmdir_if_possible
rmdir
true
rescue Errno::ENOTEMPTY
if (ds_store = self+'.DS_Store').exist? && children.length == 1
ds_store.unlink
retry
else
false
end
rescue Errno::EACCES, Errno::ENOENT
false
end
def chmod_R perms
opoo "Pathname#chmod_R is deprecated, use FileUtils.chmod_R"
require 'fileutils'
FileUtils.chmod_R perms, to_s
end
def version
require 'version'
Version.parse(self)
end
def compression_type
case extname
when ".jar", ".war"
# Don't treat jars or wars as compressed
return
when ".gz"
# If the filename ends with .gz not preceded by .tar
# then we want to gunzip but not tar
return :gzip_only
when ".bz2"
return :bzip2_only
end
# Get enough of the file to detect common file types
# POSIX tar magic has a 257 byte offset
# magic numbers stolen from /usr/share/file/magic/
case open('rb') { |f| f.read(262) }
when /^PK\003\004/n then :zip
when /^\037\213/n then :gzip
when /^BZh/n then :bzip2
when /^\037\235/n then :compress
when /^.{257}ustar/n then :tar
when /^\xFD7zXZ\x00/n then :xz
when /^LZIP/n then :lzip
when /^Rar!/n then :rar
when /^7z\xBC\xAF\x27\x1C/n then :p7zip
when /^xar!/n then :xar
when /^\xed\xab\xee\xdb/n then :rpm
else
# This code so that bad-tarballs and zips produce good error messages
# when they don't unarchive properly.
case extname
when ".tar.gz", ".tgz", ".tar.bz2", ".tbz" then :tar
when ".zip" then :zip
end
end
end
def text_executable?
%r[^#!\s*\S+] === open('r') { |f| f.read(1024) }
end
def incremental_hash(klass)
digest = klass.new
if digest.respond_to?(:file)
digest.file(self)
else
buf = ""
open("rb") { |f| digest << buf while f.read(16384, buf) }
end
digest.hexdigest
end
def sha1
require 'digest/sha1'
incremental_hash(Digest::SHA1)
end
def sha256
require 'digest/sha2'
incremental_hash(Digest::SHA2)
end
def verify_checksum expected
raise ChecksumMissingError if expected.nil? or expected.empty?
actual = Checksum.new(expected.hash_type, send(expected.hash_type).downcase)
raise ChecksumMismatchError.new(self, expected, actual) unless expected == actual
end
# FIXME eliminate the places where we rely on this method
alias_method :to_str, :to_s unless method_defined?(:to_str)
def cd
Dir.chdir(self){ yield }
end
def subdirs
children.select{ |child| child.directory? }
end
def resolved_path
self.symlink? ? dirname+readlink : self
end
def resolved_path_exists?
link = readlink
rescue ArgumentError
# The link target contains NUL bytes
false
else
(dirname+link).exist?
end
def make_relative_symlink(src)
dirname.mkpath
File.symlink(src.relative_path_from(dirname), self)
end
def /(other)
unless other.respond_to?(:to_str) || other.respond_to?(:to_path)
opoo "Pathname#/ called on #{inspect} with #{other.inspect} as an argument"
puts "This behavior is deprecated, please pass either a String or a Pathname"
end
self + other.to_s
end unless method_defined?(:/)
def ensure_writable
saved_perms = nil
unless writable_real?
saved_perms = stat.mode
chmod 0644
end
yield
ensure
chmod saved_perms if saved_perms
end
def install_info
quiet_system "/usr/bin/install-info", "--quiet", to_s, "#{dirname}/dir"
end
def uninstall_info
quiet_system "/usr/bin/install-info", "--delete", "--quiet", to_s, "#{dirname}/dir"
end
def find_formula
[join("Formula"), join("HomebrewFormula"), self].each do |d|
if d.exist?
d.children.each do |pn|
yield pn if pn.extname == ".rb"
end
break
end
end
end
# Writes an exec script in this folder for each target pathname
def write_exec_script *targets
targets.flatten!
if targets.empty?
opoo "tried to write exec scripts to #{self} for an empty list of targets"
return
end
mkpath
targets.each do |target|
target = Pathname.new(target) # allow pathnames or strings
(self+target.basename()).write <<-EOS.undent
#!/bin/bash
exec "#{target}" "$@"
EOS
end
end
# Writes an exec script that sets environment variables
def write_env_script target, env
env_export = ''
env.each {|key, value| env_export += "#{key}=\"#{value}\" "}
dirname.mkpath
self.write <<-EOS.undent
#!/bin/bash
#{env_export}exec "#{target}" "$@"
EOS
end
# Writes a wrapper env script and moves all files to the dst
def env_script_all_files dst, env
dst.mkpath
Pathname.glob("#{self}/*") do |file|
next if file.directory?
dst.install(file)
new_file = dst+file.basename
file.write_env_script(new_file, env)
end
end
# Writes an exec script that invokes a java jar
def write_jar_script target_jar, script_name, java_opts=""
mkpath
(self+script_name).write <<-EOS.undent
#!/bin/bash
exec java #{java_opts} -jar #{target_jar} "$@"
EOS
end
def install_metafiles from=Pathname.pwd
Pathname(from).children.each do |p|
next if p.directory?
next unless Metafiles.copy?(p.basename.to_s)
# Some software symlinks these files (see help2man.rb)
filename = p.resolved_path
# Some software links metafiles together, so by the time we iterate to one of them
# we may have already moved it. libxml2's COPYING and Copyright are affected by this.
next unless filename.exist?
filename.chmod 0644
install(filename)
end
end
def abv
out = ""
n = Utils.popen_read("find", expand_path.to_s, "-type", "f", "!", "-name", ".DS_Store").split("\n").size
out << "#{n} files, " if n > 1
out << Utils.popen_read("/usr/bin/du", "-hs", expand_path.to_s).split("\t")[0].strip
out
end
# We redefine these private methods in order to add the /o modifier to
# the Regexp literals, which forces string interpolation to happen only
# once instead of each time the method is called. This is fixed in 1.9+.
if RUBY_VERSION <= "1.8.7"
alias_method :old_chop_basename, :chop_basename
def chop_basename(path)
base = File.basename(path)
if /\A#{Pathname::SEPARATOR_PAT}?\z/o =~ base
return nil
else
return path[0, path.rindex(base)], base
end
end
private :chop_basename
alias_method :old_prepend_prefix, :prepend_prefix
def prepend_prefix(prefix, relpath)
if relpath.empty?
File.dirname(prefix)
elsif /#{SEPARATOR_PAT}/o =~ prefix
prefix = File.dirname(prefix)
prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
prefix + relpath
else
prefix + relpath
end
end
private :prepend_prefix
elsif RUBY_VERSION == "2.0.0"
# https://bugs.ruby-lang.org/issues/9915
prepend Module.new {
def inspect
super.force_encoding(@path.encoding)
end
}
end
end
module ObserverPathnameExtension
class << self
attr_accessor :n, :d
def reset_counts!
@n = @d = 0
end
def total
n + d
end
def counts
[n, d]
end
end
def unlink
super
puts "rm #{to_s}" if ARGV.verbose?
ObserverPathnameExtension.n += 1
end
def rmdir
super
puts "rmdir #{to_s}" if ARGV.verbose?
ObserverPathnameExtension.d += 1
end
def make_relative_symlink src
super
puts "ln -s #{src.relative_path_from(dirname)} #{basename}" if ARGV.verbose?
ObserverPathnameExtension.n += 1
end
def install_info
super
puts "info #{to_s}" if ARGV.verbose?
end
def uninstall_info
super
puts "uninfo #{to_s}" if ARGV.verbose?
end
end
|