26 Apr 2015

MSYS2上のMinGW-w64 GCCでcolored diagnosticsを使う

MSYS2上のMinGW-w64 GCCでcolored diagnostics (-fdiagnostics-color)を使いたかったのでメモ

ネイティブWindows環境ではGCCのcolored diagnosticsが無効化されている.
これはWindowsのネイティブシェル環境(つまりコマンドプロンプト)がANSI color codeに対応していないからだと思われる.
ただし,*nix環境のみでしか動かないようなコードにはなっていない.要はANSI color codeに対応している環境であれば,GCCのビルド時にこの機能を有効化すれば普通に使えるのだ.
基本的にMSYS2のbash上でしか使わないので#ifdef guardを削除して使えるようにしてしまおうと考えたが割りと面倒なことがあった.

何が面倒かというとMSYSやCygwinを使う人なら周知の"mintty上でのisatty問題"のせいでstderrが端末かどうかの判定が出来ず-fdiagnostics-color=autoが動かないということ."mintty上でのisatty問題"が何かわからない人は"mintty isatty"とかでググればいいと思う.
もちろん-fdiagnostics-color=alwaysだと動くがログ用にstderrをファイルにリダイレクトしている時とかに非常に困る.ANSI color codeがそのまま出力される.
常にcatなどでログを表示するという手もあるがそれは何か違う気がする(catはANSI color codeを認識し色付きで表示してくれる).
どうしたものかと考えていたところでちょうどgit for windowsの方で"mintty上でのisatty問題"に対するwork aroundがされたので[1, 2]そのままそっくりパクってGCCに入れて使わせてもらうことにした.
ちなみに今回の件とは関係ないがgit for windowsはMSYSベースからMSYS2ベースへと移行し,git v2.xをリリースする予定である.これに関しては色々思うところはあるし,インターネット上の日本語ベースの情報には間違った内容が非常に多いのだがここでは述べない…

これがGCCへのpatch
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 4ab7405..d230bf7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1004,10 +1004,11 @@ BUILD_LIBDEPS= $(BUILD_LIBIBERTY)
# How to link with both our special library facilities
# and the system's installed libraries.
+NTDLL = -lntdll
LIBS = @LIBS@ libcommon.a $(CPPLIB) $(LIBINTL) $(LIBICONV) $(LIBBACKTRACE) \
- $(LIBIBERTY) $(LIBDECNUMBER) $(HOST_LIBS)
+ $(LIBIBERTY) $(LIBDECNUMBER) $(HOST_LIBS) $(NTDLL)
BACKENDLIBS = $(ISLLIBS) $(GMPLIBS) $(PLUGINLIBS) $(HOST_LIBS) \
- $(ZLIB)
+ $(ZLIB) $(NTDLL)
# Any system libraries needed just for GNAT.
SYSLIBS = @GNAT_LIBEXC@
diff --git a/gcc/diagnostic-color.c b/gcc/diagnostic-color.c
index 3fe49b2..9530058 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -263,20 +263,97 @@ parse_gcc_colors (void)
return true;
}
-#if defined(_WIN32)
-bool
-colorize_init (diagnostic_color_rule_t)
+#ifdef __MINGW32__
+#include <windows.h>
+#include <winternl.h>
+
+typedef struct {
+ HANDLE osfhnd;
+ char osflags;
+} ioinfo;
+
+extern __declspec(dllimport) ioinfo *__pioinfo[];
+
+static size_t sizeof_ioinfo = 0;
+
+#define IOINFO_L2E 5
+#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E)
+
+#define FPIPE 0x08
+#define FDEV 0x40
+
+static inline ioinfo* _pioinfo(int fd)
{
- return false;
+ return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] + (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo);
}
-#else
+
+static int init_sizeof_ioinfo()
+{
+ int istty, wastty;
+ /* don't init twice */
+ if (sizeof_ioinfo)
+ return sizeof_ioinfo >= 256;
+
+ sizeof_ioinfo = sizeof(ioinfo);
+ wastty = _isatty(1);
+
+ while(sizeof_ioinfo < 256) {
+ /* toggle FDEV flag, check isatty, then toggle back */
+ _pioinfo(1)->osflags ^= FDEV;
+ istty = _isatty(1);
+ _pioinfo(1)->osflags ^= FDEV;
+ /* return if we found the correct size */
+ if(istty != wastty)
+ return 0;
+ sizeof_ioinfo += sizeof(void*);
+ }
+ fprintf(stderr, "Tweaking file descriptors doesn't work with this MSVCRT.dll");
+ return 1;
+}
+
+static void detect_msys_tty(int fd)
+{
+ ULONG result;
+ BYTE buffer[1024];
+ POBJECT_NAME_INFORMATION nameinfo = (POBJECT_NAME_INFORMATION)buffer;
+ PWSTR name;
+
+ /* check if fd is a pipe */
+ HANDLE h = (HANDLE)_get_osfhandle(fd);
+ if(GetFileType(h) != FILE_TYPE_PIPE)
+ return;
+
+ /* get pipe name */
+ if(!NT_SUCCESS(NtQueryObject(h, ObjectNameInformation, buffer, sizeof(buffer) - 2, &result)))
+ return;
+ name = nameinfo->Name.Buffer;
+ name[nameinfo->Name.Length] = 0;
+
+ /* check if this could be a msys pty pipe ('msys-XXXX-ptyN-XX') */
+ if(!wcsstr(name, L"msys-") || !wcsstr(name, L"-pty"))
+ return;
+
+ /* init ioinfo size if we haven't done so */
+ if(init_sizeof_ioinfo())
+ return;
+
+ /* set FDEV flag, reset FPIPE flag */
+ _pioinfo(fd)->osflags &= ~FPIPE;
+ _pioinfo(fd)->osflags |= FDEV;
+}
+#endif
/* Return true if we should use color when in auto mode, false otherwise. */
static bool
should_colorize (void)
{
char const *t = getenv ("TERM");
- return t && strcmp (t, "dumb") != 0 && isatty (STDERR_FILENO);
+#ifdef __MINGW32__
+ detect_msys_tty(STDERR_FILENO);
+ return t && strcmp (t, "dumb") && _isatty (STDERR_FILENO);
+#else
+ return t && strcmp (t, "dumb") && isatty (STDERR_FILENO);
+#endif
}
@@ -298,4 +375,3 @@ colorize_init (diagnostic_color_rule_t rule)
gcc_unreachable ();
}
}
-#endif
これで-fdiagnostics-color=autoが使える.

実行結果がこちら
※GCCを--with-diagnostics-color=auto-if-envでビルドし, GCC_COLORSを設定しているためデフォルトで-fdiagnostics-color=autoとなっている

MSYS2のbash & mintty上で使用した例
ちなみにminttyではないwinネイティブなANSI color codeが扱える端末エミュレータであるConEmuなどでも問題なく動く.
MSYS2のbash & winネイティブなConEmu上で使用した例
更に言うとコマンドプロンプト上でもansicon等を使いANSI color codeが使えるとOK.
MSYS2のbash & (windows cmd + ansicon)で使用した例
ちなみに上記のpatchはGCCのgit gcc-5-branchのgit SHAがf6a5ddfであるソースに対して適用したものです.
あとMSYS2ターゲットなgccだとこんなことせずとも,もともとcolored diagnosticsが有効になっているしMSVCRTのisattyではなくmsys-2.0.dllのisattyを使うのでmintty上でも問題なく-fdiagnostics-color=autoを使える.
今回の件はあくまでもMinGW-w64なwinネイティブなGCCをmintty上で動かす時の話である.


[1] https://github.com/git-for-windows/git/pull/28
[2] https://github.com/git-for-windows/git/pull/102

No comments:

Post a Comment