BoxenがどうやってMacアプリ(*.dmg)をインストールしているのか調べてみた



はじめに
----
[boxen](http://boxen.github.com)は便利そうなのですがちょっとへピーです。自分の環境ではうまく動いてくれないしエラーもよく分からず対応もできません。ですがboxenが提供している「dmgファイルからのMacアプリのインストール」は魅力的です。
このdmgファイルからのMacアプリインストールがhomebrewから出来れば、個人的にはそれで満足です。
homebrewで出来るかどうか置いといて、まずはboxenがどうやってこれをやっているのか調べてみました。

boxenで扱うモジュールの場所
-----
[http://github.com/boxen](http://github.com/boxen) の下に、puppet-XXXというリポジトリが多数あります。ここにpuppet-evernoteやpuppet-dropbox等があるので、これを元にアプリをインストールしているのでしょう。
リポジトリの中を見ると、spec/classes/(アプリ名)_spec.rb というファイルがあり、この中でdmgファイルの場所と``:provider => 'appdmg'``が設定されています。[puppet-dropboxのdropbox_spec.rb](https://github.com/boxen/puppet-dropbox/blob/master/spec/classes/dropbox_spec.rb)

``:provider => 'appdmg'``？
----
なんとなく```appdmg```というプロバイダ？がインストールをやってくれているようです。これboxenの中を探しても見つかりません。ですが、[puppet labs](https://github.com/puppetlabs)の中に[appdmg.rb](https://github.com/puppetlabs/puppet/blob/master/lib/puppet/provider/package/appdmg.rb)を見つけました。
puppetとしての処理も入ってるので、インストールしてる箇所を抜き出してみます。

```ruby:
  def self.installapp(source, name, orig_source)
    appname = File.basename(source);
    #=============
    # (4) 
    #=============
    ditto "--rsrc", source, "/Applications/#{appname}"
    File.open("/var/db/.puppet_appdmg_installed_#{name}", "w") do |t|
      t.print "name: '#{name}'\n"
      t.print "source: '#{orig_source}'\n"
    end
  end

  def self.installpkgdmg(source, name)
    unless source =~ /\.dmg$/i
      self.fail "Mac OS X PKG DMG's must specify a source string ending in .dmg"
    end
    require 'open-uri'
    require 'facter/util/plist'
    cached_source = source
    tmpdir = Dir.mktmpdir
    #=============
    # (1) 
    #=============
    begin
      if %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ cached_source
        cached_source = File.join(tmpdir, name)
        begin 
          curl "-o", cached_source, "-C", "-", "-k", "-L", "-s", "--url", source
          Puppet.debug "Success: curl transfered [#{name}]"
        rescue Puppet::ExecutionFailure
          Puppet.debug "curl did not transfer [#{name}].  Falling back to slower open-uri transfer methods."
          cached_source = source
        end
      end

    #=============
    # (2) 
    #=============
      open(cached_source) do |dmg|
        xml_str = hdiutil "mount", "-plist", "-nobrowse", "-readonly", "-mountrandom", "/tmp", dmg.path
          ptable = Plist::parse_xml xml_str
          # JJM Filter out all mount-paths into a single array, discard the rest.
          mounts = ptable['system-entities'].collect { |entity|
            entity['mount-point']
          }.select { |mountloc|; mountloc }
          begin
            mounts.each do |fspath|
              #=============
              # (3) 
              #=============
              Dir.entries(fspath).select { |f|
                f =~ /\.app$/i 
              }.each do |pkg|
                installapp("#{fspath}/#{pkg}", name, source)
              end
            end
          ensure
            hdiutil "eject", mounts[0]
          end
      end
    ensure
      FileUtils.remove_entry_secure(tmpdir, true)
    end
  end
```

rubyは好きですが得意ではありません。感覚で読み進めてみると、
- (1) dmgファイルをダウンロード
- (2) [hdiutil](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/hdiutil.1.html)でdmgをマウント
- (3) 末尾が.appなパスを選択
- (4) [ditto](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/ditto.1.html)で/Application/以下にコピー
となっているようです。 hdiutilやdittoはMac固有のコマンドだったと思うので、これが動くのはMacRubyだけ？とか思いますがよくわかりません。意外に単純ですね。

homebrewにポーティングするとしたら
----
homebrewのspecfileに、appdmg.rbと同等の動きをするロジックを組み込んで、boxenの(puppetの) xxxx_spec.rbを読み込ませれば良さそうです。そのうちやってみますが誰かやってたら教えてください。