2015年11月29日日曜日

[haskell][persistent][sqlite] Persistentパッケージ利用時にテーブルにインデックスを生成する方法

PersistentパッケージにはMigration機能が備わっており、自動的にテーブルを生成してくれます。スキーマ変更を行った際にも、変換が可能な限りテーブル内のレコードを保持したまま新しいスキーマに変換してくれます(Migration機能については過去のエントリでまとめています)。

自分が利用する上で、インデックスやトリガーを生成する手順が紹介されておらず困っていたのですが、rawExecuteという関数を用いることで自由にDDLを発行できることがわかりました。以下その手順とサンプルを紹介しておきます。

サンプルコード:

以下は、personテーブルのnameカラムにインデックスをs生成するサンプルです。runMigration実行直後に、runExecuteを実行することでインデックスを生成しています。このサンプルではインデックスを生成しているだけですが、同じ手順でトリガーの生成(CREATE TRIGGER)も可能です。
{-# LANGUAGE EmptyDataDecls             #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE TypeFamilies               #-}
import Control.Monad.IO.Class  (liftIO)
import Database.Persist         -- persistentパッケージ
import Database.Persist.Sqlite  -- persistent-sqliteパッケージ
import Database.Persist.TH      -- persistent-templateパッケージ

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
    name String
    age Int Maybe
    deriving Show
|]

main :: IO ()
main = runSqlite "test.db" $ do
    -- personテーブル生成
    runMigration migrateAll
    -- nameカラムにindexを生成する。placeholderがないため第2引数は[]でよい。
    -- 2回目以降、すでにインデックスが存在している場合のためにIF NOE EXISTSが必要。
    rawExecute "CREATE INDEX IF NOT EXISTS idx_name_on_person ON person(name);" [] -- here!

    michaelId <- insert $ Person "Michael" $ Just 26
    michael <- get michaelId
    liftIO $ print michael
runExecuteの第一引数には実行するSQLを記載します。既にインデックスが生成済みの場合には何も処理を行わないよう"IF NOT EXISTS"を指定しています。第二引数にはplaceholderの値を指定しますがこの例ではplaceholderは利用していないため"[]"を渡しています。
"IF NOT EXISTS"を指定しないと既に存在しているインデックスを再度生成しようとして、以下のようなエラーが報告されます。
*** Exception: SQLite3 returned ErrorError while attempting to perform prepare "CREATE INDEX idx_name_on_person ON person(name);": index idx_name_on_person already exists

補足:

Yesod BookのPersistentの解説には、rawQueryの利用手順が紹介されています。rawQueryとrawExecuteの型はそれぞれ以下のようになっています。
rawQueryが実行結果を配列で返すのに対し、rawExecuteの結果はunit(空)となっています。このためDDL (Data Definition Language)実行時にはrawExecuteを利用するのがよいと思われます。
さらにrawSqlという関数もあるようですが、こちらの使い分けはよく分からず…。ご存じの方是非ご教授ください。m(_ _)m
(2015/12/16:追記)rawQueryはData.Conduit.Source型で結果を返します。これは結果が巨大であってもストリーム処理できることを意味しています。これに対しrawSqlは単純にリストで結果を返すという違いがあります。

参考:

2015年11月8日日曜日

[haskell][stack] stack exec ghciで”Couldn't match expected type"エラーが発生する問題の対処

先日、haskellのパッケージ管理をcabalからstackに移行して「便利〜!」と感動していたところなのですが、stach exec ghciでソースをロードしようとすると"Couldn't match expected type: xxxxx"とエラーが発生する問題に遭遇しました。
ネットの情報を参考に解決することができたのでその手順をまとめておきます。

問題:

stack buildは成功するにもかかわらず、stack exec ghci xxx.hs(xxx.hsはbuild対象のファイル)がエラーになる。
stack exec ghci実行時のエラーログ:
% stack exec ghci FileToVec.hs
GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling FileToVec        ( FileToVec.hs, interpreted )

FileToVec.hs:42:18:
    Couldn't match expected type ‘V.Vector a’
                with actual type ‘vector-0.10.12.3:Data.Vector.Vector a0’
    NB: ‘V.Vector’
          is defined in ‘Data.Vector’ in package ‘vector-0.11.0.0’
        ‘vector-0.10.12.3:Data.Vector.Vector’
          is defined in ‘Data.Vector’ in package ‘vector-0.10.12.3’
    Relevant bindings include
      v :: vector-0.10.12.3:Data.Vector.Vector a0
        (bound at FileToVec.hs:40:15)
      file_to_vec :: FilePath -> IO (V.Vector a)
        (bound at FileToVec.hs:33:1)
    In the first argument of ‘return’, namely ‘v’
    In a stmt of a 'do' block: return v
Failed, modules loaded: none.
Leaving GHCi.
この環境でのstack buildは以下のようなログで成功しています。
% stack build
csv2db-0.1.0.0: build
Preprocessing executable 'csv2db' for csv2db-0.1.0.0...
[2 of 4] Compiling FileToVec        ( FileToVec.hs, .stack-work/dist/x86_64-osx/Cabal-1.22.4.0/build/csv2db/csv2db-tmp/FileToVec.o )
[3 of 4] Compiling DbRecord         ( DbRecord.hs, .stack-work/dist/x86_64-osx/Cabal-1.22.4.0/build/csv2db/csv2db-tmp/DbRecord.o ) [TH]
Linking .stack-work/dist/x86_64-osx/Cabal-1.22.4.0/build/csv2db/csv2db ...
    DbRecord
    FileToVec
    MyArgs
csv2db-0.1.0.0: install
Installing executable(s) in XXXX

解決方法:

以下の2つの方法があります。
  1. execコマンドを用いず、stack ghciで起動する
  2. execコマンドを利用しなければロードすることができます。普通はこちらの手順を実行するのが正しいはずです。が、ファイル指定で起動できないのでプロンプトから:loadコマンドを実行する必要があります。
    % stack ghci
    Using main module: Package `csv2db' component exe:csv2db with main-is file: /Users/kurokawa/git/work_haskell/csv2db/Main.hs
    Configuring GHCi with the following packages: csv2db
    GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
    [1 of 4] Compiling FileToVec        ( FileToVec.hs, interpreted )
    [2 of 4] Compiling MyArgs           ( MyArgs.hs, interpreted )
    [3 of 4] Compiling DbRecord         ( DbRecord.hs, interpreted )
    [4 of 4] Compiling Main             ( XXXX/Main.hs, interpreted )
    Ok, modules loaded: MyArgs, DbRecord, Main, FileToVec.
    *Main> :load FileToVec
    [1 of 1] Compiling FileToVec        ( FileToVec.hs, interpreted )
    Ok, modules loaded: FileToVec.
    *FileToVec>
    
    stack ghci実行時にソースファイルを引数で指定した場合、以下のようなエラーになるのでご注意を。
    % stack ghci FileToVec.hs
    Error parsing targets: Directory not found: FileToVec.hs
    
  3. cabalを直実行して必要パッケージをインストールする
  4. stackを利用しないでcabal installで依存パッケージをインストールし、ローカルのxxx.hsがコンパイルできる状態にし、stackを利用しないでcabal exec ghci xxx.hsを実行する。成功時のログ:
    % cabal install vector
     ... snip...
    % cabal exec ghci FileToVec.hs
    GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
    [1 of 1] Compiling FileToVec        ( FileToVec.hs, interpreted )
    Ok, modules loaded: FileToVec.
    *FileToVec>
    

エラー原因(の推測):

stack build実行時に起動されるcabalが参照するパッケージリストと、stack exec ghciで起動されるghciが参照するパッケージリストが、異なっているのが原因だと思われます。
stack build実行時には、stackが内部で管理している(?)パッケージリストを参照しているようですが、stack exec ghciで起動されたghciはcabalを直接起動したときに参照されるパッケージリストを参照し、バージョンのズレが生じてエラーになります。

参考:

2015年11月3日火曜日

[cygwin] cygwinのシェル起動時にPATHの先頭に/usr/binと/usr/local/binが勝手に追加されないようにする

cygwinのデフォルトの設定では、シェル起動時に以下の2つのディレクトリが自動的にPATHの先頭に追加されます。
  • /usr/local/bin
  • /usr/bin

cygwinでインストールされているコマンドと同名の別コマンドを優先して起動したい場合には、この設定が邪魔になります。
これを無効にするには以下の方法があります。お好みでどちらかを選択してください。
  1. /etc/profileもしくは/etc/csh.loginの該当処理をコメントアウトする(bash / tcsh)
    • cygwinがPATHを上書きしているのは、/etc/profile(bashの場合)と/etc/csh.login(tcshの場合)です。これらのスクリプトを編集することで、/usr/binと/usr/local/binが勝手に追加されないようにできます。
  2. ORIGINAL_PATHでPATHを上書きする(bashのみ)
    • シェルにbashを利用している場合は、cygwinがPATHを上書き設定する際、環境変数ORIGINAL_PATHにオリジナルの変数を保存してくれています。これを利用してPATHに上書きすれば、/usr/binと/usr/local/binを含まない設定にすることができます。

具体的な修正内容:

  1. /etc/profileもしくは/etc/csh.loginの該当処理をコメントアウトする
    • tcshの場合は/etc/csh.loginの7行目をコメントアウトする
    • #set path=( /usr/local/bin /usr/bin /bin $path:q )    # <= ここ
      
      
    • bashの場合は/etc/profileの37行目をコメントアウトする
    •   : ${ORIGINAL_PATH=${PATH}}
        if [ ${CYGWIN_NOWINPATH-addwinpath} = "addwinpath" ] ; then
      #    PATH="/usr/local/bin:/usr/bin${PATH:+:${PATH}}"  # <= ここ
        else
          PATH="/usr/local/bin:/usr/bin"
        fi
      
      
  2. ORIGINAL_PATHでPATHを上書きする
    • tcshではこの方法では対応できません
    • bashの場合は~/.bashrcに以下の記述を追加する
    • export PATH=$ORIGINAL_PATH
      

参考:

2015年9月6日日曜日

[ssh] OpenSSHのアップデートでssh-agentがパスワードを覚えてくれなくなった問題への対処

ssh-agent/ssh-addを利用してsshを用いたサーバーへのログイン時のパスワード入力を省略している方、OpenSSHのアップデートにより、毎回パスワード入力を求められるようになった場合は以下の設定を疑ってみてください。

問題の症状:

OpenSSHパッケージを最新版にアップデートすると発生するようになった問題です。ssh-agentを起動してssh-addで鍵を正しく登録しているにも関わらず、sshコマンドを実行する度に"password:"というプロンプトが表示されてパスワードの入力を求められてしまいます。

環境:

問題に遭遇&解決した私のcygwin環境では以下のバージョンで問題が発生することを確認しました。
  • OpenSSH_7.0p1, OpenSSL 1.0.2d 9 Jul 2015
  • OpenSSH_7.1p1, OpenSSL 1.0.2d 9 Jul 2015

以下のバージョンでは問題は発生していませんでした。
  • OpenSSH_6.9p1, OpenSSL 1.0.2d 9 Jul 2015

原因と解決方法:

OpenSSHがデフォルトでサポートするkey typeに変更があったのが原因です。私の場合は鍵がDSAであったため、"ssh-dss"というkey typeをサポートするように設定することで、以前通り正しくパスワードを覚えてくれるようになりました。
具体的には~/.ssh/config(もしくは/etc/ssh_config)に以下の記述を追加すれば、デフォルトではサポートされていないキータイプがサポートされます。
PubkeyAcceptedKeyTypes +ssh-dss

自分の鍵のタイプは以下のコマンドで確認可能です。
% ssh-add -l
1024 xx:yy:8e:4f:72:2c:a4:b9:ad:ce:e7:80:a3:5d:e6:0f /home/xxxxx/.ssh/id_dsa (DSA)

cygwinのMLに質問を投げたところGeorgeさんという親切な方がアドバイスがとどき、教わった解決方法です。感謝。

関連情報:

1024bitの鍵は安全ではないので、2048bit以上の鍵を使うのが正しい解決方法だよ、という指摘をいただきました。DSAは1024bitまでしか扱えないようですが、RSAなど他のタイプで2048bit以上の鍵を作ることのがよいようです。RSAの2048bitの鍵を作って確認したところ、PubkeyAcceptedKeyTypesの設定を変えなくても期待通り動作しました。

参考:

2015年9月2日水曜日

[cygwin] cygwin版X server(XWin)にリモートクライアントから接続できない問題の対処方法

cygwinを最新バージョンにアップデートしたところ、リモートのX clientからwindows上のcygwin版X serverに接続できなくなってしまいました。
対処方法は簡単でXWin起動時に"-listen tcp"オプションを付けてあげればよいだけです。cygwinバージョンアップ前にはオプション指定なしで接続できていたため、デフォルトの設定が変わったのだと思われます。

cygwin環境:

uname -aで表示されるcygwinバージョン情報です。
% uname -a
CYGWIN_NT-6.1 xxx 2.2.1(0.289/5/3) 2015-08-20 11:42 x86_64 Cygwin

問題発生時の症状:

クライアント側で起動したxtermをサーバー側で表示しようとすると、以下のようなエラーになっていました。
% xterm
xterm Xt error: Can't open display: xxx:0.0

解決方法:

XWin.exeの起動パラメタに"-listen tcp"を追加します。Cygwin-Xのアイコン(ショートカット)のプロパティを開き、「リンク先」を以下のように変更してください。
  • (デフォルト)
    • C:\cygwin64\bin\run.exe --quote /usr/bin/bash.exe -l -c "cd; /usr/bin/startxwin"
  • (修正後)
    • C:\cygwin64\bin\run.exe --quote /usr/bin/bash.exe -l -c "cd; /usr/bin/XWin.exe :0 -multiwindow -listen tcp"

関連情報:

これ以外にもいろいろとハマリどころはあるので、上記オプションで解決しない場合は以下のケースも疑ってみてください。
  • サーバー(windows/cygwin)側の問題
    • xhostへのクライアント登録ができていない
    • ファイアウォール設定が有効になっている
    • ssh起動時の引数に-Xを指定していない
  • クライアント側の問題
    • DISPLAY環境変数が不正
    • sshdの設定不正(X11Forwardingがnoとなっている)
    • xauthがインストールされていない

参考:

2015年8月29日土曜日

[haskell][persistent][sqlite] Persistentパッケージのmigration機能のまとめ

HaskellでDB操作ができるPersistentパッケージの紹介をしましたが、このエントリではPersistentパッケージが提供しているmigration機能をまとめておきます。
DBを作って運用していると、機能追加や仕様変更に伴いスキーマ変更が必要になるケースが多々あります。このようなケースにおいてPersistentのmigration機能がどれくらい使えるのかを調べた結果です。

基本:

Persistetのmigration機構は(保守的なルールに沿って)スキーマ変更をある程度まで自動で処理してくれます。
ロードしたDB内のテーブル情報と、コードで定義されたEntity Definition(テーブル定義)を比較し、以下のケースにおいてスキーマの変更を行います。
  • カラムの型を変更した場合:
    • ただし、値の変換ができない場合には、DBによって拒否されることになります。
  • カラムを追加した場合:
    • ただし、追加したカラムにNOT NULL制約がある場合は、デフォルト値は設定されず、エラーとなります。
  • カラムのNOT NULL制約を外してNULL許容にした場合:
    • 変換を実行します。
    • 逆にNULL許容カラムにNON NULL制約に変更するケースの結果は、DBの状態に依存します(NULL値が存在しなければ成功、存在していると失敗)。
  • 新規Entity(テーブル)が追加された場合
Persistentは以下のケースには対応していません。
  • フィールド、Entith(テーブル)のリネーム
    • 変更前の名前と変更後の名前の対応関係がわからないので…。
  • フィールドの削除
    • データ喪失につながるので、デフォルトではフィールド削除はエラーとなります。ですが、runMigration()の代わりにrunMigrationUnsafe()を呼ぶことで強制的に削除することは可能です(もちろん非推奨)。

実験:

Migration対象のDBを以下のコードでold.dbという名前で生成します。このコードで生成されたold.dbを、新しいスキーマにmigrateすると、結果がどのようになるかをまとめておきます。
{-# LANGUAGE EmptyDataDecls             #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE TypeFamilies               #-}
import Database.Persist
import Database.Persist.TH
import Database.Persist.Sqlite
import Control.Monad.IO.Class (liftIO)

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
    name String
    age Int
    deriving Show
|]

main :: IO ()
main = runSqlite "old.db" $ do
    -- this line added: that's it!
    runMigration migrateAll
    michaelId <- insert $ Person "Michael" 26
    michael <- get michaelId
    liftIO $ print michael


以下、いくつかのスキーマ変更を実際に試してみた結果です。
  • NULL許容カラムの追加:OK
    • コードの変更点
    • Person
          name String
          age Int
          address String Maybe  -- add new NULL-able column
          deriving Show
      |]
      
          michaelId <- insert $ Person "Michael" 26 (Just "Tokyo")
      
    • 実行結果ログ
    • Migrating: CREATE TEMP TABLE "person_backup"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" INTEGER NOT NULL,"address" VARCHAR NULL)
      Migrating: INSERT INTO "person_backup"("id","name","age") SELECT "id","name","age" FROM "person"
      Migrating: DROP TABLE "person"
      Migrating: CREATE TABLE "person"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" INTEGER NOT NULL,"address" VARCHAR NULL)
      Migrating: INSERT INTO "person" SELECT "id","name","age","address" FROM "person_backup"
      Migrating: DROP TABLE "person_backup"
      Just (Person {personName = "Michael", personAge = 26, personAddress = Just "Tokyo"})
      
  • NON NULL制約のカラム追加:NG
    • コード変更点
    • share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
      Person
          name String
          age Int
          address String  -- add new NON NULL column
          deriving Show
      |]
      
          michaelId <- insert $ Person "Michael" 26 "Tokyo"
      
    • 実行結果ログ
    • Migrating: CREATE TEMP TABLE "person_backup"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" INTEGER NOT NULL,"address" VARCHAR NOT NULL)
      Migrating: INSERT INTO "person_backup"("id","name","age") SELECT "id","name","age" FROM "person"
      add_column: user error (SQLite3 returned ErrorConstraint while attempting to perform step.)
      
  • カラムの型の変更:OK
    • コード変更点
    • Person
          name String
          age String  -- change type from Int to String
          deriving Show
      |]
      
          michaelId <- insert $ Person "Michael" "26"
      
    • 実行結果ログ
    • Migrating: CREATE TEMP TABLE "person_backup"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" VARCHAR NOT NULL)
      Migrating: INSERT INTO "person_backup"("id","name","age") SELECT "id","name","age" FROM "person"
      Migrating: DROP TABLE "person"
      Migrating: CREATE TABLE "person"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" VARCHAR NOT NULL)
      Migrating: INSERT INTO "person" SELECT "id","name","age" FROM "person_backup"
      Migrating: DROP TABLE "person_backup"
      Just (Person {personName = "Michael", personAge = "26"})
      
ログを見る限り以下の手順でmigrationを行っています。参照したYesodサイトに記載がありましたがSQLiteではALTER TABLEの機能が足りないためだと思われます。
  1. 古いスキーマでperson_backupテーブルを生成
  2. person_backupテーブルにオリジナルのpersonテーブルの内容をコピー
  3. personテーブルを破棄
  4. 新しいスキーマでpersonテーブルを生成
  5. person_backupの内容をpersonにコピー
  6. person_backupテーブルを破棄

宿題:

自分の用途では、NON NULL制約のカラムを追加し、既存レコードの値にはデフォルト値を埋めた状態にしたかったのですがPersistent備え付けの機能ではできないようです。
migration処理はTemplateHaskellによって生成されたコードで実行されているらしいので、そのあたりを弄ればなんとかなるのかも。
カラム名の変更も新スキーマのカラムと旧スキーマのカラム名の対応をmigration処理にうまく伝えることができれば、なんとかなりそうな気がします。このあたりは今後の課題として、方法がわかったらまたblogにまとめます。

参考:

2015年8月17日月曜日

[cygwin][emacs] cygwin版emacsでgtagsを使えるようにする方法

これまでタグジャンプにはctagsを用いていたのですが、gtagsの「呼び出し元も一覧できる」という機能に惹かれてセットアップしてみました。いくつかはまりポイントがあったのでその対応方法も含め、手順を残しておきます。
cgwin提供のパッケージにはgtagsは含まれていなかったため、自前でビルドすることにしました。

目次:

  • gatgsの特徴
  • gtagsのインストール手順
  • emacsの設定
  • gtagsの使い方


gtagsの特徴:

  • 良い点!
    • 関数の呼び出し元を一覧表示しジャンプできる(gtags-find-rtag)
    • 関数だけでなく、ローカル変数を含む任意のシンボルをタグジャンプできる(gtags-find-symbol)
      • 正規表現で一覧絞り込み可能(gtags-find-pattern)
      • 開いているファイルで絞り込み可能(gtags-parsefiile)
    • 特定文字列をファイル名に含むファイルを一覧できる(gtags-find-file)
    • ジャンプした場所をスタックで記憶しており、何個でも遡れることができる(gtags-pop-stack)
      • コールスタックを調査している場合、この機能が非常に役に立ちます。
  • 悪い点…orz
    • ctags(find-tag)ではclass宣言にジャンプできましたが、gtags-find-tagではタグジャンプできませんでした(typedef, struct, enum定義はOKなのですが)…。コンストラクタ・デストラクタしかタグづけされておらず、class宣言の場所はgtags-find-rtagで探し出さないといけない状態です。
      • できる!というかた是非手順をご教示くださいm(_ _)m


gtagsインストール手順:

  1. ローカルで適当なディレクトリを作って、GNU GLOBAL source code tagging systemからソースコードのglobal-6.5.tar.gzアーカイブをダウンロードします。
    • (参考)DOS and Windows portsport版のglo65s.zipもありますが、cygwin環境でコンパイル&利用する上ではport版を利用する必要はないようです。port版をビルドする際には、以下の手順でconfigureにchmodで実行可能属性を付与する必要があります。
      • % chmod +x configure
        
      • zipを展開しただけだと実行権限がついておらず、configureスクリプトを実行しようとすると以下のようなエラーになります。
        % ./configure
        ./configure: Permission denied.
        
  2. zipアーカイブを展開し、生成されたglobal-6.5ディレクトリに移動
    • % tar xzf global-6.5.tar.gz
      % cd global-6.5
      
    • 全てのファイルがglobal-6.5ディレクトリ以下に展開されます。
  3. ./configureを実行
    • % ./configure
      
    • 足りないライブラリがあればcygwinパッケージでインストールする。自分の環境では以下のライブラリの追加インストールが必要でした。
      • libncurses-devel
    • このパッケージがない状態で./configureを実行すると以下のようなエラーになります。
    • % ./configure
       (...snip...)
      configure: checking "location of ncurses.h file"...
      configure: error: curses library is required but not found.
      If you are not going to use gtags-cscope, please try ./configure --disable-gtagscscope
      
  4. makeと、make installを実行
    • % make
      % make install
      
    • デフォルトでは/usr/local以下にファイルがコピーされます。
  5. 念のため確認
    • % rehash
      % which gtags
      /usr/local/bin/gtags
      % gtags --version
      gtags (GNU GLOBAL) 6.5
      Copyright (c) 2015 Tama Communications Corporation
      License GPLv3+: GNU GPL version 3 or later <http://www.gnu.org/licenses/gpl.html>
      This is free software; you are free to change and redistribute it.
      There is NO WARRANTY, to the extent permitted by law.
      
    • /usr/local/binにPATHが通っていないと以下のようなエラーになります。
      % which gtags
      gtags: Command not found.
      


emacsの設定:

  1. gtags.elをload-pathに移動する
    • % cd /usr/share/emacs/site-lisp
      % ln -s /usr/local/share/gtags/gtags.el . 
      
    • load-pathに/usr/local/share/gtagsを追加する方法でも問題はありません。個人的な好みでsymbolic linkを利用しています。
  2. .emacsの編集
    • ;;; gtags ;;;
      (require 'gtags)
      (add-hook 'java-mode-hook (lambda () (gtags-mode 1)))
      (add-hook 'c-mode-hook (lambda () (gtags-mode 1)))
      (add-hook 'c++-mode-hook (lambda () (gtags-mode 1)))
      
  3. キーバインドのカスタマイズ
    • (setq gtags-mode-hook
            '(lambda ()
               (local-set-key "\C-j\C-t" 'gtags-find-tag)
               (local-set-key "\C-j\C-h" 'gtags-find-tag-from-here)
               (local-set-key "\C-j\C-p" 'gtags-find-pattern)
               (local-set-key "\C-j\C-r" 'gtags-find-rtag)
               (local-set-key "\C-j\C-s" 'gtags-find-symbol)
               (local-set-key "\C-j\C-f" 'gtags-find-file)
               (local-set-key "\C-j\C-l" 'gtags-parse-file)
               (local-set-key "\C-j\C-j" 'gtags-pop-stack)
               ))
      (setq gtags-select-mode-hook
            '(lambda ()
               (local-set-key "\C-j\C-j" 'gtags-pop-stack)
        (local-set-key [127] 'gtags-pop-stack)      ; [DEL]
               ))
      
    • M-t, M-r, M-s, C-tにバインドする例が紹介されていましたが、個人的に利用するものとバッティングしていため、C-j C-???にマッピングしています。
    • C-tでは不要でしたが、C-j C-jをgtags-pop-stackにバインドするには、gtags-select-mode-hookでの定義も必要でした。


gtagsの使い方:

  1. ソース群が格納されたディレクトリのルートへ移動してGTAGSファイルを生成する
  2. % cd src
    % gtags -v
    
  3. emacsを起動しgtags-find-tagで関数名を指定し[RET]
    • 初回はGTAGSファイルの場所を聞かれます。gtagsを実行したディレクトリに生成されているファイルを選択します。

備考:

cygwin以外の環境(mac, linux)でも同様の手順でセットアップができました。
cygwin-mountを入れないと動かないかも、という記述を見かけましたが私の(以下の)環境では問題ありませんでした。
% uname -a
CYGWIN_NT-6.3 win8 2.2.0(0.289/5/3) 2015-08-03 12:51 x86_64 Cygwin


参考:

2015年7月19日日曜日

[cygwin][haskell][emacs] MinGWでcygwinの"/cygdrive"パスにアクセスする裏技(cygwin環境のemacsでflycheckを動作させる方法)

haskell関連のコマンドはMinGW上でビルドされているため、cygwin環境の"/cygdrive"から始まるパスには対応していません。haskell-hlintから呼び出されるhlintも当然この問題の影響を受けておりemacs関連の設定が適切にされていたとしても、以下のようなエラーが表示されてしまいます。

ミニバッファに表示されるエラー詳細:

Suspicious state from syntax checker haskell-hlint: Checker haskell-hlint returned non-zero exit code 1, but no errors from output: hlint.exe: Couldn't find file: /cygdrive/c/Users/Hiroyuki/tmp/flycheck_hello.hs

MinGW関連コマンドで"/cygdrive"から始まるパスを解釈できない問題の回避方法:

"/cygdrive"から始まるパスをMinGWによってビルドされたバイナリ(hlint)からも解釈できるようになれば、この問題は回避できるのでは?と考え、mklinkを用いて以下の手順でシンボリックリンクを作ってみました。
C:\> cd \
C:\> mkdir cygdrive
C:\> cd cygdrive
C:\cygdrive> mklink /d C C:\
これが大成功!"C:\CYGDRIVE\C"に"C:\"へのシンボリックリンクを作っておくことでcygwin上の"/cygdrive/c/xxx/yyy"といったパスがMinGW上では"C:\xxx\yyy"として解釈される状態になります。
この環境ではemacs上でflycheckが正しく動作するようになりました。

この環境はMinGW関連コマンド全般に適用可能:

この裏技はMinGW/MSYSでビルドされた任意のコマンドに適用可能なはずです。cygwin環境とMinGW環境が混在していて、MinGW/MSYS関連コマンドの実行に弊害が出ている方は是非お試しいただければ、と思います。

余談:うまくいかなかった方法(cygwinのマウント機能の利用メモ):

cygwin環境において"C:"ドライブのルートを"/C"としてマウントする方法もありますが、MinGWは"/C/xxx/yyy"というパスは解釈できずNGでした。
% cd /
% mkdir c
% mount c: /c
"C:"を/C:"としてマウントできれば、と思ったのですが残念ながら以下のようなエラーになりました…。
% mkdir c:
% mount c: c:
mount: c:: Invalid argument

参考:

2015年6月23日火曜日

[subversion] 特定ディレクトリ以下をサーバーとの同期対象から除外する方法

subversionでチェックアウトした作業コピー内の特定ディレクトリ以下をサーバーとの同期対象から除外したい、ということを考えたことはないでしょうか。
例えば、リポジトリの特定ディレクトリ以下に自分にとって不要なファイルやアーカイブが多数コミットされていると、以下のような無駄が生じます。これらの無駄をなくしたい、というのが主なユースケースです。
  • 不要なファイル・アーカイブによってディスクスペースが占有される
  • サーバー上に更新が発生した際にupdateに余計な時間がかかってしまう

単純に特定ディレクトリ以下の作業ファイルを削除してしまうと、コミット時に差分として検知されてしまう、また、次回以降のupdateコマンドで再ダウンロードされてしまう、という非常に残念な挙動になります。
このようなケースでは以下の方法を使えば、差分が検知されない状態で所定ディレクトリ以下のファイルを削除し、かつ、差分として検知されることも、リポジトリの更新に伴って再度ダウンロードされることもない状態をキープできます。

特定ディレクトリ以下のファイル・ディレクトリを削除する:

--set-depthオプションで対象ディレクトリのDepthを"empty"に設定します。
具体的には、以下のコマンドを実行します。
% svn --set-depth empty update TARGET-DIR
TortoiseSVNを利用している場合には、ディレクトリを選択した状態で右クリックし、[TortoiseSVN] → [特定のリビジョンへ更新] → [更新の深さ]で"empty"を指定します。

特定ディレクトリ以下のファイル・ディレクトリを再度取得する:

--set-depthオプションで対象ディレクトリのDepthを"infinity"に設定します。
具体的には、以下のコマンドを実行します。
% svn --set-depth infinity update TARGET-DIR
TortoiseSVNを利用している場合には、ディレクトリを選択した状態で右クリックし、[TortoiseSVN] → [特定のリビジョンへ更新] → [更新の深さ]で"infinity"を指定します。

特定ディレクトリのDepthを確認する方法:

ディレクトリを指定してinfoコマンドを実行すると、Depth項目として以下のいずれかが表示されます。
  • empty
  • files
  • immediates
  • infinity
  • exclude
TortoiseSVNを利用している場合には、ディレクトリを選択した状態で右クリックし[プロパティ]で表示されるダイアログの[Subversion]タブを表示すると、その中にDepth項目があります。

emptyとexcludeの違い:

Sparse Directories (nightly)の情報によると、--set-depth emptyと--set-depty excludeはにた効果が得られますが、以下の点が異なります。ディスク消費を減らす目的があるのであればemptyが正解です。infinityと頻繁に切り替えるのであればexcludeを利用するのが良いと思われます。
  • empty
    • チェックアウト済みのファイル・サブディレクトリは再帰的に全て削除される
  • exclude
    • チェックアウト済みのファイル・サブディレクトリは全てリポジトリ管理外のファイルとしてそのまま残る
    • 1.6以降で新しくサポートされたオプション

参考:

2015年4月25日土曜日

[haskell] cmdargsパッケージで楽々コマンドライン引数パース

コマンドラインツール実装時、オプション指定とか引数の並びとか考え始めると大変です。HaskellではSystem.EnvironmentモジュールからgetArgsという関数が提供されていますが、本エントリで紹介するcmdargsパッケージを利用すると以下のようなことが簡単にできます。

cmdargsパッケージの特徴:

  • データ構造を定義するだけで起動引数・オプションのパースができる
  • パース結果を型付きで参照することができる
  • パース失敗時には、原因がわかるエラーメッセージが表示される
  • --help, --versionオプションで表示される情報を自動で生成してくれる
Haskell版GNU getoptライブラリと比べて以下の2点が優れている、とHPには書かれています
  1. HLintコマンドラインのハンドリングが1/3の短さ
  2. Cabal, darcsなどのmultiple modeプログラムに対応
  3. ※multiple modeは、引数に応じてオプション引数仕様が変わる動作を指している模様

簡単なサンプル:Sample.hs

{-# LANGUAGE DeriveDataTypeable #-}
import System.Console.CmdArgs

data Sample = Sample {
      hello :: String
} deriving (Show, Data, Typeable)

sample = Sample {
           hello = def &= help "World argument" &= opt "world"}
         &= summary "This is a sample of CmdArgs."

main = print =<< cmdArgs sample
パース結果を格納するデータ型(Sample)を定義しています。この型はShow, Data, Typeableのインスタンスである必要があります。defは任意の型のデフォルト値で、この例(String)では""と同じです。&=以降はhelp用のアノテーションになります。

実行結果:Sample.hs

% runghc Sample.hs --help
This is a sample of CmdArgs.

sample [OPTIONS]

Common flags:
  -h --hello[=ITEM]  World argument
  -? --help          Display help message
  -V --version       Print version information

% runghc Sample.hs --hello
Sample {hello = "world"}

より複雑なパース:MyArgs.hs

以下の例は自作コマンドに利用したコードの一部です。複数のCSVファイル、もしくはCSVファイルが格納されたディレクトリを入力として与え、CSVファイル内のデータをdboptで指定したタイプのDB(ファイル名・コネクションは必須の第一引数)に格納するものでした。
{-# LANGUAGE DeriveDataTypeable #-}
import System.Console.CmdArgs

data DbOpt = SQLite | PostgreSQL | MySQL deriving (Data, Typeable, Eq, Show, Read)

data MyArgs = MyArgs {
      dbopt    :: DbOpt
    , schema   :: String
    , recursive :: String
    , targetdb :: String
    , csvfiles :: [String]
    , color :: Bool
    } deriving (Data,Typeable,Show)

help_dbopt = "specify DB type. default DB is 'sqlite'.\n"
             ++ "'postgresql', 'mysql' & etc. may be supported in the future."
help_schema = "specify table type.\n"
              ++ "specify predefined schema index such as 'd12', 'd13', etc.\n"
              ++ "default is 'normal' which stores all values as string.\n"
help_recursive = "specify directory to iterate all files in it recursively."
help_targetdb = "specify DB file/connection name."
help_csvfiles = "specify one ore more csv files.\n"
              ++ "one file must be specified at the minimum."
help_color = "enable colored output."
help_program = "parse CSV files and store all data into DB.\n"
               ++ "TARGET_DB is the mandatory argument."

config = MyArgs {
      dbopt   = SQLite &= typ "TARGET_DB_TYPE" &= help help_dbopt
    , schema  = "normal" &= typ "SCHEMA_INDEX" &= help help_schema
    , recursive = def &= typ "RECURSIVE_DIR" &= help help_recursive
    , targetdb  = def &= typ "TARGET_DB" &= argPos 0 -- &= help help_targetdb
    , csvfiles = def &= typ "CSV_FILES" &= args -- &= help help_csvfiles
    , color = def &= name "c" &= help help_color
} &= verbosity &= program "MyArgs" &= help help_program

main = print =<< cmdArgs config

実行結果:MyArgs.hs

# ヘルプ
% runghc MyArgs.hs --help
The MyArgs program

MyArgs [OPTIONS] TARGET_DB [CSV_FILES]
  parse CSV files and store all data into DB. TARGET_DB is the mandatory
  argument.

Common flags:
  -d --dbopt=TARGET_DB_TYPE     specify DB type. default DB is 'sqlite'.
                                'postgresql', 'mysql' & etc. may be supported
                                in the future.
  -s --schema=SCHEMA_INDEX      specify table type. specify predefined schema
                                index such as 'd12', 'd13', etc. default is
                                'normal' which stores all values as string.
  -r --recursive=RECURSIVE_DIR  specify directory to iterate all files in it
                                recursively.
  -c --color                    enable colored output.
  -? --help                     Display help message
  -V --version                  Print version information
  -v --verbose                  Loud verbosity
  -q --quiet                    Quiet verbosity

# エラーケース(dboptが不正)
% runghc MyArgs.hs -d invalid_dbopt
Could not read, expected one of: sqlite postgresql mysql
# エラーケース(引数不足)
% runghc MyArgs.hs 
Requires at least 1 arguments, got 0

# 正常系(複数csvファイル指定)
% runghc MyArgs.hs a.db 1.csv 2.csv
MyArgs {dbopt = SQLite, schema = "normal", recursive = "", targetdb = "a.db", csvfiles = ["1.csv","2.csv"]}
# 正常系(オプションでdbopt, recursiveを指定)
% runghc MyArgs.hs a.db -r data_dir -d MySQL
MyArgs {dbopt = MySQL, schema = "normal", recursive = "data_dir", targetdb = "a.db", csvfiles = []}

パースの結果得たMyArg型の値から、起動引数・オプションで指定した値を取り出すことができます。必須の第一引数が指定されていない場合、dboptで不正なDBタイプを指定した場合など、起動引数・オプションが不正である場合に、適切なエラーが出力されているのが確認できます。
(2016/02/11: Bool型のオプション--colorを追加。)

参考:

2015年4月5日日曜日

[windows][haskell] Widnwos環境でHaskell Platformを完全削除する方法

Windows上でHaskell Platformを完全削除する方法です。
LinuxやMac環境についてはネット上に多数情報がありますが、Windows環境についてはそれが見当たらなかったため、本エントリにまとめておきます。確認した環境はWindows 8.1+Haslell Platform 2014.2.0.0です。

削除手順:

Windows環境では以下の手順でHaskell Platformを完全に削除できます。
  1. Haskell Platformのアンインストール
    1. [コントロールパネル] - [プログラム] - [プログラムと機能]を開く
    2. "Haslell Platform 2014.2.0.0"を選択して[アンインストール]を実行
  2. ユーザー領域に作成されたパッケージ関連ファイルの削除
    1. 手動(エクスプローラ、rmコマンドなど)で次の2つのディレクトリ以下を完全に削除
      • C:\Users\???\AppData\Roaming\ghc
      • C:\Users\???\AppData\Roaming\cabal
      ※"C:\Users\???\"はユーザーのホームディレクトリです。
    2. 手動で全ての".cabal-sandbox"ディレクトリを削除する
    3. cabal sandbox initを実行したディレクトリには.cabal-sandboxディレクトリが生成され、その中にパッケージ関連ファイルが格納されています。(cabal sandbox delete もしくはエクスプローラーなどで)これらを全て削除します。

参考:

2015年3月27日金曜日

[cygwin] cygwin版のgitコマンドでサーバーとの通信に失敗する問題の対処

cygwin上でgitコマンドを使用しているのですが、ある環境でのみgit pull/pushで以下のようなエラーが発生する問題に遭遇しました。調子よく動作している別の環境との比較で原因が判明したので、同じ問題にハマってしまった人向けに情報を残しておきます。

問題の現象:

問題の環境ではgit pull/pushを実行すると、以下のようなエラーが出力されていました。
% git pull origin master
fatal: 'pull' appears to be a git command, but we were not
able to execute it. Maybe git-pull is broken?

% git push origin master
fatal: Full write to remote helper failed: Broken pipe

解決方法:

この問題はPATH環境変数の設定によって発生します。cygwinのバイナリが格納されているディレクトリ(C:\cygwin64\bin)がユーザー環境変数にしか定義されていない状態だと、このエラーになります。以下のいずれかの設定に変更することで問題を解決できます。

  1. cygwinのシェルスクリプトでpath環境変数の先頭に/usr/binを追加する
    • cygwinをインストールしたデフォルト環境ではこの状態になっています。自分はcabalコマンドがcygwin上で実行できるよう、このデフォルト設定を無効にしていました。
  2. ユーザー環境変数ではなく、システム環境変数のPATHにcygwinディレクトリを追加する
    • 推測ですが、システムプロセスとして/usr/bin以下のコマンドを実行しているのだと思われます。パスの順序は変わらないのに、ユーザー環境変数のPATHにcygwinディレクトリを記述していると、問題の現象が発生しています。

備考:

2015年3月25日水曜日

[windows][haskell] bzlibパッケージ利用時にHSbzlib-0.5.0.5.o: unknown symbol `fileno'となる問題の対処

Haskellでは、bzip2で圧縮されたデータを解凍(もちろん逆に圧縮も)できるbzlibパッケージが提供されています。

ところが、自分の環境(Windows 8.1+Haslell Platform 2014.2.0.0)でbzlibパッケージをインポートするサンプルプログラムをビルド・実行しようとすると、以下のようなエラーになってしまいました。
> runghc examples/bunzip2.hs       
bunzipz2.hs: C:\Users\XXX\AppData\Roaming\cabal\x86_64-windows-ghc-7.8.3\bzlib-0.5.0.5\HSbzlib-0.5.0.5.o: unknown symbol `fileno'                      
bunzip2.hs: bunzip2.hs: unable to load package `bzlib-0.5.0.5'            

ネット上の情報を漁ってみたのですが同じ問題に遭遇している人はいなさそう・・・。ということで、自力で調べてみました。以下、分かったことと回避方法をまとめておきます。


問題の原因:

  • 詳細は「調査の内容」に記しましたが、bzlibが内部で呼び出しているfileno()とfdopen()が定義されているmingwのlibmoldname.aがリンクされないのが原因のようです。

問題の回避方法

  • libmoldname.aをリンクする修正が正しいのですが、その方法が分からなかったため、bzlibのコードに手をいれてileno()とfdopen()を呼ばないバージョンをビルドし、それで正規のパッケージを置き換えることで、bzlibが利用できるようになりました。以下、その手順です。
    1. cabalのpackageがダウンロードされているディレクトリに移動する
    2. % cd C:\Users\XXX\AppData\Roaming\cabal\packages\hackage.haskell.org\bzlib\0.5.0.5
      
    3. bzlib-0.5.0.5.tar.gzを展開する
    4. % tar xzf bzlib-0.5.0.5.tar.gz
      
    5. このパッチをDLしてbzlib.patchという名前で保存し、適用する
    6. % patch -p1 < bzlib.patch
      
    7. パッチを含んだアーカイブでオリジナルのbzlib-0.5.0.5.tar.gzを置き換える
    8. % tar czf bzlib-0.5.0.5.tar.gz bzlib-0.5.0.5/
      
    9. bzlibパッケージをreinstallする
    10. % cabal install bzlib --reinstall
      

調査の内容:

  • 自分の環境にcygwinとHaskell付属のmingwの両方が存在しているが原因かと思ったがそうではないらしい。
    • mingwのライブラリにもfilenoがちゃんと用意されている。
    • mingw環境においてここのfilenoのサンプルがビルドでき、動作しているため
      • > gcc fileno.cpp
  • mingw環境の確認
    • filenoを利用するC言語版サンプルをビルドしてmapファイルを出力したところ、filenoはlibmoldname.aというライブラリの定義をリンクしていることが分かった。
      • gcc -Wl,-Map=a.map -g fileno.cpp
      •  .text          0x0000000000402c10        0x8 c:/program files/haskell platform/2014.2.0.0/mingw/bin/../lib/gcc/x86_64-w64-mingw32/4.6.3/../../../../x86_64-w64-mingw32/lib/../lib/libmoldname.a(dnvzs00026.o)
                        0x0000000000402c10                fileno
        
  • bzlib-0.5.0.5をローカル環境でビルドしてその内容を確認
    • filenoがunknown symbolとなっているのは以下の2つのオブジェクトファイル
      • bzlib-0.5.0.5/dist/build/cbits/bzlib.o
      • bzlib-0.5.0.5/dist/build/cbits/HSbzlib-0.5.0.5.o
    • 上記オブジェクトファイルで同じくunknown symbolになっているfopen, fread, fwrite, fcloseといった関数はlibmoldname.aとは別のlibmsvcrt.aにアーカイブされている。
      • a.mapより
      •  .text          0x0000000000402c70        0x8 c:/program files/haskell platform/2014.2.0.0/mingw/bin/../lib/gcc/x86_64-w64-mingw32/4.6.3/../../../../x86_64-w64-mingw32/lib/../lib/libmsvcrt.a(dnhvs00971.o)
                        0x0000000000402c70                fopen
         .text          0x0000000000402c78        0x8 c:/program files/haskell platform/2014.2.0.0/mingw/bin/../lib/gcc/x86_64-w64-mingw32/4.6.3/../../../../x86_64-w64-mingw32/lib/../lib/libmsvcrt.a(dnhvs00979.o)
                        0x0000000000402c78                fread
        
        
  • windows版のみcabal buildでパッケージをビルドする際に、filenoの解決に必要なlibmoldname.aのリンクが漏れているのでは?
    • 試しに、libmoldname.aを削除してcabal buildをビルドしてみたところ、リンクエラーになりました。ということは、パッケージビルド時にはlibmoldname.aをリンクしようとしていることになります。・・・ハズレ。
    • Resolving dependencies...
      Configuring bzlib-0.5.0.5...
      Building bzlib-0.5.0.5...
      Preprocessing library bzlib-0.5.0.5...
      c:/program files/haskell platform/2014.2.0.0/mingw/bin/../lib/gcc/x86_64-w64-mingw32/4.6.3/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmoldname
      c:/program files/haskell platform/2014.2.0.0/mingw/bin/../lib/gcc/x86_64-w64-mingw32/4.6.3/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmoldname
      collect2: ld returned 1 exit status
      linking dist\build\Codec\Compression\BZip\Stream_hsc_make.o failed (exit code 1)
      
      command was: C:\Program Files\Haskell Platform\2014.2.0.0\mingw\bin\gcc.exe dist\build\Codec\Compression\BZip\Stream_hsc_make.o dist\build\Codec\Compression\BZip\Stream_hsc_utils.o -o dist\build\Codec\Compression\BZip\Stream_hsc_make.exe -LC:\Users\XXX\AppData\Roaming\cabal\x86_64-windows-ghc-7.8.3\bytestring-0.10.4.1 -LC:\Program Files\Haskell Platform\2014.2.0.0\lib\deepseq-1.3.0.2 -LC:\Program Files\Haskell Platform\2014.2.0.0\lib\array-0.5.0.0 -LC:\Program Files\Haskell Platform\2014.2.0.0\lib\base-4.7.0.1 -lwsock32 -luser32 -lshell32 -LC:\Program Files\Haskell Platform\2014.2.0.0\lib\integer-gmp-0.5.1.0 -LC:\Program Files\Haskell Platform\2014.2.0.0\lib\ghc-prim-0.3.1.0 -LC:\Program Files\Haskell Platform\2014.2.0.0\lib/rts-1.0 -lm -lwsock32 -lgdi32 -lwinmm
      
  • パッケージビルド時ではなく、パッケージを利用するコードをコンパイルして実行ファイルを生成するときにlibmodulename.aがリンクされていないようです。
    • これはどこの設定に書かれているのか・・・?
      
      

備考:

  • 冒頭に書いたとおり、本来ならば適切にlibmoldname.aをリンクするように修正するのが正しい解決方法なのですが、bzlibパッケージの中にそのような見つからず、どこを変えれば良いか分かりませんでした・・・。orz 。どこを修正すればリンクライブラリを追加できるか知っている方、是非ご教示ください。 m(_ _)m 
  • unknown symbolとなるfilenoとfdopenはbzlib-0.5.0.5/cbits/bzlib.cの中で呼び出しています。コードを確認したところどちらも環境によってはundefされており、呼び出さなくても問題はないことを確認しました。
  • なお、Linux, Mac環境上ではこの問題は発生しません。問題なく利用できています。

参考:

2015年3月12日木曜日

[windows][haskell] cabal installでWin32File.hsc:34:19: fatal error: Share.h: No such file or directoryというエラーが出たときの対処方法

Windows 8.1+Haslell Platform 2014.2.0.0環境でcabal installを実行したところ、以下のようなエラーに遭遇しました。

問題の現象:

Preprocessing library streaming-commons-0.1.9.1...
Win32File.hsc:34:19: fatal error: Share.h: No such file or directory
compilation terminated.
compiling dist\build\System\Win32File_hsc_make.c failed (exit code 1)

問題の原因:

ネットの情報によると、これはcygwin環境とmingw環境が混在している場合に発生するようです。Haskell Platformはmingwに依存しているのですが、cygwinがインストールされているとcabal installから実行されるgccその他の挙動がmingwのものと異なってしまい、エラーとなります。

問題の解決方法:

cygwin付属のバイナリでなく、mingw付属のバイナリが利用されるようにPATHの記述を変更することで、この問題が発生しなくなります。PATH環境変数の中にあるcygwin環境のディレクトリを"C:\Program Files\Haskell Platform\2014.2.0.0\mingw\bin"というディレクトリよりも後ろに記載するように修正してください。

参考:

2015年2月8日日曜日

[sqlite] WITHOUT ROWIDオプションを使った性能改善の解説

SQLite 3.8.2以降のバージョンでは、テーブル生成時のSQLに"WITHOUT ROWID"という指定ができるようになっているのをご存じでしょうか。この"WITHOUT ROWID"を利用することで、ストレージ使用量削減、処理高速化が可能です。
本家のページを参考に使用方法、どういう仕組みで何が最適化されるか、をまとめておきます。


使用方法:

テーブル生成時のSQLの末尾にWITHOUT ROWIDを追加するだけです(下線部)。
WITHOUT ROWIDを用いてテーブルを生成する際のSQL例:
CREATE TABLE IF NOT EXISTS wordcount(
  word TEXT PRIMARY KEY,
  cnt INTEGER
) WITHOUT ROWID;

ただし、WITHOUT ROWID指定時には以下の点に気をつけてください。
  • PRIMARY KEYが必要
  • INTEGER PRIMARY KEYの場合はWITHOUT ROWID指定なしでも同等の効果が得られる
  • 以下の機能・APIが使えない
    • AUTOINCREMENT
    • sqlite3_last_insert_rowid()
    • incremental blob I/O
    • sqlite3_update_hook()

WITHOUT ROWID指定テーブルと通常テーブルの違い:

通常の(ROWID)テーブルを生成する際のSQL例:
CREATE TABLE IF NOT EXISTS old_wordcount(
  word TEXT PRIMARY KEY,
  cnt INTEGER
);
 
上記のような通常のSQLで生成したテーブルと、WITHOUT ROWID指定で生成したテーブルには以下の違いがあります。通常のROWIDテーブルとWITHOUT ROWIDテーブルとでは、ROWID値を保持するカラムをテーブル内部に生成するかどうか、という点が根本的に異なります。
通常(ROWID)テーブルは、PRIMARY KEYの指定の有無にかかわらず、必ず内部にROWIDカラムを生成し、ROWID用のB-Treeが構築されます(キー:ROWID、バリュー:word, cnt)。PRIMARY KEYが指定された場合には、ROWID用のB-Treeに加えて、PRIMARY KEY用のB-Treeがさらに構築されます(キー:word、バリュー:ROWID)。
これに対し、WITHOUT ROWIDテーブルではROWIDカラムが生成されなくなり、PRIMARY KEYで指定したカラムのB-Treeだけが構築されます(キー:word、バリュー:cnt)。
この違いにより例に挙げたテーブルでは(概算ですが)、必要ストレージが1/2になり、かつ、PRIMARY KEY指定でcntを参照する際の検索速度が2倍になる、メリットが得られます。なぜこのメリットが得られるのかを事項で説明します。

WITHOUT ROWIDテーブルで性能が改善される理由:

以下のSQLを実行を例にとって両テーブルの処理の違いを説明します。
SELECT cnt FROM wordcount WHERE word='xyzzy';

上記のSQL実行時、通常(ROWID)テーブルでは2つのB-Treeに対する捜査が必要になります。具体的には、最初にwordをキーに持つB-Treeを検索し、該当するROWIDを取り出します。その後、ROWIDをキーに持つB-Treeを検索することでcntを取り出します。
これに対しWITHOUT ROWIDテーブルではwordをキーに持つ、一つのB-Treeに対する捜査でcntを取り出すことができます。

  • 必要ストレージの改善:
    • 通常(ROWID)テーブルでは各wordの値を2個ずつ保存する
    • WITHOUT ROWIDテーブルでは1個ずつでよい
  • 検索処理の改善
    • 通常(ROWID)テーブルではwordのB-TreeとROWIDのB-Tree、2回のバイナリサーチが必要
    • WITHOUT ROWIDテーブルではwordのB-Treeに対する1回のバイナリサーチでよい

INTEGER PRIMARY KEY利用時にWITHOUT ROWID指定なしでも最適化される理由:

WITHOUT ROWID指定なしの通常(ROWID)テーブルで、INTEGER PRIMARY KEYを利用する場合、PRIMARY KEYに指定したカラムはROWIDのエイリアスになります。このエイリアス機能によりPRIMARY KEY用のB-TreeをROWIDとは別に構築する必要がなくなり、WITHOUT ROWIDを指定した場合と同様の効果が得られます。

参考:

[cygwin][ruby] Cygwin上でrubyのgemコマンドが動作しない問題の対処方法

新しいPC(Win8.1)にrubyのrefe, rrse環境をセットアップしようとして気がつきました。私の環境だと、gemコマンドの実行で以下のようなエラーになります。
※現状は、refeでなくrefe2を利用する必要があるらしいです。
% gem install refe
ERROR:  While executing gem ... (ArgumentError)
    invalid byte sequence in UTF-8


私の環境は以下の通り。
% uname -a
CYGWIN_NT-6.3 win8 1.7.33-2(0.280/5/3) 2014-11-13 15:47 x86_64 Cygwin
% ruby --version
ruby 2.0.0p598 (2014-11-13) [x86_64-cygwin]
% gem --version
2.4.1


ネット上の情報によると、LANG環境変数にC.BINARYを設定するとよいそうです。 私の環境でも、この方法でエラーを回避することができました。
% setenv LANG C.BINARY
% echo $LANG
C.BINARY


これでgemコマンドが正常に動作するようになり、refe/rrseのセットアップを完了しました!
% gem install refe
Fetching: refe-0.8.0.3.gem (100%)
Successfully installed refe-0.8.0.3
Parsing documentation for refe-0.8.0.3
Installing ri documentation for refe-0.8.0.3
Done installing documentation for refe after 3 seconds
1 gem installed
% gem install rrse
Fetching: rack-1.6.0.gem (100%)
Successfully installed rack-1.6.0
Fetching: progressbar-0.21.0.gem (100%)
Successfully installed progressbar-0.21.0
Fetching: bitclust-core-0.8.0.gem (100%)
Successfully installed bitclust-core-0.8.0
Fetching: rrse-0.6.1.gem (100%)
Successfully installed rrse-0.6.1
Parsing documentation for rack-1.6.0
Installing ri documentation for rack-1.6.0
Parsing documentation for progressbar-0.21.0
Installing ri documentation for progressbar-0.21.0
Parsing documentation for bitclust-core-0.8.0
Installing ri documentation for bitclust-core-0.8.0
Parsing documentation for rrse-0.6.1
Installing ri documentation for rrse-0.6.1
Done installing documentation for rack, progressbar, bitclust-core, rrse after 36 seconds
4 gems installed



参考:

2015年1月28日水曜日

[linux][cygwin] less, manの終了直前の画面をクリアしないで端末上に残す方法

端末上でmanコマンドでオプション引数の詳細を調べて、さあコマンドを実行、というときにオプション引数の情報が画面から消えて困る・・・という目に遭っている方はいないでしょうか。

最近のlinuxのディストリビューションやcygwin環境ではデフォルトの設定によっって上記の挙動になっていますが、terminfoの設定を適切に変更することで、manコマンドだけでなくlessやviで表示していた画面をそのまま残した状態で、プロンプトに戻りそれを参照しながらコマンドを実行するということが可能になります。

terminfoの設定手順

t9mdさんの日記に書かれている通りですが、以下の手順でless, manなどの終了前の画面が端末上に残るようにできます。

% infocmp > screen.terminfo
% vi screen.terminfo             # vi上でrmcup、smcupを削除
% tic -o ~/.terminfo screen.terminfo
% rm -f screen.terminfo


rmcup, smcupの削除について、自分の環境では、具体的には以下の二箇所の取り消し線部を削除しました。

# Reconstructed via infocmp from file: /usr/share/terminfo/x/xterm
xterm|xterm terminal emulator (X Window System), 
 am, bce, km, mc5i, mir, msgr, npc, xenl, 
 colors#8, cols#80, it#8, lines#24, pairs#64, 
 acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 
 bel=^G, blink=\E[5m, bold=\E[1m, cbt=\E[Z, civis=\E[?25l, 
 clear=\E[H\E[2J, cnorm=\E[?12l\E[?25h, cr=^M, 
 csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H, 
 cud=\E[%p1%dB, cud1=^J, cuf=\E[%p1%dC, cuf1=\E[C, 
 cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A, 
 cvvis=\E[?12;25h, dch=\E[%p1%dP, dch1=\E[P, dl=\E[%p1%dM, 
 dl1=\E[M, ech=\E[%p1%dX, ed=\E[J, el=\E[K, el1=\E[1K, 
 flash=\E[?5h$<100>\E[?5l, home=\E[H, hpa=\E[%i%p1%dG, 
 ht=^I, hts=\EH, ich=\E[%p1%d@, il=\E[%p1%dL, il1=\E[L, 
 ind=^J, indn=\E[%p1%dS, invis=\E[8m, 
 is2=\E[!p\E[?3;4l\E[4l\E>, kDC=\E[3;2~, kEND=\E[1;2F, 
 kHOM=\E[1;2H, kIC=\E[2;2~, kLFT=\E[1;2D, kNXT=\E[6;2~, 
 kPRV=\E[5;2~, kRIT=\E[1;2C, kb2=\EOE, kbs=\177, kcbt=\E[Z, 
 kcub1=\EOD, kcud1=\EOB, kcuf1=\EOC, kcuu1=\EOA, 
 kdch1=\E[3~, kend=\EOF, kent=\EOM, kf1=\EOP, kf10=\E[21~, 
 kf11=\E[23~, kf12=\E[24~, kf13=\EO2P, kf14=\EO2Q, 
 kf15=\EO2R, kf16=\EO2S, kf17=\E[15;2~, kf18=\E[17;2~, 
 kf19=\E[18;2~, kf2=\EOQ, kf20=\E[19;2~, kf21=\E[20;2~, 
 kf22=\E[21;2~, kf23=\E[23;2~, kf24=\E[24;2~, kf25=\EO5P, 
 kf26=\EO5Q, kf27=\EO5R, kf28=\EO5S, kf29=\E[15;5~, 
 kf3=\EOR, kf30=\E[17;5~, kf31=\E[18;5~, kf32=\E[19;5~, 
 kf33=\E[20;5~, kf34=\E[21;5~, kf35=\E[23;5~, 
 kf36=\E[24;5~, kf37=\EO6P, kf38=\EO6Q, kf39=\EO6R, 
 kf4=\EOS, kf40=\EO6S, kf41=\E[15;6~, kf42=\E[17;6~, 
 kf43=\E[18;6~, kf44=\E[19;6~, kf45=\E[20;6~, 
 kf46=\E[21;6~, kf47=\E[23;6~, kf48=\E[24;6~, kf49=\EO3P, 
 kf5=\E[15~, kf50=\EO3Q, kf51=\EO3R, kf52=\EO3S, 
 kf53=\E[15;3~, kf54=\E[17;3~, kf55=\E[18;3~, 
 kf56=\E[19;3~, kf57=\E[20;3~, kf58=\E[21;3~, 
 kf59=\E[23;3~, kf6=\E[17~, kf60=\E[24;3~, kf61=\EO4P, 
 kf62=\EO4Q, kf63=\EO4R, kf7=\E[18~, kf8=\E[19~, kf9=\E[20~, 
 khome=\EOH, kich1=\E[2~, kind=\E[1;2B, kmous=\E[M, 
 knp=\E[6~, kpp=\E[5~, kri=\E[1;2A, mc0=\E[i, mc4=\E[4i, 
 mc5=\E[5i, meml=\El, memu=\Em, op=\E[39;49m, rc=\E8, 
 rev=\E[7m, ri=\EM, rin=\E[%p1%dT, rmacs=\E(B, rmam=\E[?7l, 
 rmcup=\E[?1049l, rmir=\E[4l, rmkx=\E[?1l\E>, rmso=\E[27m, 
 rmul=\E[24m, rs1=\Ec, rs2=\E[!p\E[?3;4l\E[4l\E>, sc=\E7, 
 setab=\E[4%p1%dm, setaf=\E[3%p1%dm, 
 setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 
 setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 
 sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, 
 sgr0=\E(B\E[m, smacs=\E(0, smam=\E[?7h, smcup=\E[?1049h, 
 smir=\E[4h, smkx=\E[?1h\E=, smso=\E[7m, smul=\E[4m, 
 tbc=\E[3g, u6=\E[%i%d;%dR, u7=\E[6n, u8=\E[?1;2c, u9=\E[c, 
 vpa=\E[%i%p1%dd, 


cygwin上でinfocmp, ticコマンドをインストールするには

cygwinではデフォルトではinfocmp, ticはインストールされていません。ncursesパッケージをインストールするとこれらのコマンドが使えるようになります。

その他

先日のエントリでlvを紹介した際、lvではlessの-X(--no-init)オプションに相当する機能がないのが残念、としましたがterminfoの設定を変えることで、コマンドに依らず起動中の画面を消さずにそのまま表示を引き継ぐことができることがわかりました。

参考:

メモ:

  • infocmpをcmpinfoとtypoしていたのを修正しました(2015/02/18)。

2015年1月23日金曜日

[cygwin][linux] lvで複数の文字コードが混在する環境で簡単に文字化けを回避する

複数の文字コードを扱う環境では「文字化けで読めない・・・、editorで開き直そう。」という煩わしい作業が頻繁に発生しているのではないでしょうか。該当する方はlvというコマンドを利用すれば幸せになれますので是非一度試してみてください。

lvは入力された文字コードは自動判別し、出力するコードはLANG環境変数で指定している文字コードに変換してくれるので、どんなファイルを与えても文字コード指定なしで意図通り表示してくれます(すばらしい!)。

環境変数に以下の設定をしておけば、manやgitからもlvが利用されるようになります。-cは文字装飾用ANSIエスケープシーケンスに対応するためのオプション指定です。
 
PAGER='lv -c'


もし自動判別に失敗する、もしくは、LANGで指定していない文字コードで出力したい、といった特殊なケースには、-Iで入力、-Oで出力する文字コードを指定できます。
 
       coding-system:
              a: auto-select
              c: iso-2022-cn
              j: iso-2022-jp
              k: iso-2022-kr
              ec: euc-china
              ej: euc-japan
              ek: euc-korea
              et: euc-taiwan
              u7: UTF-7
              u8: UTF-8
              l1..9: iso-8859-1..9
              l0: iso-8859-10
              lb,ld,le,lf,lg: iso-8859-11,13,14,15,16
              s: shift-jis
              b: big5
              h: HZ
              r: raw mode

例えば、入力をeuc-japan、出力をshift-jisに指定したい、といった場合には以下のオプションを与えればOKです。
 
% lv -Iej -Os euc.txt


自分にとって唯一残念なのがlessの-X(--no-init)オプションに相当する機能が用意されていない点です。lessだと-Xオプションを指定しておくとlessで表示していた画面がlessをquitした後もそのままの状態でターミナル上に残ります。lessで情報を表示した後に、それを参照しつつコマンド実行する、というケースにはこの機能が必須なのですが・・・

参考:

2015年1月17日土曜日

[cygwin] アップデートで起動しなくなったCygwin-X(1.7.33)の問題の解決方法

windows7にインストールしているcygwinを最新(1.7.33)に更新したところCygwin-Xが起動しなくなってしまいました。ログを確認しネット上の情報を参考に試行錯誤したところ、正常に起動できる状態に復旧することができたので、その内容をまとめておきます。

問題の症状

  • Cygwin-XでXを起動すると、一瞬XWin.exeが起動されるが、すぐに終了してしまう。
  • ログ(/var/log/xwin/XWin.0.log)は以下のようになっており、"winClipboardProc - winClipboardFlushWindowsMessageQueue trapped WM_QUIT message, exiting main loop."というメッセージの出力の通り、WM_QUITメッセージによりメインループを抜けてしまっている。
  • Welcome to the XWin X Server
    Vendor: The Cygwin/X Project
    Release: 1.16.3.0
    OS: CYGWIN_NT-6.1 hostname 1.7.33-2(0.280/5/3) 2014-11-13 15:47 x86_64
    OS: Windows 7 Service Pack 1 [Windows NT 6.1 build 7601] (Win64)
    Package: version 1.16.3-1 built 2014-12-30
    
    XWin was started with the following command line:
    
    /usr/bin/XWin :0 -multiwindow -nolisten tcp -auth 
     /cygdrive/c/Users/username/.serverauth.7540 
    
    ddxProcessArgument - Initializing default screens
    winInitializeScreenDefaults - primary monitor w 1920 h 1200
    winInitializeScreenDefaults - native DPI x 96 y 96
    [   403.995] (II) xorg.conf is not supported
    [   403.995] (II) See http://x.cygwin.com/docs/faq/cygwin-x-faq.html for more information
    [   403.995] LoadPreferences: /cygdrive/c/Users/0000119109/.XWinrc not found
    [   403.995] LoadPreferences: Loading /etc/X11/system.XWinrc
    [   403.995] LoadPreferences: Done parsing the configuration file...
    [   403.995] winDetectSupportedEngines - DirectDraw4 installed, allowing ShadowDDNL
    [   403.995] winDetectSupportedEngines - Returning, supported engines 00000015
    [   403.995] winSetEngine - Multi Window or Rootless => ShadowGDI
    [   403.995] winScreenInit - Using Windows display depth of 32 bits per pixel
    [   404.011] winAllocateFBShadowGDI - Creating DIB with width: 1920 height: 1200 depth: 32
    [   404.011] winFinishScreenInitFB - Masks: 00ff0000 0000ff00 000000ff
    [   404.011] winInitVisualsShadowGDI - Masks 00ff0000 0000ff00 000000ff BPRGB 8 d 24 bpp 32
    [   404.011] MIT-SHM extension disabled due to lack of kernel support
    [   404.026] XFree86-Bigfont extension local-client optimization disabled due to lack of shared memory support in the kernel
    [   404.026] glWinSelectGLimplementation: Loaded 'cygnativeGLthunk.dll'
    [   404.026] (II) AIGLX: Testing pixelFormatIndex 1
    [   404.136] GL_VERSION:     3.3.0 - Build 8.15.10.2712
    [   404.136] GL_VENDOR:      Intel
    [   404.136] GL_RENDERER:    Intel(R) HD Graphics 4000
    [   404.136] (II) AIGLX: enabled GLX_SGI_make_current_read
    [   404.136] (II) AIGLX: enabled GLX_MESA_copy_sub_buffer
    [   404.136] (II) AIGLX: enabled GLX_SGI_swap_control and GLX_MESA_swap_control
    [   404.136] (II) AIGLX: enabled GLX_SGIX_pbuffer
    [   404.136] (II) AIGLX: enabled GLX_ARB_multisample and GLX_SGIS_multisample
    [   404.136] (II) 66 pixel formats reported by wglGetPixelFormatAttribivARB
    [   404.136] (II) AIGLX: Set GLX version to 1.4
    [   404.136] (II) 21 fbConfigs
    [   404.136] (II) ignored pixel formats: 0 not OpenGL, 6 RBGA float, 3 RGBA unsigned float, 0 unknown pixel type, 36 unaccelerated
    [   404.136] (II) GLX: Initialized Win32 native WGL GL provider for screen 0
    [   404.229] winPointerWarpCursor - Discarding first warp: 960 600
    [   404.229] (--) 5 mouse buttons found
    [   404.229] (--) Setting autorepeat to delay=500, rate=31
    [   404.791] (II) Loading US keyboard layout.
    [   404.791] (--) Windows keyboard layout: "E0210411" (00000411) "ATOK 2012", type 7
    [   404.791] (--) Found matching XKB configuration "Japanese"
    [   404.791] (--) Model = "jp106" Layout = "jp" Variant = "none" Options = "none"
    [   404.791] Rules = "base" Model = "jp106" Layout = "jp" Variant = "none" Options = "none"
    [   404.853] winInitMultiWindowWM - DISPLAY=:0.0
    [   404.853] winMultiWindowXMsgProc - DISPLAY=:0.0
    [   404.884] winProcEstablishConnection - winInitClipboard returned.
    [   404.884] winInitMultiWindowWM - XOpenDisplay () returned and successfully opened the display.
    [   404.884] winClipboardThreadProc - DISPLAY=:0.0
    [   404.884] OS maintains clipboard viewer chain: yes
    [   404.884] winMultiWindowXMsgProc - XOpenDisplay () returned and successfully opened the display.
    [   404.884] winClipboardProc - XOpenDisplay () returned and successfully opened the display.
    [   405.134] winClipboardProc - winClipboardFlushWindowsMessageQueue trapped WM_QUIT message, exiting main loop. <=== ここ!
    [   405.134] winClipboardProc - XDestroyWindow succeeded.
    [   405.134] winClipboardIOErrorHandler!
    [   405.134] winMultiWindowXMsgProcIOErrorHandler!
    [   405.134] winInitMultiWindowXMsgProc - Caught IO Error.  Exiting.
    [   405.134] winDeinitMultiWindowWM - Noting shutdown in progress
    [   405.134] (EE) Server terminated successfully (0). Closing log file.
    
    

調査の結果わかったこと

  • HOMEディレクトリに自前の.startxwinrcを用意していると問題が発生する
    • デフォルトの/etc/X11/xinit/startxwinrcのスクリプトが走れば起動する
  • スクリプトで起動している/usr/bin/fbpanelの有無で再現性が変わる

解決方法

二通りの解決方法があります。
  1. Xwin.exeを直起動する
    • Cygwin-Xのアイコン(ショートカット)のプロパティを開き、「リンク先」を以下のように変更すればよい。
      • (デフォルト)C:\cygwin64\bin\run.exe --quote /usr/bin/bash.exe -l -c "cd; /usr/bin/startxwin"
      • (修正後)C:\cygwin64\bin\run.exe --quote /usr/bin/bash.exe -l -c "cd; /usr/bin/XWin.exe :0 -multiwindow"
    • この方法だとHOMEディレクトリ以下の.startxwinrcがロードされないので注意してください(.xinitrc, .Xclientsもロードされない模様)。
  2. 自前の.startxwinrcで/usr/bin/fbpanelを起動する
    • fbpanelを起動すると画面左上の最前面に常にXアイコンが表示される状態になり、非常にジャマです。fbpanelを使いたい!という人以外には、この方法はおすすめしません。
残念ながら、根本原因を突き止めることはできていません。環境によってはこの解決策は意図した通り機能しないかも…。

参考情報

いずれも自分の環境では解決には至りませんでしたが、参考まで。

(2016/11/08追記)

cygwinを最新版(2.6.0)にアップデートしたところ、インストールしたままのデフォルトの状態でXサーバーが起動できるようになっていることが分かりました。逆に、本エントリで紹介しているXWin.exeを直に起動する手順を実行するとXサーバーの起動に失敗する(いつまでまっても起動されない)のでご注意を。


2015年1月15日木曜日

[haskell][cygwin] cygwinのシェル上でcabal install cabal-installが失敗する問題の対処方法

Windows7上でcabal install cabal-installがエラーになる問題に遭遇しました。その症状と解決方法をまとめておきます。

問題の症状

Windows7にHaskell Platform 2014.2.0.0をインストールしている環境でcabal install cabal-installを実行すると、以下のようなエラーとなりインストールに失敗してしまいます。cygwinは 1.7.33-2。
% cabal install cabal-install
Resolving dependencies...
Configuring Cabal-1.22.0.0...
Failed to install Cabal-1.22.0.0
Last 10 lines of the build log ( C:\Users\XXX\AppData\Roaming\cabal\logs\Cabal-1.22.0.0.log ):
[73 of 78] Compiling Distribution.Simple.UserHooks ( C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\Distribution\Simple\UserHooks.hs, C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\dist\setup\Distribution\Simple\UserHooks.o )
[74 of 78] Compiling Distribution.Simple.Bench ( C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\Distribution\Simple\Bench.hs, C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\dist\setup\Distribution\Simple\Bench.o )
[75 of 78] Compiling Distribution.Simple.Test.ExeV10 ( C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\Distribution\Simple\Test\ExeV10.hs, C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\dist\setup\Distribution\Simple\Test\ExeV10.o )
[76 of 78] Compiling Distribution.Simple.Test ( C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\Distribution\Simple\Test.hs, C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\dist\setup\Distribution\Simple\Test.o )
[77 of 78] Compiling Distribution.Simple ( C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\Distribution\Simple.hs, C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\dist\setup\Distribution\Simple.o )
[78 of 78] Compiling Main             ( C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\Setup.hs, C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\dist\setup\Main.o )Linking C:\Users\000011~1\AppData\Local\Temp\Cabal-1.22.0.0-10368\Cabal-1.22.0.0\dist\setup\setup.exe ...
Configuring Cabal-1.22.0.0...
setup.exe: fd:4: invalid argument
setup.exe: fd:4: hGetContents: invalid argument (invalid byte sequence)
cabal.exe: Error: some packages failed to install:
Cabal-1.22.0.0 failed during the configure step. The exception was:
ExitFailure 1
cabal-install-1.22.0.0 depends on Cabal-1.22.0.0 which failed to install.


調べてわかったこと

自分の環境では/bin/tcshをシェルに設定していますが、/bin/bashに変えても症状は改善せず。
cygwin上で自動的に先頭に登録されるパス(/usr/bin)と環境変数LANG=ja_JP.UTF-8が原因。

解決方法

PATHの設定で、mingwのバイナリが格納されたディレクトリ(C:\Program Files\Haskell Platform\2014.2.0.0\mingw\bin)をcygwinのディレクトリ(/usr/bin, /usr/local/bin)よりも前書いておく。
cygwinはシェル起動スクリプトで/usr/binと/usr/local/binを勝手にPATHの先頭に追加してしまします。ここの情報を参考に順番を整理してください。
この設定をした上で、以下の手順のどちらかを実行すれば問題を回避することができます。
  1. コマンドプロンプト上で実行する
  2. コマンドプロント(cmd.exe)を起動してcabal install cabal-installを実行すると、以下の通り正常にインストールすることができました。 なぜcygwin上だと失敗してしまうのか。ディレクトリセパレータの扱いがコマンドプロンプト上とcygwin上で変わっているのが原因ではないか、と疑ってはいますが正確なところは調べられていません・・・。
    C:\Windows\system32>cabal install cabal-install
    Resolving dependencies...
    Configuring Cabal-1.22.0.0...
    Building Cabal-1.22.0.0...
    Installed Cabal-1.22.0.0
    Configuring cabal-install-1.22.0.0...
    Building cabal-install-1.22.0.0...
    Installed cabal-install-1.22.0.0
    
  3. cygwin環境のLANG環境変数を削除する
  4. コマンドプロンプトではなく、cygwinのターミナル上発生する問題を回避するには、LANG環境変数を変更する必要があります。cabalに以下のaliasを設定しておけば、cabal実行時のみLANG環境変数を無効にできます。
    alias cabal env LANG= cabal
    

参考: