MinGW-w64 GCC本地化-Windows

让编译器用中文输出

最后更新:

参考资料1

常规MinGw-w64编译步骤(以GCC13.2.0为例)

  1. 环境:Windows-64bit or Linux + Wine-64bit

  2. 安装MSYS2:Install MSYS2 from: http://sourceforge.net/projects/msys2/ (MSYS2 wiki: https://www.msys2.org/wiki/MSYS2-installation/)

  3. 把来自niXman的编译脚本放到以下目录 Get the scripts into <msys root>/home/<user>/mingw-builds: cd && git clone <paste correct url>

    git clone https://github.com/niXman/mingw-builds.git

  4. 在MSYS2环境中删除以下文件夹或重命名 In the MSYS2 file structure delete or rename the /mingw32 and /mingw64 directory.

  5. 从环境变量中删除指向任何预安装的 MinGW 的路径 Delete the paths pointing to any preinstalled MinGW from the PATH environment variable.

  6. 进入以下目录 Go into the MinGW-builds root directory: cd && cd mingw-builds

  7. 编译选项Options

  8. 运行Run: (注意:编译运行前需做本地化修改,详见下文)

1
2
cd mingw-builds
./build --mode=gcc-13.2.0 --buildroot=/c/buildroot --jobs=4 --rev=0 --with-default-msvcrt=ucrt --rt-version=v11 --threads=posix --exceptions=seh --arch=x86_64 --bin-compress --enable-languages=c,c++,fortran
  1. 编译得到的编译器为:
    c:\buildroot\archives\x86_64-13.2.0-release-posix-seh-ucrt-rt_v11-rev0.7z
    其中c:\buildroot为上一步由--buildroot=/c/buildroot指定

本地化需要修改内容(以GCC13.2.0为例)-按参考资料2

  1. 编译前修改编译参数:
    • 脚本文件:scripts/gcc-13.2.0.sh--disable-nls修改为--enable-nls
    • (可选)将仓库中所有文件的--disable-nls修改为--enable-nls
  2. 对GCC13.2.0源码打补丁
    • 参考资料2制作补丁文件patches\gcc\gcc13.2.0-fix-localedir.patch(注意在Windows下要dos2unix)
    • scripts\gcc-13.2.0.sh加入上述补丁文件

结果说明-按参考资料2

  • 情况1,只改第1条
    需依赖C:\msys64\mingw64\share\locale\zh_CN\LC_MESSAGES\gcc.mo才能输出中文。
    其中C:\msys64\mingw64是编译MinGW64 GCC13.2.0时用的编译器的路径。视编译情况需调整。gcc.exe、g++.exe、cc1.exe、cc1plus.exe都依赖这个路径。

  • 情况2,全改第1、2条
    Windows 7/8.1控制台正常。windows 10/11控制台正常输出错误提示时中文乱码(如果换为GCC11.4.0也正常)。
    但在小熊猫C++使用此编译器时,Windows 7/8.1/10/11全部正常。因为其使用管道。
    网友猜测可能原因:
    GCC为了兼容Windows残废的VT序列用力过猛,结果遇到真支持VT序列的Windows就抓瞎了

  • 参考资料2相比参考资料3的优点:

    • 不用再复制一份语言文件
    • 支持中文编译器路径、支持中文源文件路径、支持中文源文件名,均含调试

本地化需要修改内容(以GCC13.2.0为例)-按参考资料3

  1. 编译前修改编译参数:
    • 脚本文件:scripts/gcc-13.2.0.sh--disable-nls修改为--enable-nls
    • (可选)将仓库中所有文件的--disable-nls修改为--enable-nls
  2. 修改gcc源码中gcc\intl.cc文件进行修改,详见下文:参考资料3。
    这里要重点说明的是,gcc的源码是编译脚本自动下载的,正常见情况下无法修改。
    这里提供几个方法:
    • 方法1:参考上文添加补丁文件
    • 方法2:编译完成后去 c:\buildroot\src目录找到gcc-13.2.0\gcc\intl.cc修改,然后重新编译。
    • 方法3:脚本文件:scripts/gcc-13.2.0.sh中有gcc的源码的下载地址,下载下来修改代码后再传到网上(我用了我自己的云服务器),并将这个下载地址修改为你的地址。
1
2
3
4
5
6
7
8
# 脚本文件:scripts/gcc-13.2.0.sh
PKG_VERSION=13.2.0
PKG_NAME=gcc-${PKG_VERSION}
PKG_DIR_NAME=gcc-${PKG_VERSION}  
PKG_TYPE=.tar.xz  
PKG_URLS=(
  "https://ftpmirror.gnu.org/gnu/gcc/gcc-${PKG_VERSION}/gcc-${PKG_VERSION}${PKG_TYPE}"  
)

上述网址即https://ftpmirror.gnu.org/gnu/gcc/gcc-13.2.0/gcc-13.2.0.tar.xz
我这里改为了http://www.yuanpeirong.com/gcc-13.2.0.tar.xz
因资金有限,编译后,现在我已经在我的服务器上删除了,你不用找了

  1. 编译完成后解压编译器:x86_64-13.2.0-release-posix-seh-ucrt-rt_v11-rev0.7z
    mingw64目录下,将share\locale\zh_CN\LC_MESSAGES\*.*拷贝一份至
    libexec\gcc\x86_64-w64-mingw32\下,最后形成的路径为
    libexec\gcc\x86_64-w64-mingw32\share\locale\zh_CN\LC_MESSAGES\*.*

结果说明-按参考资料3

  • 情况1,只改第1条
    需依赖C:\msys64\mingw64\share\locale\zh_CN\LC_MESSAGES\gcc.mo才能输出中文。
    其中C:\msys64\mingw64是编译MinGW64 GCC13.2.0时用的编译器的路径。视编译情况需调整。gcc.exe、g++.exe、cc1.exe、cc1plus.exe都依赖这个路径。

  • 情况2,只改第1、2条
    只有gcc -v、gcc --version、gcc --help能输出中文。

  • 情况3,全改第1、2、3条
    Windows 7/8.1控制台正常。windows 10/11控制台正常输出错误提示时中文乱码。
    但在小熊猫C++使用此编译器时,Windows 7/8.1/10/11全部正常。因为其使用管道。
    网友猜测可能原因:
    GCC为了兼容Windows残废的VT序列用力过猛,结果遇到真支持VT序列的Windows就抓瞎了

用Github Action自动编译

可快速编译各个版本,此处不另外展开

参考资料2:网友cyano.CN的NLS补丁

  • 来源:https://github.com/redpanda-cpp/mingw-lite/blob/nls/patch/gcc-fix-localedir.patch
  • 其修改了gcc-13.2.0/gcc/intl.ccgcc-13.2.0/libcpp/init.cc
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
diff --unified --recursive --text gcc-13.2.0.orig/gcc/intl.cc gcc-13.2.0/gcc/intl.cc
--- gcc-13.2.0.orig/gcc/intl.cc	2023-07-27 16:13:04.000000000 +0800
+++ gcc-13.2.0/gcc/intl.cc	2024-02-14 13:43:07.483162078 +0800
@@ -22,6 +22,12 @@
 #include "coretypes.h"
 #include "intl.h"
 
+#ifdef _WIN32
+#include <string>
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
 #ifdef HAVE_LANGINFO_CODESET
 #include <langinfo.h>
 #endif
@@ -55,7 +61,32 @@
   setlocale (LC_ALL, "");
 #endif
 
+#ifdef _WIN32
+  {
+    /* Find the locale directory.
+       TODO: Use config instead of hard-coded dirs. */
+    char buf[MAX_PATH] = {0};
+    GetModuleFileNameA(NULL, buf, MAX_PATH);
+    std::string exe_path = buf;
+
+    /* \libexec\gcc\<triplet>\<version>\cc1.exe */
+    size_t pos = exe_path.rfind(R"(\libexec\gcc\)");
+    if (pos == std::string::npos) {
+      /* \bin\gcc.exe */
+      pos = exe_path.rfind(R"(\bin\)");
+    }
+
+    if (pos == std::string::npos) {
+      (void) bindtextdomain ("gcc", LOCALEDIR);
+    } else {
+      std::string real_prefix = exe_path.substr(0, pos);
+      std::string win32_locale_dir = real_prefix + "/share/locale";
+      (void) bindtextdomain ("gcc", win32_locale_dir.c_str());
+    }
+  }
+#else
   (void) bindtextdomain ("gcc", LOCALEDIR);
+#endif
   (void) textdomain ("gcc");
 
   /* Opening quotation mark.  */
diff --unified --recursive --text gcc-13.2.0.orig/libcpp/init.cc gcc-13.2.0/libcpp/init.cc
--- gcc-13.2.0.orig/libcpp/init.cc	2023-07-27 16:13:07.000000000 +0800
+++ gcc-13.2.0/libcpp/init.cc	2024-02-14 13:42:44.969838638 +0800
@@ -26,6 +26,12 @@
 #include "localedir.h"
 #include "filenames.h"
 
+#ifdef _WIN32
+#include <string>
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
 #ifndef ENABLE_CANONICAL_SYSTEM_HEADERS
 #ifdef HAVE_DOS_BASED_FILE_SYSTEM
 #define ENABLE_CANONICAL_SYSTEM_HEADERS 1
@@ -181,8 +187,33 @@
       init_trigraph_map ();
 
 #ifdef ENABLE_NLS
+#ifdef _WIN32
+      {
+        /* Find the locale directory.
+           TODO: Use config instead of hard-coded dirs. */
+        char buf[MAX_PATH] = {0};
+        GetModuleFileNameA(NULL, buf, MAX_PATH);
+        std::string exe_path = buf;
+
+        /* \libexec\gcc\<triplet>\<version>\cc1.exe */
+        size_t pos = exe_path.rfind(R"(\libexec\gcc\)");
+        if (pos == std::string::npos) {
+          /* \bin\gcc.exe */
+          pos = exe_path.rfind(R"(\bin\)");
+        }
+
+        if (pos == std::string::npos) {
+          (void) bindtextdomain (PACKAGE, LOCALEDIR);
+        } else {
+          std::string real_prefix = exe_path.substr(0, pos);
+          std::string win32_locale_dir = real_prefix + "/share/locale";
+          (void) bindtextdomain (PACKAGE, win32_locale_dir.c_str());
+        }
+      }
+#else
        (void) bindtextdomain (PACKAGE, LOCALEDIR);
 #endif
+#endif
     }
 }

参考资料3

《MinGW/GCC 编译器修改 gettext 初始化路径使之能在任意位置输出中文消息》
来源:https://blog.csdn.net/hackpascal/article/details/15222083
原文摘抄如下:

GCC 编译器支持 gettext 的本地化,MinGW 也一样,只是可惜他们在内部实现时使用了绝对路径。这个绝对路径的前缀(prefix) 由编译时传递给 configure 的 –prefix 设定,这样就导致了 MinGW 只有在指定的位置上才能实现编译消息本地化。

我所做的就是让 GCC 在初始化 gettext 时使用相对路径,这样就能使 MinGW 在任何地方都能使用本地化的字符串了。包含文件搜索路径也同理。

通过查找 bindtextdomain 函数可以知道 gcc 对 gettext 的初始化在 gcc\intl.cc 中完成。包含文件搜索路径定义在 incpath.c 中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void
gcc_init_libintl (void)
{
#ifdef HAVE_LC_MESSAGES
  setlocale (LC_CTYPE, "");
  setlocale (LC_MESSAGES, "");
#else
  setlocale (LC_ALL, "");
#endif
 
  (void) bindtextdomain ("gcc", LOCALEDIR);
  (void) textdomain ("gcc");
 
  /* Opening quotation mark.  */
  open_quote = _("`");
 
  /* Closing quotation mark.  */
  close_quote = _("'");
 
#if defined HAVE_LANGINFO_CODESET
  locale_encoding = nl_langinfo (CODESET);
  if (locale_encoding != NULL
      && (!strcasecmp (locale_encoding, "utf-8")
	  || !strcasecmp (locale_encoding, "utf8")))
    locale_utf8 = true;
#endif
 
  if (!strcmp (open_quote, "`") && !strcmp (close_quote, "'"))
    {
      /* Untranslated quotes that it may be possible to replace with
	 U+2018 and U+2019; but otherwise use "'" instead of "`" as
	 opening quote.  */
      open_quote = "'";
#if defined HAVE_LANGINFO_CODESET
      if (locale_utf8)
	{
	  open_quote = "\xe2\x80\x98";
	  close_quote = "\xe2\x80\x99";
	}
#endif
    }
}

由于我所做的是针对 Windows 下的修改,因此所有改动都要加上 #ifdef WIN32 … #endif
首先在开头加上:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#ifdef WIN32
#include <windows.h>
 
BOOL DirectoryExists(LPSTR lpszPath)
{
	WIN32_FIND_DATA wfd;
	BOOL bResult = FALSE;
	HANDLE hFind = FindFirstFile(lpszPath, &wfd);
	if ((hFind != INVALID_HANDLE_VALUE) && (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
	{
		bResult = TRUE; 
	}
	FindClose(hFind);
	return bResult;
}
#endif

其中 DirectoryExists 是用来判断路径是否存在的。

然后修改 (void) bindtextdomain (“gcc”, LOCALEDIR); 为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#ifndef WIN32
    (void) bindtextdomain ("gcc", LOCALEDIR);
#else
    DWORD dwSize = MAX_PATH + 20;
    LPSTR lpszName = (LPSTR) xmalloc(dwSize);
    DWORD dwRealSize = GetModuleFileNameA(NULL, lpszName, dwSize) + 1;
	if (dwRealSize > dwSize)
	{
      lpszName = (LPSTR) xrealloc(lpszName, dwSize + 20);
	  GetModuleFileNameA(NULL, lpszName, dwRealSize + 20);
	}
 
	/* 去掉文件名 */
    int l = strlen(lpszName);
	while (l >= 0)
	{
      if (lpszName[l] == '\\')
		  break;
	  l--;
	}
	lpszName[l] = 0;
 
	/* 去掉一层文件夹 */
	l = strlen(lpszName);
	while (l > 0)
	{
      if (lpszName[l] == '\\')
		  break;
	  l--;
	}
 
	/* 判断是否到根路径 */
	if (lpszName[l] != '\\')
		(void) bindtextdomain ("gcc", LOCALEDIR);
	else
	{
		// 连接上 share\locale
		lpszName[l + 1] = 0;
		strcat(lpszName, "share\\locale");
		if (DirectoryExists(lpszName))
		  (void) bindtextdomain ("gcc", lpszName);
		else
		  (void) bindtextdomain ("gcc", LOCALEDIR);
	}
 
	free(lpszName);
#endif

这样重新编译后,无论 MinGW 位置在哪,它都能显示翻译后的消息了。

     转载说明:请附上本文链接及上述版权声明。