きりかノート 3冊め

おあそびプログラミング

RubyCocoaをふつーのgemにしたい

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

-現状の課題

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

目標はこんなところ。

作業の流れ(ここまで)

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

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

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

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

今後の予定

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

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

新しいmacOSのbetaでてたので試してみた。案の定いろいろ問題あったんだけど、それ以前に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?

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の次のリリースで直ってると思う。

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

例外のテストがメッセージを検証するというものなので、日本語環境で実行すると失敗する。
手元では直してみたけれど、この方法でよいかが自信ないのでプルリクエスト出すのは保留してる。

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をローカライズする方法がわからない……別に英語で出るのはかまわんのだけど、日本語のメッセージにまざってるとかっこ悪いしなあ。

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の呪縛から脱出できる感じがするねえ。

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日くらい遅れてきたので調べていません。)

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

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が動くようになっているはずです。

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

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

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

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

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

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

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