2016/04/11


[D] Windowsはターミナルがダメだから使えないってのは過去の話?

基本的にはいい感じに見えますが、いくつか問題は発覚してます。

http://blog.drikin.com/2015/01/windows-2.html
僕は Cygwin よりも msys2 が好きです

理由は最後の方に書きます。

もちろん msys2 を POSIX 環境としても使いますが、一番の目的は

cmd.exe から Windows ネイティブなアプリケーションを使いたいから

例えば ag (the silver searcher) を Windows から使いたいなら、Windows native でビルドすべきだと考えます。

Cygwin がなぜ嫌われ始めたのかを皆もう一度考えるべきだと思います。Cygwin が不安定な理由は Windows 上に無理くり POSIX をエミュレーションしているのが原因。これに尽きます。Windows は UNIX では無いのだから Windows には出来ない事はたくさんあるのです。

drikin さんの記事でも書かれていますが、Cygwin (および msys2) には fork があり、UNIX を模擬する事が出来ます。しかし Windows で fork を実現する為にオーバーワークとも言える事をやっています。fork は現在実行しているプロセスのコピーを作るシステムコールです。Cygwin の fork はこれを実現する為に、POSIX で提供されるファイルハンドルの管理やメモリのアロケーションを全て Cygwin 配下で行い、fork の実行と共にそのメモリを複製し、スタックを疑似的に再現した上でジャンプ命令を実行しています。なのでたかがmallocなのに非常に実行が遅くなります。また完全にエミュレーション出来ていません。例えばコピーオンライトは動きません。つまり完全な fork ではありません。

例えば Windows はファイルハンドルを握られた状態だとそのファイルを削除する事は出来ません(FILE_SHARE_DELETE を指定して CreateFile した場合は別です)。よく UNIX 向けのソフトウェアでは後処理としてテンポラリディレクトリを削除するといった物がありますが、そのディレクトリ内のファイルハンドルを握ったままだとディレクトリの削除に失敗します。

もう一つ。Windows にはファイルの実行権限(許可という意味ではなく)がありません。ファイルが実行できるかどうかは拡張子で決まります。Cygwin ではコマンドを実行すると、手当たり次第にパスを検索し、拡張子が無ければファイルの中に shebang 行が無いかを調べて実行できそうならそこで初めてファイルが実行されます。この様にいろいろな違いがあるのです。必然的に Cygwin パッチが必要となります。

Cygwin が出始めた当初は、各 UNIX 向け OSS メンテナも「Windows ユーザにも自分達が作ったプロダクトを使って貰える」と喜び、こぞって Cygwin 特有のパッチを取り込んでくれました。しかし実際にリリースしてみると問題が出るわ出るわ。そしてなぜその問題が発生したのかは、パッチを書いた人じゃないと分からないという悪循環。最悪のケースだと誰も原因が分からない、もしくは解決の方法が見つからないと放置されました。

次第に Cygwin は OSS プロダクトから嫌われ始め、Cygwin 向けパッチを送ると「また Cygwin か」と言われる事もチラホラありました。

そうなんです。Windows 上で完全な POSIX エミュレーションは VM 等を使わない限り無理なのです。

Big Sky :: Windowsへの移植も視野にいれたプログラムを書くなら読んでおいて欲しい事

絶対パスの先頭に / が来る事を期待してはいけない しかしながら絶対パスの先頭にドライブレターが来る事を期待してはいけない UNCパスのホスト名やシェア名はディレクトリではないのでファイルシステムAP...

http://mattn.kaoriya.net/software/20120507131015.htm

以前書いた記事を読んで貰えると Windows で UNIX を模倣するのは到底ムリゲーなのがよく分かりますね。先日 Microsoft から発表された Bash On Windows もおそらくこういった問題があると思います。(追記: 例えばファイルシステムが NTFS 上なので ext4 と同等のファイル属性が持てません。)

だからといって有益なソフトウェアを Windows 上で使えないのは勿体ない。幾らか機能制限があったとしても使いたいものです。なので僕は Windows 向けのパッチを書くときは殆ど、Windows ネイティブな環境で動作させる為のパッチしか書きません。(中には msys2 専用の物もありますが)

例えば上記で説明した ag。ag を Windows ポートしたのは僕ですが、Cygwin のシェルから使わせる為のパッチではなく cmd.exe から使わせる為のパッチとして pull-request しました。

Porting to win32 by mattn · Pull Request #158 · ggreer/the_silver_searcher · GitHub
https://github.com/ggreer/the_silver_searcher/pull/158

他の物を見ても、おおよそ Windows ネイティブ環境の為の pull-request です。

完全に UNIX 環境じゃないと実行できない物は、VM を使えばいいんです。無理して Cygwin を使う必要は無いんです。そして Windows ユーザならば cmd.exe を使うべきなんです。(ここちょっと宗教くさいですが)

しかし cmd.exe で生きるには多くの苦行が待ち伏せています。実は Windows は環境変数に指定できる文字列の長さが4096文字しか設定できません。これはシステム環境変数もユーザ環境変数も足した状態の長さになります。通常インストールした後の状態だと600文字から700文字くらいなのですが、必要な物を色々インストールすると勝手に PATH 環境変数が足されて行きます。気付いたら2000文字や3000文字になっているなんてことはザラです。その上で各種コンパイラやスクリプト言語の処理系をインストールするとどうでしょう。4096文字なんて意外と一瞬で使い切ってしまいます。ただでさえ、Windows は UNIX の様に実行モジュールを一か所に集める習慣が無いのですから、膨れ上がって当然です。さらには mysql のライブラリをリンクさせる為に mysql-config へのパスを通す必要がある、であったりそのパスを通したおかげで zlib1.dll のシンボルの異なるバージョンの dll に先にパスが通ってしまい、他のツール群が動かなくなったなんて事、日常茶飯事です。時には気付かず4096文字を超えてしまっていて、いくらインストールし直しても動かない、であったり誤動作するなんて事もあります。

なので僕は基本的に Windows 上では不必要に PATH を通しません。例えば Java の開発をする場合、ant、maven、その他の開発に必要なツール群はオフィシャルサイトからダウンロードし、特定のフォルダに解凍した上で PATH を通して使っておられると思いますが、僕は Windows のシステム設定で PATH を通しません。

僕は基本的に、そのツールを使いたい時に初めて PATH を通します。そしてそれ専用のバッチファイルを用意しています。例えばコマンドラインから groovy を使いたい場合、groovy のオフィシャルサイトからダウンロードして解凍したディレクトリには PATH は通さず、以下のバッチファイルを書きます。

@echo off
set PATH=c:\Program Files\groovy-2.4.5\bin;%PATH%

コマンドインから groovy を使った開発を行いたい場合は、まずこのバッチファイル(groovyenv.bat という名前にしています)を実行します。その後で、groovy コマンドを使ったスクリプトを書いたり実行したりします。手間と思うかもしれませんが、これが Windows の誤動作を防ぐ一番簡単な方法なのです。システムのプロパティから設定する環境変数 PATH は、自分が思っている以上に他のプログラムに影響を与えてしまいます。先に述べた様に zlib1.dll などは色々なプロダクトで参照されており、dll に含まれるシンボルが1つ違っただけで Windows では実行時エラーが起きてしまいます。

Fuck DLL Hells

僕が上記で Cygwin よりも msys2 が好きだと言ったのは、msys2 で提供される mingw64(ネイティブ) 環境は、実行モジュールが1か所 C:\msys64\mingw64\bin に置かれており、この環境変数の問題も起きえないという所にあります。さらに適度な頻度でパッケージも更新されるので使っていても安心できるという点もあります。

どうしても groovyenv.bat の様なバッチファイルを叩きたくない場合の解決方法をご紹介しておきます。

それは bash でいう所の .bashrc を書く事です。Windows の cmd.exe には、起動時に自動実行されるコマンドを設定する為のレジストリが存在します。

レジストリエディタを実行し、以下のキーを開きます。

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Command Processor
そこに「AutoRun」という名前の文字列値を作り、以下の値を設定します。 %USERPROFILE%\init.cmd
init.cmd

そしてお好きなテキストエディタで %USERPROFILE% (僕だと C:\Users\mattn)に init.cmd というファイルを作り、以下の様に書きます。

@echo off

doskey ls=ls --color=auto --show-control-chars -N $*
doskey grep=grep --color=auto $*
doskey rm=rm -i $*
doskey cp=cp -i $*
doskey mv=mv -i $*
doskey vi=vim $*
doskey find=c:/msys64/usr/bin/find.exe $*
doskey ag=ag --nocolor $*

if "%CMD_INIT_SCRIPT_LOADED%" neq "" goto :eof
set CMD_INIT_SCRIPT_LOADED=1

set GIT_EDITOR=c:/msys64/usr/bin/vim.exe
set SBCL_HOME=C:\Program Files\Steel Bank Common Lisp\1.3.3
set PATH=%SBCL_HOME%;%PATH%;c:/msys64/usr/local/bin;c:\dev\instantclient_12_1
set ORACLE_HOME=c:\dev\instantclient_12_1

cls

ここで注意して欲しいのは、CMD_INIT_SCRIPT_LOADED という環境変数を使ってガードしている部分です。cmd.exe は cmd.exe から start コマンドを使って新しい cmd.exe を起動した場合、環境変数は引き継がれるのですが doskey は引き継がれません。なので doskey のみ毎回実行される必要があるのです。また doskey を使う事で、cp や rm 等の上書き削除確認や、無理する事無く find の動作を UNIX 風に書き換える事が出来るのです。ls で日本語も出ますよ。しかもシステムのプロパティから PATH を書き換えずに実現しているので、他のプログラムに与える影響が小さく、かつ設定したレジストリの項目がユーザ専用なので同じ PC を使う他のユーザにも影響を与えません。

僕も始めは「レジストリを弄るのか...」と思っていましたが、これが一番 dll 問題を回避できているし、使い勝手も良いのです。

ちなみに msys2 のシェルも使うと書きましたが、その場合は逆に native 側への alias を作っています。

alias ag='/c/msys64/mingw64/bin/ag.exe'
alias vagrant='winpty /c/dev/vagrant/bin/vagrant'

winpty を使えばコンソールネイティブな物も実行できます。

Windows のコマンドラインでうまく設定できずに悩んでいる方、一度この方法を試してみられてはどうでしょうか?

追記: 幾らか意図が伝わっていない部分があるので説明を足すと、僕は Cygwin がサブシステムを使わない、DLL で実装されたアプリケーションである事を前提に話していて、もし Cygwin が POSIX サブシステムで実装されていたら違う意見を持っていたと思います。言いたいのはサブシステムを使わないアプリケーションである以上、完全な UNIX にはなれないのだから「期待してはいけない」という事です。

Posted at by



2016/04/07


ちょっと使えるかも(?)しれない、正規表現 - Qiita

ただ、get と set の後に続く文字がキャメルケースになってません。(もう少し頑張ればなんとかなるかも?)

http://qiita.com/hirokapi/items/d3c8193ed9d2751e2751

Vim では置換文字列 \1 等の前に \u を付ける事で対象文字列を大文字に変換できます。詳しくは :help sub-replace-special を参照して下さい。ちなみにリンク先の例だと

%s/^\(.\)\(.*\)$/    private String &;\r\r    public String get\u\1\2() {\r        return &;\r    }\r\r    public void set\u\1\2(String &) {\r        this.& = &;\r    }\r/g

この様になります。先頭文字と後続文字を別のサブマッチに分け、\1 のみを大文字にします。若干間違いあるのでコメント欄参照ください。

ちなみに、僕の場合はこういった変換はマクロを使う事が多いです。Windows10 だと標準で入ってた XBox に録画機能があったので試してみました。

How to make java setter and getter with vim macro from mattn on Vimeo.

vimeo で字幕を入れてあるので字幕が見たい人は動画右下をクリックして字幕表示を切り替えて下さい。

Posted at by



2016/04/05


だいぶ時間が掛かった様ですが、ようやく buildmode=c-archive が Windows でも使える様になりました。

cmd/go: -buildmode=c-archive should work on windows · Issue #13494 · golang/go · GitHub

32-bit is also important to me. I'd like to help. I'm seeing those same link errors when I enable it...

https://github.com/golang/go/issues/13494

まだ buildmode=c-shared (いわゆる dll) はビルド出来ないけど、ひとまずC言語から golang のライブラリをリンクして動かせる様になりました。例えばこんな事が出来ます。

package main

import "C"

import (
    "fmt"
    "github.com/mattn/go-haiku"
)

var (
    q = make(chan string)
)

func init() {
    go func() {
        for {
            fmt.Println(<-q)
        }
    }()
}

//export PrintHello
func PrintHello(p *C.char) {
    q <- C.GoString(p)
}

//export IsHaiku
func IsHaiku(p *C.char) C.int {
    var ret C.int
    if haiku.Match(C.GoString(p), []int{575}) {
        ret = 1
    }
    return ret
}

//export PrintLine
func PrintLine(p *C.char) {
    fmt.Println(C.GoString(p))
}

func main() {
}

この様なソースコード lib.go を用意し以下の手順で lib.a を作ります。

go build -buildmode=c-archive lib.go

そしてC言語からは以下の様に呼び出します。

#include <stdio.h>

int
main(int argc, char* argv[]) {
  extern void PrintHello(char*);
  PrintHello("hello");
  PrintHello("hello");
  PrintHello("hello");
  PrintHello("hello");
  PrintHello("hello");
  Sleep(3000);
  return 0;
}

ビルドは以下の様になります。

gcc -o example.exe example.c lib.a -lws2_32 -lntdll

goroutine と channel を使っているので PrintHello の呼び出しは非同期に画面出力されます。golang では簡単に扱えるけどC言語だと若干手間だなと思える様な処理もリンクするだけで使える様になります。以下は ikawaha さんの kagome を使って俳句を扱えるライブラリ go-haiku をC言語から呼び出しています。

#include <stdio.h>

int
main(int argc, char* argv[]) {
  extern void PrintLine(char*);
  extern int IsHaiku(char*);

  char* s = "古池や蛙飛び込む水の音";

  PrintLine(s);
  if (IsHaiku(s)) {
    PrintLine("それ575じゃん");
  }
  return 0;
}
古池や蛙飛び込む水の音
それ575じゃん

めちゃめちゃ簡単ですね。

Posted at by