標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

Windows で使える make

mingw32-make.exe

MinGW でインストール可能な GNU Make。フルセットなので、一般によく読まれる make の解説記事どおりの Makefile を利用することができる。

問題としてはコマンド名が長くて、シェルでコマンド名を補完しようとすると mingw-get-setup.exeが邪魔で、最低 mingw3 とまで打たなくてはいけない。

UNIX系なのでドライブレター系パスの認識が懸念されるが、Makefile ではフルパスを使うことはないので心配する必要はないだろう

コマンドラインの解釈に CMD.EXE を使いたい時は明示的に SHELL:=CMD.EXE と書いておくのが安全だ。

nmake.exe

Visual Studio に入ってくる

  • 通常は Visual Studio Command Prompt の中からでないと PATH が通っていない場所にある (vcvarsall.bat などで PATH が通る)
  • 他の make と互換性のない独自拡張文法が多い
    • 実は Undocumented な拡張がある:SETコマンドが nmake 内蔵処理となっている
      • 設定された環境変数の値が次の行まで継続する
      • CMD.EXE のような SET "FOO=BAR" のような記述ができない
  • 添付元の Visual StudioVC++ のランタイムが必要(例:MSVCR100.DLL 、 VCRUNTIME140.DLL など…)

scoop でインストールできる make.exe

2021/05/15 現在、scoop install make を実行すると、make-4.3-without-guile-w32-bin.zip というパッケージがインストールされる。普通に GNU Make のようだ。一番、無難な make といえる。

どの make でも触りなく動作する Makefile を書くには?

検索したが、世の中 GNU make がほぼ標準扱いで、そういった資料はなかなかないが、「make posix」で検索するとかろうじて英語系のページが見つかる。

とりあえず、共通する特殊マクロをリストアップしてみたが、道はなかなか険しそうだ(ここで自分は諦めた)

特殊マクロ

機能 POSIX nmake 備考
$@ OK OK ターゲットファイル名
$? OK OK ターゲットファイルよりも新しい依存ファイルのリスト
$* OK※ OK 拡張子をのぞいたターゲットファイル名(※推論規則のコマンドでのみ有効な場合がある)
$% OK - ターゲットがライブラリファイル(*.a)の時にそのメンバー
$< OK - 依存ファイル(推論規則のコマンドでのみ有効)
$** - OK すべての依存ファイル

私的 goawk リポート

benhoyt/goawk: A POSIX-compliant AWK interpreter written in Go

  • Go言語による gawk 互換処理系
  • Windows では Unicode-APIを利用しているため、コンソールに UTF8 を出力しても、化けずに表示される(逆にいうと、CP932 は対応していない)
  • シングルクォートをダブルクォート同様に使えるので、Windowsワンライナーに優しい
    (例:goawk "BEGIN { print 'foo', 42 }"
  • 組み込み用途にも使える(が、個人的にはそういう用途には GopherLua を使うので、詳しくは調べるつもりはない)
  • 文字列の扱いはGo 言語に準じる。つまり
    • 文字列のインデックスはバイト単位
    • しかし、正規表現. などはちゃんとUTF8の1文字に対応している
$ echo あいうえお | goawk "{ s=$0 ; while(match(s,/./) > 0){ print substr(s,RSTART,RLENGTH) ; s=substr(s,RSTART+RLENGTH)} }"
あ
い
う
え
お

なお、Windows版では自前のワイルドカード展開がなかった(~1.7.0)ため、不便だった。 だが、ワイルドカード展開を行うプルリク送ってみたところ、採用はされなかったものの、対応してくれた。ありがたや! まだリリースバイナリには反映されていないみたいなので、使いたい人は自分でビルドしよう!(go build だけでよい)

Before:

$ goawk "FNR==1 { print FILENAME }" *.go
open *.go: The filename, directory name, or volume label syntax is incorrect.

After:

$ goawk "FNR==1 { print FILENAME }" *.go
goawk.go
goawk_test.go
goawk_windows.go

スクリプト言語の元祖である awk が UTF8 で使えるのは嬉しい。引用符拡張のおかげでバッチファイルからも使いやすくなっていて、ツールとして便利よく使えそうだ。

個人的コマンドプロンプト開発体制

たぶん、同じようなことをしている人は皆無だと思うが…コマンドプロンプトベースでなんでもやる人の参考になるかも

基本

ベースはこれ。これで基本ツールをそろえる

作業ベース

作業フォルダーは %USERPROFILE%\go\src\github.com\zetamatta\project みたいな深い場所になる。そこへ cd (chdir) するのは毎回たいへん。そこで次のような nyagos の機能のいずれかを使って簡単に cd できるようにする

  • set "CDPATH=%USERPROFILE%\go\src\github.com\zetamatta" しておいて、zetamatta 以下のフォルダーには名前だけで cd できるようにする
  • lnk %USERPROFILE%\go\src\github.com\zetamatta\project ~\. して、ホームディレクトリの直下に project.lnk というショートカットを作る。これで cd project.lnk だけで移動できる
  • 一度移動したことのあるフォルダーであれば、cd -hディレクトリヒストリを出して、cd -2 とかで移動する

Visual Stdio で開発する場合は、ここから open *.sln すれば IDE が起動する

.NET 開発用に vo に用意

github.com

go.exe 風に Visual Studioコマンドラインツール devenv.com をコールするツール。 これのセールスポイントは、2010~2019までのどの Visual Stdio を呼ぶべきかをカレントディレクトリの .sln ファイルから判断して、その devenv.com を自動的に探し出して呼び出せるところ(%PATH% の設定は不要)。

ところで、なぜ、こんなものを用意したかというと、Visual Studio でも C++ の場合は「バッチビルド」で Debug 版/Release版全部のビルドを一度にできたりするが、.NET の場合はそういうのがないためだ。Debug 版でテストして、Release版を提出する時に切り替えを間違えたりすることがよくある。だが、コマンドプロンプトから vo rebuild -a で、全コンフィギュレーションに対してリビルドをかければ間違いはない

Makefile ではなくて、make.cmd

  • Go や devenv.com を使う限り、実はファイルのタイムスタンプを比較してコマンドを発行したりすることはほとんどない。
  • nmake.exe , mingw32-make.exe はどの環境でも入っているわけでないし、コマンド名が長い

なので、バッチファイルで十分だったりする。自分は次のような make.cmd をよく用意する

setlocal
call :"%1"
endlocal
exit /b

:""
:"all"
   vo rebuild -a
   exit /b

:"debug"
    vo build -d
    exit /b

:"release"
    vo build -r
    exit /b
  • %1"%1" のように二重引用符で囲んでいるのは、引数なしの make が実行された時も :"" というラベルに飛べるようにするため DEATH !

参考:/bin/shに慣れた人に贈るバッチファイルの書き方

nmake と set

nmake では set は CMD.EXE ではなく、nmake 自身の組み込みコマンドらしい

test:
  set foo=ahaha
  echo %%foo%%

% は nmake用の Makefile ではファイル指定子であるため、2つ重ねなくてはいけない

$ nmake

Microsoft (R) Program Maintenance Utility Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

        set foo=ahaha
        echo %foo%
ahaha

二重引用符の扱いについて、CMD.EXE との互換性はない

test:
  set "foo=ahaha"
  echo %%foo%%
  echo %%"foo%%
$ nmake

Microsoft (R) Program Maintenance Utility Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

        set "foo=ahaha"
        echo %foo%
%foo%
        echo %"foo%
ahaha"

CMD.EXE の set を呼ぶには丸括弧で囲めば良い

test1:
  set GOOS=linux & go env GOOS

test2:
  ( set "GOOS=linux" & go env GOOS )
$ nmake test1

Microsoft (R) Program Maintenance Utility Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.                                                                                                                                  set GOOS=linux & go env GOOS
$ nmake test2

Microsoft (R) Program Maintenance Utility Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.                                                                                                                                  ( set "GOOS=linux" & go env GOOS )
linux

メンバーにポインタがなければ、コピーコンストラクタは要らないのか?

#include <cstdio>

class Bar {
public:
    Bar(){ puts("Bar()"); }
    Bar( const Bar & ){ puts("Bar(const Bar &)"); }
    ~Bar(){ puts("~Bar()"); }
};

// no copy-constructor class
class Foo {
    Bar bar1;
};

int main()
{
    Foo foo1;

    Foo foo2(foo1);
    return 0;
}

クラス Foo は、リッチなメンバー bar1 を保持しているが、自身のコピーコンストラクタを持っていない。果たして Foo のコピー時にメンバー bar1 のコピーコンストラクタはちゃんと呼び出されるのか?

$ a.exe
Bar()
Bar(const Bar &)
~Bar()
~Bar()

呼ばれてるっぽい

Windows 8.1(64bit) に 32bit 系 OCX をインストールする

とある OCX を導入しようとしたが、うまくゆかない。手順書には C:\Windows\system32 へコピー後、regsvr32 C:\windows\system32\xxxxx.ocx とあるが、コピーそのものができないのか、regsvr32 がファイルが見つからないというエラーが出る。

(無論、管理者権限にはしている。本来はここでエラーメッセージを引用すべきだが、実は解決して、コピーできなくなってしまったw)

そして、手製のバージョン確認ツールでも同ファイルを見ようとすると、同じようなエラーが出る。ただし、コピー元(デスクトップ)で実行すると、きちんとバージョン番号が出る。これはセキュリティー的な問題か!?

で、ググってみたところ、OCX の種類は違うが次のようなページにたどり着いた。

ここにある記載を真似て、コピー先を C:\Windows\System32 ではなく、C:\Windows\SysWOW64 にしてみたところ、regsvr32 も問題なく OCX を読み込むことができるようになった。wfile にも大丈夫だった。

その後、リポート用に上のエラーを再現させようと、C:\Windows\System32 に置いたバージョンについて wfile を実行してみたところ、エラーが出なくなってしまった! このあたり、Windows のパスを差し替えるセキュリティー機能のせいだと思うけど、なかなか難しい話だなぁ (どういうルールでこうなったか、よく分かっていない)

あれ? tar --remove-files -cvf って、ディレクトリも削除できるのか?

tar --remove-files -cvf ARCHIVE.tar DIRECTORY って、ディレクトリをアーカイブしてからその中のファイルをアーカイブするから、まだ中のファイルが残っている時点でディレクトリを削除することはできない。それゆえ、実行後、ディレクトリが残ってしまうのは、仕方がない

と思ってたんだけど、なんか、こんな記事を見つけてしまった。

--remove-files bug was fixed in tar-1.19.

え、バグだったの?ちょっと Windows10 で試してみた。

標準の tar (C:\WINDOWS\system32\tar.exe)は BSD tar なので、--remove-files を持っている GNU tar で試さなきゃいけない。scoop install tar でインストールしてみると、

$ scoop\shims\tar.exe --version
tar (GNU tar) 1.23
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://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.

Written by John Gilmore and Jay Fenlason.

よしよし。では a ディレクトリ以下を tar アーカイブへ移動してみよう

scoop/shims/tar.exe --remove-files -cvf a.tar a/

あ、ちゃんと a フォルダーや、a の下のサブディレクトリも消えた。マジでバグだったのか?

ただ、リンク先を見ると、ファイルがアーカイブされている間に変更があった時に警告を出す処理で、ディレクトリまで対象になってたから例外にしたような修正みたいな書き方になってる。ということは、もっと前のバージョンで直ってたのかなぁ。よく分からない


ところで、zip -m の場合は、アーカイブファイルを全部作ってから、ファイル・ディレクトリを削除するので、ディレクトリが残るという問題もないし、途中で中断した場合なんかも確か元ファイルが消されることはなかったように思う(たぶん)

だが、tar --remove-files は、アーカイブした尻からファイルを削除するので、途中でうっかり中断して、再実行したりすると、ファイルの一部が完全に失われてしまう事故が発生する。なので、あんまり tar --remove-files は使わない方がよい。
(実際、学生の頃に事故った。まぁ、卒論提出後だったので、実害はなくてたすかったが…)