Hatena::ブログ(Diary)

きりかノート 2冊め

2017-06-18

[] RubyCocoaをふつーのgemにしたい

だいぶ前から考えてた、RubyCocoaをふつーのgemにすることに挑戦してみた。アプリ用の.frameworkはまだ手を付けてないけど、拡張ライブラリのほうは`rake test`が全部通るとこまできた。


-現状の課題

今のRubyCocoaはほとんど全部の機能がRubyCocoa.frameworkのほうに入っているので、rubyスクリプトから使う場合にも必ずフレームワークの配置が必要になる。フレームワークは特定のバージョン・構成のlibrubyにリンクして、ちょっとしたスクリプト書くときや、いろんなバージョンで検証したいときにけっこう手間がかかる。

目標はこんなところ。

  • `gem "rubycocoa"`でrubycocoaのスクリプトを書けるようにしたい
  • bundler/rakeベースの開発ができるようにしたい

作業の流れ(ここまで)

とりあえず、`bundle gem --ext`で新規に用意したものに、framework/src, test/のファイルを入れて試してみて、問題や変更点を洗い出し。

めどが立ったので、実際のリポジトリでブランチ切って作業。

  • bundleの生成ファイルを追加
  • ソースファイルを移動
    • [ruby] framework/src/ruby/osx -> lib/osx
    • [objc] framework/src/objc -> ext/rubycocoa
  • 拡張ライブラリのコンパイルをextconf.rbにお任せに
  • テストを調整
    • minitestからtest-unitに
    • ファイル名をtest_*.rbから*_test.rbに変更。(生成されたRakefileの形式に合わせる)
    • testディレクトリで実行することを想定した相対パスまわりの対応。
  • 結果的にいらなくなったファイルを整理。

だいたい1日半くらい。思ったより簡単に対応できた印象。


今後の予定

残ってるRubyCocoa.frameworkは、NSBundleでrubycocoa.bundleをロードして、dlopenから初期化関数を呼び出すみたいな感じになる予定。.framework自体はlibrubyにリンクしない形式にして、場合によっては複数のバージョンのrubycocoa.bundleを使い分けできるように準備しておく。

2017-06-17

[] RubyCocoa今週のコミット 2017-06-12..17

新しいmacOSbetaでてたので試してみた。案の定いろいろ問題あったんだけど、それ以前に10.12で動いてないことにいまさら気付いた…


librubyに静的リンクしたRubyCocoaの生成を可能に

すっごい前にプルリクもらってたのをマージ。

なんかいろいろな変更が混ざってるからあとで見ようと思って完全に忘れてたですよ。


macOS 10.12でSEGVする問題を修正

以前に確認したような気がするんだけど、10.12だとrequire 'osx/cocoa'だけでSEGVするので対応。

どうも調べたところ、WebKitの内部クラスでrespondsToSelector:をちゃんと実装していないのがいくつかあって、それが原因らしい。そいつらはRuby側からも見えてる必要がないので無視するように変更した。


テストまわりの修正

  • QTKitの関数がなくなってたので、それらは実行しない。
  • ruby-2.4でFixnumの警告がでるのでIntegerに変更した。

10.11以降で付属のrubyで落ちる

OS付属のrubyでrequire 'osx/cocoa'すると、class_addMethod()のあたりで落ちる。たぶんSIP(System Integrity Protecton)で保護されてるため。たとえば/usr/bin/rubyを作業ディレクトリにコピーして、そのrubyから実行すると問題なく動作する。

なので、リンクした.appとかは問題ない。rubyスクリプトでなんかやろうとすると落ちる。

たぶんovmixのあたりの処理を見直して、Objective-C側で定義したクラスにメソッド追加とかできないように制御すれば落ちなくはなると思う。ちょっと優先度低め。

   diff --git a/install.rb b/install.rb
   index a57e53c..4fffc2f 100644
   --- a/install.rb
   +++ b/install.rb
   @@ -1088,6 +1088,7 @@ class ToplevelInstaller < Installer
        ruby_cmd = '"' + File.join(RbConfig::CONFIG['bindir'],
                                   RbConfig::CONFIG['RUBY_INSTALL_NAME']) + '"'
        ruby_cmd = "/usr/bin/arch -#{@options['arch']} " + ruby_cmd if @options['arch']
   +    ruby_cmd = File.expand_path("../ruby", __FILE__)
        dive_into('test') {
          ENV['DYLD_FRAMEWORK_PATH'] = File.join('../framework', framework_obj_path)
          ENV['BRIDGE_SUPPORT_PATH'] = '../framework/bridge-support'
   diff --git a/test/util.rb b/test/util.rb
   index d65afe5..fbe9c54 100644
   --- a/test/util.rb
   +++ b/test/util.rb
   @@ -2,7 +2,8 @@
    module TestHelper

      def __spawn_line(line)
   -    cmd = "#{@ruby_path} -I../lib -I../ext/rubycocoa -e \"#{line}\""
   +    ruby_cmd = File.expand_path("../../ruby", __FILE__)
   +    cmd = "#{ruby_cmd} -I../lib -I../ext/rubycocoa -e \"#{line}\""
        cmd = cmd_with_dyld_env(cmd)
        res = IO.popen(cmd) {|io| io.read}
        raise "Can't spawn Ruby line: '#{line}'" unless $?.success?

2017-06-12

PowerShellテンプレートエンジンEPS

rubyのERBみたいの。

Windows上でテキスト生成するのにテンプレート処理的なことをしたくって、でもわざわざrubyとかいれるのもなあと思って、PowerShell Galleryで”Template”で検索。コード生成みたいのが多い中で、EPSがふつうにテキスト生成するものっぽいので試してみることに。

"<%" "%>"で囲った中にPowerShellのコードを書くと実行される。まんまeRuby/ERBだね。

ReadMe.mdの例。

   PS> Get-Content Test.eps
   Hi <%= $name %>

   <%# this is a comment -%>
   Please buy me the following items:
   <% 1..5 | %{ -%>
     - <%= $_ %> pigs ...
   <% } -%>

   Dave is a <% if($True) { %>boy<% } else { %>girl<% } %>.

   Thanks,
   Dave
   <%= (Get-Date -f yyyy-MM-dd) %>
   PS> Invoke-EpsTemplate -Path Test.eps -Safe -binding @{ name = "dave" }
   Hi dave

   Please buy me the following items:
     - 1 pigs ...
     - 2 pigs ...
     - 3 pigs ...
     - 4 pigs ...
     - 5 pigs ...

   Dave is a boy.

   Thanks,
   Dave
   2017-06-12

由来的にもわからないでもないが、"EPS"って名前はどーなん?特にファイル名の拡張子


ファイル入力のとき相対パス解決しない

Invoke-EpsTemplateには文字列かファイルのいずれかを食わせるんだけど、ファイル名を渡したときReadMeにあるように相対パスで書いてもうまく処理できないことがある。コードみたら簡単に直せそうだったのでプルリクエストだしといた。

テストどうするか悩んだものの、もともと-Pathに対してテストなかったのでスルー。

無事に取り込まれたので、現在の0.4.0の次のリリースで直ってると思う。


日本語環境でテストが失敗する

例外のテストがメッセージを検証するというものなので、日本語環境で実行すると失敗する。

手元では直してみたけれど、この方法でよいかが自信ないのでプルリクエスト出すのは保留してる。

2016-12-28

[] MacBookのTouch IDでsudoを使う(PAM)

MacBookに付いてるTouch IDでsudoできないかなあと探してみたら、”「MacBook Proの「Touch ID」を利用してsudoなどの認証を行うPAMが公開。 | AAPL Ch.」”という記事を見つけたので試してみた。

手順は

1. pam_touchidコンパイルして、てきとうな場所に置く

2. /etc/pam.d/sudo を編集して上記のモジュールの記述を**先頭に**追加

となる。これだけでちゃんと動いて、なんか不思議な感じする。。

ローカルにPortfile書いてインストールして設定した。issueでSSHのケース議論してたり、コードまだ読んでなかったりと自信ないので公式のportには入れてない。brewもまだないっぽいし。

   % port contents pam_touchid
   Port pam_touchid contains:
     /opt/local/lib/pam/pam_touchid.so.2
     /opt/local/share/doc/pam_touchid/LICENSE
     /opt/local/share/doc/pam_touchid/README.md
     /opt/local/share/doc/pam_touchid/screenshot.png
   % grep -n pam_touchid /etc/pam.d/sudo
   2:auth     sufficient     /opt/local/lib/pam/pam_touchid.so reason="execute a command as another user"
   % port cat pam_touchid
   # -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:et:sw=4:ts=4:sts=4

   PortSystem          1.0
   PortGroup           github 1.0
   PortGroup           xcode 1.0

   github.setup        hamzasood pam_touchid 0.0.1
   github.version      ec7b7bdc1285b3588fe083d5bb9ac5ab5137fda2
   categories          security
   description         A PAM module for authentication with Touch ID
   long_description    ${description}

   platforms           darwin
   license             GPL-3
   maintainers         kimuraw openmaintainer

   checksums           md5  5a80622d1150b9ff34515e7e97ee3a9c \
                       sha1 b08b8d338574da2131faaebd9f188098c93d51a1 \
                       sha256  7bac41ef30b7ba4a69aea5f8683d3b035a469139697f985ab8030c68f9724a13

   xcode.project       pam_touchid.xcodeproj
   xcode.configuration Release
   xcode.destroot.path ${prefix}/lib/pam
   xcode.destroot.type none
   # build without code signing for macports
   xcode.build.settings    CODE_SIGN_IDENTITY=
   xcode.destroot.settings CODE_SIGN_IDENTITY= INSTALL_MODE_FLAG=440

   post-destroot {
       xinstall -d ${destroot}${prefix}/share/doc/${name}
       xinstall -W ${worksrcpath} README.md LICENSE screenshot.png \
           ${destroot}${prefix}/share/doc/${name}
   }

    post-activate {
       ui_msg ""
       ui_msg "================================================================================"
       ui_msg "If you try to use Touch ID with `sudo` command, add the below line to the top "
       ui_msg "of \"/etc/pam.d/sudo\". (see ${homepage})"
       ui_msg ""
       ui_msg "auth sufficient ${prefix}/lib/pam/pam_touchid.so reason=\"execute a command as another user\""
       ui_msg "================================================================================"
       ui_msg ""
   }

(2016-12-30 追記)reasonローカライズする方法がわからない……別に英語で出るのはかまわんのだけど、日本語のメッセージにまざってるとかっこ悪いしなあ。

2016-12-27

[][]MacPortsのPortGroup rubyをsubport対応に

per5, php, pythonなんかでは以前からひとつのPortfileで複数のインタプリタのバージョンに対応したsubportをまとめて登録できるようになっている。たとえば、perl5だとperl5.brahcnes・pythonだとpython.versionsというオプションで登録するsubportのバージョンを指示できる。

rubyではgemでみんな自分で入れるだろうし(最近のrb22-やrb23-てportはぜんぜん登録されてない)、いらないかなあと思ってとくに作ってなかったのだけど、ruby-2.4の準備中に「以前本体に入ってたruby/tkくらいは用意しておくか」と思ってちょっと作業してみたら、わりと簡単に対応できたのでコミットしといた

  • ruby.branchesという値を導入
  • これの設定があると、ruby.setup中でsubportを登録する
  • 以下の点はperl5やphpと異なる
    • 必須ではない
    • ruby.branchesの先頭の値がデフォルトのバージョンとなり、replaced_byされる。(他はdefault_branchのバージョンがデフォルトになるが、rubyは過去の互換性のためdefault_branchが1.8になっている)

もともとはruby/tkように準備していたものの、X11の問題があってお蔵入りになってしまい、使わないのもくやしいので、bundlerに入れておいた。

   % port info --subports rb-bundler
   subports: rb23-bundler, rb24-bundler

rb-bundlerというPortfileで2.3と2.4のsubportが登録されている。

これであとはport:rubyをport:ruby18に改名すれば、個人的にはやっと1.8の呪縛から脱出できる感じがするねえ。

2016-12-25

[][]MacPortsのport:ruby24を登録

例によってクリスマスに新しいバージョンがリリースされていたので対応しました。変更点などは公式のリリースアナウンスを見てください。

従来通り、MacPorts版は

  • ruby2.4, rake2.4, gem2.4などバージョンのsuffixがつく
  • port select --set ruby ruby24などselectを使うと、suffixなしのバージョンで使える

となっています。

以前からアナウンスが出ていたように、tkがruby本体のリリースに含まれなくなりましたので、variantから関連するものを外しています。OSのTk.frameworkにリンクするようにして(デフォルトでそうなるはず)gem tkを入れるのがよいでしょう。

手元の環境で試した感じだとX11版はMacだと以下の問題が起きてダメっぽい感じです。

  • Sierra: XQuartzがそもそも動かない? wishすら立ち上がらないので、ruby/tkもダメです。
  • El Capitan: ruby-2.4だとウィンドウを閉じたときにSEGVする。ruby-2.3はセーフ。

(追記)OS X 10.7以前でコンパイルできない問題/解消済み

10.7や10.6のbuildbotで”ext/-test-/memory_status/emory_status.c”のコンパイルエラーになっていた。

さすがに古い環境なので「知らんがな」という感じだったものの、twitter上で@n0kadaさんとやりとりしたりしてtrunkにr57180がはいったので、その変更をマージしたruby24-2.4.0_1として登録しました。まあ使ってる人いるかは知らないけど…

(ちなみに10.5 ppcはダメなままです。buildbotのメールが2日くらい遅れてきたので調べていません。)

2016-11-25

[]Mac App Store用のコマンドラインツール mas を登録

コマンドラインでMac App Store経由のインストール、更新などの操作ができる mas (mas-cli) というツールがある。

使い方はREADMEに書かれてる用例を見ればほとんどわかると思う。

   $ mas list                  #インストールしているストアアプリの一覧を出力
   $ mas search app-name       #ストア上のアプリを名前で検索
   $ mas install app-id-number #ストアアプリをインストール
   $ mas outdated              #更新されているアプリを表示
   $ mas upgrade               #更新されているアプリを最新にする
      :

有償のアプリを購入することはさすがにできないみたい。

個人的にはちょっと前から使っていたのだけど、アプリケーションの指定時に謎の数字(ストア上のURLに含まれるもの)で指示するところとかがどうかなー?と思って公式のリポジトリには登録してなかった。

最近読んだいくつかのMacの環境構築記事で、新しい環境にストアアプリを入れるのに使うということが書かれてて、そういうユースケースならあらかじめ数字わかってるし問題ないと納得したので公式のport登録することにした。

リポジトリ入れてbuildbotの結果ながめるまで気付かなかったのだけど、最新版はswift3で書かれてるからXcode 8以上がいるんだね。OS X 10.11上でも使えてたからあまり意識してなかった。

ということでPortfileに制限の記載を追加。

   # requires swift3
   minimum_xcodeversions {15 8}
   pre-fetch {
       if {${os.major} < 15} {
           ui_error "${name} @${version} requires OS X 10.11 or later."
           return -code error "incompatible OS X version"
       }
   }

これで、Xcode 7.xな環境だとちゃんとバージョンが足りてない旨のメッセージが出るようになる。

   --->  Extracting mas
   Error: On macOS 10.11, mas @1.3.1 requires Xcode 8 or later but you have Xcode 7.3.
   Error: See https://guide.macports.org/chunked/installing.xcode.html for download links.
   Error: org.macports.extract for port mas returned: incompatible Xcode version
   Please see the log file for port mas for details:
       /opt/local/var/macports/logs/_Users_kimuraw_work_ports_sysutils_mas/mas/main.log
   To report a bug, follow the instructions in the guide:
       http://guide.macports.org/#project.tickets
   Error: Processing of port mas failed

2016-11-19

[]MacPortsのw3mmanが動かないので直した

w3mというターミナル上で動作する偉大なブラウザがあってですね、それに付属する`w3mman`というコマンドがSEE ALSOやヘッダファイルをリンクとしてジャンプできたりとえらく便利で愛用してたんですよ。

いつのまにか実行しても空の画面しか表示されなくなっていて、「ながいこと更新されてないソフトだししゃーないな」と思ってエイリアス解除して忘れてしまっていた。さいきんふと思い出して調べてみたら、

   % perl w3mman2html.cgi
   Can't use 'defined(%hash)' (Maybe you should just omit the defined()?) at w3mman2html.cgi line 223.
   %

と、そもそもスクリプトperlのエラーで動かないということがわかった。どうもメッセージのとおり単にdefined()を外せばよいようで、@msmhrtさんからdebian版は問題ないらしいとの情報をもらって確認したりして対応した。

port:w3mはnomaintainerなので修正パッチを追加(ついでにw3mhelp.cgiも)してコミット

というわけで、port:w3m @0.5.3_6からはw3mmanが動くようになっているはずです。

2016-11-11

[]poll(2)の動作がSierraで変わってるぽい

(2016-11-12追記)この現象は10.12.2 beta2(16C41b)で直っているようです。10.12.2がリリースされたら解消するかもしれません。

ruby 2.4-preview3が出たので、test-all流して動作確認してたら、Sierraで通らないテストがあったので気付いた。

pollのman見ると、poll(2)のインターフェイスはこんななんだけど

   SYNOPSIS
        #include <poll.h>

        int
        poll(struct pollfd fds[], nfds_t nfds, int timeout);

呼び出し時に渡すnfdsの値(=fdsの数)が0のときの動作がSierraから変わっているみたいで

  • 10.11以前: timeoutだけ待つ
  • 10.12: 待たずにすぐ終了する

となっている。

curlのほうでも先月に話題になっていたよう。

Hacker Newsでは「そもそもPOSIX的にどうなん」「仕様的にセーフだったとしてもこれはダメだろ」みたいな議論をしてるっぽいがよくわからん。

Sierraのxnuのソースコードがまだopensource.apple.comにあがってないのがコードみて確認できないのがもどかしい。

この件はruby側の問題じゃないと思うのだけれど、テスト通らないのは動作確認で困るので報告しておいた。

問題のtest/fiddle/test_function.rbを元に、ruby+fiddleでpollの動作確認用のスクリプトを書いた。

   require 'fiddle/import'
   require 'tempfile'

   include Fiddle

   libc = dlopen('/usr/lib/libc.dylib')
   @func = Function.new(libc['poll'], [TYPE_VOIDP, TYPE_INT, TYPE_INT], TYPE_INT)

   def test_poll(fds, nfds)
       t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
       n1 = @func.call(fds&.to_ptr, nfds, 200)
       t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
       puts "fds=#{fds ? fds.fd : "(nil)"}, nfds=#{nfds}, slept=#{t1 - t0}, status=#{n1}"
   end

   fds = Fiddle::Importer.struct(["int fd", "short events", "short revents"]).malloc
   file = Tempfile.open('poll')
   begin
       fds.fd = file.fileno
       fds.events = 0
       fds.revents = 0

       puts RUBY_DESCRIPTION
       puts `uname -sr`.chomp
       test_poll(fds, 0) # not block on macOS Sierra
       test_poll(fds, 1)
       test_poll(nil, 0)
       test_poll(nil, 1)
   ensure
       file.unlink
   end

SierraおよびEl Capitanでの結果は次のとおり。

   # Sierra
   ruby 2.4.0preview3 (2016-11-07 trunk 56661) [x86_64-darwin16]
   Darwin 16.1.0
   fds=7, nfds=0, slept=0, status=0
   fds=7, nfds=1, slept=205, status=0
   fds=(nil), nfds=0, slept=0, status=0 # ←nfds=0では待たない
   fds=(nil), nfds=1, slept=0, status=-1

   # El Capitan
   ruby 2.4.0preview3 (2016-11-07 trunk 56661) [x86_64-darwin15]
   Darwin 15.6.0
   fds=7, nfds=0, slept=201, status=0
   fds=7, nfds=1, slept=206, status=0
   fds=(nil), nfds=0, slept=203, status=0 # ←nfds=0でも待つ
   fds=(nil), nfds=1, slept=0, status=-1
   # Sierra 10.12.2 beta2 (16C41b) INS 2016-11-12
   ruby 2.4.0preview3 (2016-11-07 trunk 56661) [x86_64-darwin16]
   Darwin 16.3.0
   fds=7, nfds=0, slept=201, status=0
   fds=7, nfds=1, slept=202, status=0
   fds=(nil), nfds=0, slept=200, status=0 # 直ってる!?
   fds=(nil), nfds=1, slept=0, status=-1

待たずに終わったときのpoll()の戻り値が0なのもちょっと嫌な感じ。

正直よく知らないのだけど、fd渡さずに単に指定した時間待たせるためにpoll(2)使うのって一般的なテクニックなんですかね?

    • とりあえずAppleバグとして報告したほうがよさそうだけど、再現コードをCで書き直すのめんどいなあ。--(次の10.12.2で直るみたい)

(2016-12-17 追記)10.12.2で解消したことを確認しました。

   # Sierra 10.12.2 (16C67) INS 2016-12-17
   ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin16]
   Darwin 16.3.0
   fds=7, nfds=0, slept=205, status=0
   fds=7, nfds=1, slept=205, status=0
   fds=(nil), nfds=0, slept=206, status=0 # <- same as 10.11 El Capitan or earlier
   fds=(nil), nfds=1, slept=0, status=-1

2016-10-26

[] MacPortsの開発リポジトリgithubへ移行(予定)

話はずいぶん前から出てたのだけれど、ようやくMacPortsの開発がgithubに移ることになったそうです。今週末の10/29-30で移行する予定らしい。

今まではmacports.org上のsvntrac上で作業してたけれど、ちょくちょくtracが落ちてたのでgithub.comのほうが稼働率高そうなのがいちばん助かるかなあ。coreはともかくportfileは単純なものだからvcsはsvnでもそんなに困らんし。

コミッタのみなさまMLでの指示(”Moving to GitHub: Status Update, Action Required”)どおり

  • (なければ)githubのアカウントを登録
  • アドレスに macports.org のメールアドレスを追加
  • macports-infra あてに"Please invite me to MacPorts on GitHub"の件名でメール送る

の手続きをとってねとのこと。てゆかさ、ふつうに開発者MLに流れてたから見逃してたよ!

2016-01-01

[][]MacPortsのport:ruby23を登録

例によってクリスマスに新しいバージョンがリリースされていたので対応しました。従来通り、MacPorts版は

  • ruby2.3, rake2.3, gem2.3などバージョンのsuffixがつく
  • port select ruby ruby23などselectを使うと、suffixなしのバージョンで使える

となっています。


Yosemite環境でのコンパイルエラー調査

portへのコミットはわりとすぐやってたんだけど、OS X 10.10 Yosemite環境のbuildbotがエラーはいてて、手元で再現できなかったりと調べてるうちに年が明けてしまった。。

buildbotのエラー

エラー箇所の抜粋。

   compiling regerror.c
   compiling regexec.c
   Stack dump:
   0.	Program arguments: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -cc1 -triple x86_64-apple-macosx10.10.0 -emit-obj -disable-free -disable-llvm-verifier -main-file-name regexec.c -mrelocation-model pic -pic-level 2 -mdisable-fp-elim -masm-verbose -munwind-tables -target-cpu core2 -target-linker-version 242.2 -dwarf-column-info -coverage-file /opt/local/var/macports/build/_opt_mports_dports_lang_ruby23/ruby23/work/ruby-2.3.0/regexec.o -resource-dir /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/6.1.0 -D _FORTIFY_SOURCE=2 -D RUBY_EXPORT -D _XOPEN_SOURCE -D _DARWIN_C_SOURCE -D _DARWIN_UNLIMITED_SELECT -D _REENTRANT -I /opt/local/include -I /opt/local/include -I . -I .ext/include/x86_64-darwin14 -I ./include -I . -I/opt/local/include -Os -fdebug-compilation-dir /opt/local/var/macports/build/_opt_mports_dports_lang_ruby23/ruby23/work/ruby-2.3.0 -ferror-limit 19 -fmessage-length 0 -fvisibility hidden -fwrapv -stack-protector 1 -mstackrealign -fblocks -fobjc-runtime=macosx-10.10.0 -fencode-extended-block-signature -fmax-type-align=16 -fno-common -fdiagnostics-show-option -vectorize-loops -vectorize-slp -o regexec.o -x c regexec.c
   1.	&lt;eof&gt; parser at end of file
   2.	Per-module optimization passes
   3.	Running pass &#39;CallGraph Pass Manager&#39; on module &#39;regexec.c&#39;.
   4.	Running pass &#39;Loop Pass Manager&#39; on function &#39;@match_at&#39;
   5.	Running pass &#39;Loop Invariant Code Motion&#39; on basic block &#39;%4037&#39;
   clang: error: unable to execute command: Segmentation fault: 11
   clang: error: clang frontend command failed due to signal (use -v to see invocation)
   Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
   Target: x86_64-apple-darwin14.5.0
   Thread model: posix
   clang: note: diagnostic msg: PLEASE submit a bug report to http://developer.apple.com/bugreporter/ and include the crash backtrace, preprocessed source, and associated run script.
   clang: note: diagnostic msg:

regexec.cのコンパイルでエラーでて失敗してる。あんま見たことないケースだなあ。手元の環境で確認したときは問題なかったし、rubyciもgreenだし。

あれこれ試して、以下の場合で再現することがわかった。

のときがアウトっぽい。なんかすごくコンパイラのバグっぽいのですけど。。。

とりあえずPortfileで上記に該当する場合は、最適化フラグを-Osから-O3に書き換えすることで対応することにした。

2015-12-20

[][]MacPortsのport:ruby22, ruby21, ruby20を更新

あたらしいバージョンがリリースされていたので対応しました。今回のバージョンはセキュリティ修正(CVE-2015-7551: Fiddle と DL における tainted 文字列使用時の脆弱性について)が含まれています。

以下自分ようのメモ。


2.2 Leopard以前にlibunwind.hがない?

2.2.3が古いOS X(10.5 Leopard以前)でコンパイルエラーがあるらしくパッチが当てられていたけれど、2.2.4ではupstreamで対応された(Bug #11591)ので削除。


2.2, 2.1 仮想マシン上のtest/ruby/test_io.rbでTypeError

先週ようやく10.11に上げたので、10.10の検証(インストールの確認とtest-all流す)はVMWare上の仮想マシンでやった。

2.2.4と2.1のtest-allでTestIOでエラーが4件出る。

     1) Error:
   TestIO#test_close_on_exec:
   TypeError: no implicit conversion from nil to integer

rubyciではOS X上の2.2/2.1はtest-allのエラーはないので、環境かMacPortsのビルドの問題かなあと考えて調べてみる。

まずはport使わずにふつーにconfigure && make。再現することを確認。2.2.3ではテストをパスすることも確認。

2.2.3と2.2.4でconfig.hを比較、上述のlibunwind.hの件だけで関係なさそう。

   --- .ext/include/x86_64-darwin14/ruby/config.h	2015-12-20 01:34:06.000000000 +0900
   +++ ../ruby-2.2.4/.ext/include/x86_64-darwin14/ruby/config.h	2015-12-20 01:07:06.000000000 +0900
   @@ -332,6 +332,7 @@
    #define SET_THREAD_NAME(name) pthread_setname_np(name)
    #define DEFINE_MCONTEXT_PTR(mc, uc) mcontext_t mc = (uc)->uc_mcontext
    #define HAVE_EXECINFO_H 1
   +#define HAVE_LIBUNWIND_H 1
    #define HAVE_BACKTRACE 1
    #define BROKEN_BACKTRACE 1
    #define DLEXT_MAXLEN 7

次にテストスクリプトを比較、RLIMITまわりの設定が追加されてるね。

   --- ../ruby-2.2.3/test/ruby/test_io.rb      2015-08-14 00:53:27.000000000 +0900
   +++ test/ruby/test_io.rb    2015-12-20 10:51:14.000000000 +0900
   @@ -1065,7 +1065,9 @@
      def ruby(*args)
        args = ['-e', '$>.write($<.read)'] if args.empty?
        ruby = EnvUtil.rubybin
   -    f = IO.popen([ruby] + args, 'r+')
   +    opts = {}
   +    opts[:rlimit_nproc] = 1024 if defined?(Process::RLIMIT_NPROC)
   +    f = IO.popen([ruby] + args, 'r+', opts)
        pid = f.pid
        yield(f)
      ensure
   @@ -1120,6 +1122,10 @@

      def test_dup_many
        ruby('-e', <<-'End') {|f|
   +      if defined?(Process::RLIMIT_NOFILE)
   +        lim = Process.getrlimit(Process::RLIMIT_NOFILE)[0]
   +        Process.setrlimit(Process::RLIMIT_NOFILE, [lim, 1024].min)
   +      end
          ok = 0
          a = []
          begin

試してみた感じだと

   opts[:rlimit_nproc] = 1024 if defined?(Process::RLIMIT_NPROC)

ここの"1024"が環境の最大値を超えてるとだめっぽい。

   # OS X 10.11.2 実機
   % ruby2.2 -e 'p Process.getrlimit(Process::RLIMIT_NPROC)'
   [709, 1064]
   # OS X 10.10.5 VMWare fusion
   % ruby2.2 -e 'p Process.getrlimit(Process::RLIMIT_NPROC)'
   [266, 532]

仮想のほうで1024を532にするとテストをパスするようになる。533だとエラーになる。

ruby本体の問題ではなさそうなのでとりあえず無視することに。ちゃんと調べたらbugs.r-l.oにレポートだそう。

(2015-12-22 追記)Bug #11852として報告して、送ったパッチもマージされた。ありがとうございます!


2.0.0 rubygems/test_gem_remote_fetcher.rbで"dh key too small"

例によってopensslのバージョンが上がったことで要求されるサイズが増えたみたい。

test_gem_remote_fetcher.rbの`OpenSSL::PKey::DH.new(128)`を256にしてみたけど、test-allで流すとエラーのぜんぶは解消できなくってめんどうになってあきらめた。

2015-09-26

[] RubyCocoa 今日のコミット 2015-09-26

以前に見つかっていたバグがよくわからないけど直った。ひさしぶりにぜんぶテストが通るようになったぜ。。

  • NSArrayController#arragendObjects#eachするとruby-2.xだけNSGenericExceptionが起きるのを修正(beca6d4)

rubycocoa issue#2に書いたとおりなんだけど、

   require 'osx/cocoa'
   ctl = OSX::NSArrayController.alloc.init
   content = [1,2,3,4,5].map {|i| i.to_s}
   ctl.addObjects(content)

   p ctl.arrangedObjects
   # expects ["1", "2", "3", "4", "5"]
   # but ruby-2.0 fail with the following error:
   # /Library/Frameworks/RubyCocoa.framework/Resources/ruby/osx/objc/oc_wrapper.r
   # b:55:in `ocm_send': NSGenericException - *** Collection <_NSControllerArrayP
   # roxy: 0x7f8e04a66b00> was mutated while being enumerated. (OSX::OCException)

と、ruby-2.x上でObjective-C側の例外が起きてしまう。もちろんふつうのNSArrayは問題ない。

NSArray#eachは

   iter = self.objectEnumerator
   while obj = iter.nextObject
     yield obj
   end

といった感じで実装されていてenumeraterまわしてるだけなので、コレクションのオブジェクトを変更をしているとは思えない。そもそもruby-1.8のときは問題ないしねえ。。

あれこれ調べてたんだけどけっきょく原因はわからなくて、「動かないよりはいいか」とコレクションかイテレータのクラスかなんかで判定してダメそうな場合は

   self.count do |i|
     yield self[i - 1]
   end

みたいにぐるぐるすればよいかと思って試していたら、iter.classNameを呼び出すと元々の実装でも動くようになったことに気付いた。なんだかわからんけど、Objective-CのメソッドをnextObejctの前に呼び出してやればよさそうなことがわかったのでとりあえずそれで対応した。

2015-09-19

[] OS Xの"DYLD_*"環境変数

先日のCocoa勉強会で話してきたので整理しとく。


まとめ

  • dyldはdarwin(OS X, iOS)のld(1)。
  • ldのLD_*環境変数と同様に、DYLD_*環境変数で動作を制御できる。
  • OS X 10.11では、DYLD_*環境変数が無効にされるケースがある見込みだぜ。

dyldと"DYLD_"ではじまる環境変数

dyldはOS X/iOSのダイナミックリンカで、ライブラリフレームワークをロードするプログラムです。man DYLD(1)にあるように、"DYLD_"ではじまる環境変数で動作を変更することができます。UnixLinuxLDでいうところのLD_LIBRARY_PATHやLD_PRELOADの類ですね。

すっごく雑に分けると、これらの環境変数は

  • ライブラリの探索方法を指示
    • DYLD_*_PATH: 探す場所を指示する。
    • DYLD_IMAGE_SUFFIX: デバッグ用などに用意したライブラリのsuffixを指示する。
  • 実行中の情報を表示
    • DYLD_PRINT_*: ロードしたものや実行環境の情報などを標準エラー出力に表示する。
  • その他
    • DYLD_INSERT_LIBRARIES: LD_PRELOAD
    • DYLD_FORCE_FLAT_NAMESPACE, DYLD_BIND_AT_LAUNCH: リンカの解決方法を指定する。

という感じになります。

RubyCocoaの開発ではテストスクリプトの実行時に

  • DYLD_FRAMEWORK_PATH: 開発中のRubyCocoa.frameworkの場所を指示する。
  • DYLD_PRINT_LIBRARIES_POST_LAUNCH: テストスクリプトが正しく開発中のRubyCocoa.frameworkやrubycocoa.bundleをロードしたかを確認する。

の2つを使っています。プログラム的には次のようなrubyスクリプトです。

   ENV['DYLD_FRAMEWORK_PATH'] = path_to_devel_framework
   ENV['DYLD_PRINT_LIBRARIES_POST_LAUNCH'] = '1'
   libs = Open3.popen3("ruby -rosx/cocoa -e ''") do |stdin, stdout, stderr|
     # read stderr
   end
   # test loaded rubycocoa framework/bundle paths from stderr

DYLD_PRINT_LIBRARIESとDYLD_PRINT_LIBRARIES_POST_LAUNCHのちがいは、プロセスの起動時にロードしたものを表示するか否かです。

   % DYLD_PRINT_LIBRARIES=YES ruby2.2 -e 'require "zlib"'
   dyld: loaded: /opt/local/bin/ruby2.2
   dyld: loaded: /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
   dyld: loaded: /opt/local/lib/libruby.2.2.0.dylib
   dyld: loaded: /usr/lib/libSystem.B.dylib
     :
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/encdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/trans/transdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/thread.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/date_core.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/zlib.bundle
   dyld: loaded: /opt/local/lib/libz.1.dylib
   % DYLD_PRINT_LIBRARIES_POST_LAUNCH=YES ruby2.2 -e 'require "zlib"'
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/encdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/trans/transdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/thread.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/zlib.bundle
   dyld: loaded: /opt/local/lib/libz.1.dylib
   %

OS X 10.11での制限

そろそろ10.11がリリースされそう(当時beta 8)なのでRubyCocoaの検証をしていたところ、上記のテストスクリプトのフレームワークのパスの検証が通らないことに気付きました。どうもDYLD_FRAMEWORK_PATH環境変数が機能していないようです。

試してみると、

   % uname -r
   15.0.0 # 10.11 GM
   % ruby -e "system({'DYLD_AAA' => 'AAA', 'AAA_DYLD_BBB' => 'BBB'}, \
                 '/usr/bin/printenv | grep DYLD')"
   AAA_DYLD_BBB=BBB
   %

と、"DYLD_"ではじまる環境変数が子プロセスに反映されないようになっているようです。betaの無印のときはこんなことなかったと思うんだけどなあ。もちろんOS X 10.10では"DYLD_AAA"も出力されます。

仕方ないので、次のような一時ファイルのシェルスクリプトを生成して実行することを考えています。

   # temp shell script for testing with DYLD_ environments
   DYLD_FRAMEWORK_PATH=path ${RUBY} -rosx/cocoa -e ''

さすがにこれで解消するのですが、なんかすごく不毛な気持ちになります。もうちょっとスマートに書けるような…

よい方法があれば教えてください!だいぶ切実です。

(2015-09-19追記: ついったで @n0kada さんに`env`コマンドでいけることを教えてもらいました。ありがとうございます!)

また、WWDC 2015のセッション706:”Security and Your Apps”(ASCIIwwdc)で説明されているように、保護される領域(/Systemや/usr/binなど)にあるバイナリではDYLD_*環境変数は問答無用で無視されるようになっています。

例を示すと、

   % uname -r
   15.0.0 # 10.11 GM
   % cat dyld_env.rb
   # print DYLD_* environments
   ENV.each_pair do |k, v|
       puts "#{k}=#{v}" if k =~ /DYLD/
   end
   % DYLD_AAA=AAA AAA_DYLD_BBB=BBB /usr/bin/ruby dyld_env.rb
   AAA_DYLD_BBB=BBB
   % DYLD_AAA=AAA AAA_DYLD_BBB=BBB /opt/local/bin/ruby2.2 dyld_env.rb
   dyld: warning, unknown environment variable: DYLD_AAA
   DYLD_AAA=AAA
   AAA_DYLD_BBB=BBB
   %

のように、システム付属のrubyにはDYLD_ではじまる環境変数が渡されないようになっています。


感想

セキュリティ観点としてはいままでがざるざるだったこともあり、制限は仕方ないし正しい方向だとは思います。だけど、プログラム書く人間としてはめんどくさいなあという気持ちも隠せません。

人によってはぜんぜん影響ないかもしれませんが、思わぬワナにはまるかもなのでご注意を。

[] RubyCocoa 今日のコミット 2015-09-19

10.11GM上で10.10と同じようにテスト通る(既知の1F)まで持ってきた。

作業はビルドまわりとテストスクリプト側の修正でコアの方は手をいれてない。

  • 10.11ではRubyCocoa.frameworkは@excecutable_pathなしをデフォルトに。 (ceb7962)
  • 上述のDYLD_環境変数の件に対応。(c0bd8ae, 095de97)
  • CoreFoundationのretain countのテストが通らないのを修正。てか元のテストの意図がびみょーだったので消した。(56647a4)
  • KeyValueObvervingのテストで観測対象の値を初期化するように。一部のケースでKVO発火時に現在の値を取得するようになったぽい。実際には@value1だけ初期値が必要なんだけどそれも気持ち悪いのでひととおり入れてくようにした。(20133e6)

最初の@excecutable_pathは、OS添付のrubyコマンドから使うときに問題起きないようにするために変更した。

`ruby`から実行するとき、require 'osx/cocoa'でrubycocoa.bundleが読み込まれ、そこからリンクしているRubyCocoa.frameworkがロードされるという順になるのだけれど、このときバイナリに記録されているパスが@excecutable_pathを含んでいると"unsafe use of @executable_path"とのメッセージが表示されロードが中止されてしまう。ということで、/Library/Frameworksがデフォルトになるようにした。

その影響で、今後リリースするだろうパッケージからインストールしたRubyCocoa.frameworkをアプリケーションに同梱するときは単に.frameworkを.appにほうりこむだけではダメで、install_name_toolでライブラリのパスを変更する必要がある。standaloneify.rbを使っている場合はスクリプト側で対処する予定なので、従来どおり使えば問題ない。

現在の構成もわかりづらいしこういう問題もあるので、将来的には

  • rubyコマンドから使う場合はrubycocoa.bundleだけをロードする。
  • .appからはRubyCocoa.frameworkをロードする。

というように住み分けするようにしたほうがよいと考えている。前者だけgemで配布できるようになるし。

2015-08-20

[][]MacPortsのport:ruby22, ruby21, ruby20を更新

あたらしいバージョンがリリースされていたので対応しました。

RubyGemsCVE-2015-3900のセキュリティ修正が含まれてたりします。詳しくは公式のリリースアナウンスをみてください。

また、以前に書いたように、port:ruby22にjemallocバリアントを追加しました。