aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/extend/fileutils.rb
blob: af17d4effcd30f40f8331bf653ba0cdb9ee6c578 (plain)
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
require "fileutils"
require "tmpdir"
require "etc"

# Homebrew extends Ruby's `FileUtils` to make our code more readable.
# @see http://ruby-doc.org/stdlib-1.8.7/libdoc/fileutils/rdoc/FileUtils.html Ruby's FileUtils API
module FileUtils
  # Create a temporary directory then yield. When the block returns,
  # recursively delete the temporary directory. Passing opts[:retain]
  # or calling `do |staging| ... staging.retain!` in the block will skip
  # the deletion and retain the temporary directory's contents.
  def mktemp(prefix = name, opts = {})
    Mktemp.new(prefix, opts).run do |staging|
      yield staging
    end
  end

  module_function :mktemp

  # Performs mktemp's functionality, and tracks the results.
  # Each instance is only intended to be used once.
  class Mktemp
    include FileUtils

    # Path to the tmpdir used in this run, as a Pathname.
    attr_reader :tmpdir

    def initialize(prefix = name, opts = {})
      @prefix = prefix
      @retain = opts[:retain]
      @quiet = false
    end

    # Instructs this Mktemp to retain the staged files
    def retain!
      @retain = true
    end

    # True if the staged temporary files should be retained
    def retain?
      @retain
    end

    # Instructs this Mktemp to not emit messages when retention is triggered
    def quiet!
      @quiet = true
    end

    def to_s
      "[Mktemp: #{tmpdir} retain=#{@retain} quiet=#{@quiet}]"
    end

    def run
      @tmpdir = Pathname.new(Dir.mktmpdir("#{@prefix}-", HOMEBREW_TEMP))

      # Make sure files inside the temporary directory have the same group as the
      # brew instance.
      #
      # Reference from `man 2 open`
      # > When a new file is created, it is given the group of the directory which
      # contains it.
      group_id = if HOMEBREW_BREW_FILE.grpowned?
                   HOMEBREW_BREW_FILE.stat.gid
                 else
                   Process.gid
                 end
      begin
        # group_id.to_s makes OS X 10.6.7 (ruby-1.8.7-p174) and earlier happy.
        chown(nil, group_id.to_s, tmpdir)
      rescue Errno::EPERM
        opoo "Failed setting group \"#{Etc.getgrgid(group_id).name}\" on #{tmp}"
      end

      begin
        Dir.chdir(tmpdir) { yield self }
      ensure
        ignore_interrupts { rm_rf(tmpdir) } unless retain?
      end
    ensure
      if retain? && !@tmpdir.nil? && !@quiet
        ohai "Kept temporary files"
        puts "Temporary files retained at #{@tmpdir}"
      end
    end
  end

  # @private
  alias_method :old_mkdir, :mkdir

  # A version of mkdir that also changes to that folder in a block.
  def mkdir(name, &_block)
    old_mkdir(name)
    if block_given?
      chdir name do
        yield
      end
    end
  end
  module_function :mkdir

  # The #copy_metadata method in all current versions of Ruby has a
  # bad bug which causes copying symlinks across filesystems to fail;
  # see #14710.
  # This was resolved in Ruby HEAD after the release of 1.9.3p194, but
  # never backported into the 1.9.3 branch. Fixed in 2.0.0.
  # The monkey-patched method here is copied directly from upstream fix.
  if RUBY_VERSION < "2.0.0"
    # @private
    class Entry_
      alias_method :old_copy_metadata, :copy_metadata
      def copy_metadata(path)
        st = lstat
        unless st.symlink?
          File.utime st.atime, st.mtime, path
        end
        begin
          if st.symlink?
            begin
              File.lchown st.uid, st.gid, path
            rescue NotImplementedError
            end
          else
            File.chown st.uid, st.gid, path
          end
        rescue Errno::EPERM
          # clear setuid/setgid
          if st.symlink?
            begin
              File.lchmod st.mode & 01777, path
            rescue NotImplementedError
            end
          else
            File.chmod st.mode & 01777, path
          end
        else
          if st.symlink?
            begin
              File.lchmod st.mode, path
            rescue NotImplementedError
            end
          else
            File.chmod st.mode, path
          end
        end
      end
    end
  end

  # Run `scons` using a Homebrew-installed version rather than whatever is in the `PATH`.
  def scons(*args)
    system Formulary.factory("scons").opt_bin/"scons", *args
  end

  # Run the `rake` from the `ruby` Homebrew is using rather than whatever is in the `PATH`.
  def rake(*args)
    system RUBY_BIN/"rake", *args
  end

  if method_defined?(:ruby)
    # @private
    alias_method :old_ruby, :ruby
  end

  # Run the `ruby` Homebrew is using rather than whatever is in the `PATH`.
  def ruby(*args)
    system RUBY_PATH, *args
  end

  # Run `xcodebuild` without Homebrew's compiler environment variables set.
  def xcodebuild(*args)
    removed = ENV.remove_cc_etc
    system "xcodebuild", *args
  ensure
    ENV.update(removed)
  end
end

# Shim File.write for Ruby 1.8.7, where it's absent
unless File.respond_to?(:write)
  class File
    def self.write(filename, contents)
      File.open(filename, 'w') do |file|
        file.write contents
      end
    end
  end
end