Skip to content

Commit 6caea6a

Browse files
authored
Fix static linking when using MinGW-w64 (crystal-lang#15167)
* The MinGW-w64 equivalent for MSVC's `libcmt.lib` or `msvcrt.lib` is provided by MinGW-w64's built-in spec files directly (see `cc -dumpspecs`), so we do not link against it. * There cannot be static libraries for the Win32 APIs in MinGW-w64, because that would be proprietary code; all static libraries on MSYS2 link against the C runtimes dynamically, i.e. they behave like `/MD` in MSVC or `CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL` in CMake. We therefore never link against `libucrt`. * Passing `-lucrt` explicitly may lead to a crash in the startup code when combined with `-static` and `-msvcrt`, depending on the relative link order. In MinGW-w64, [`-lmsvcrt` is already equivalent to `-lucrt` or `-lmsvcrt-os`](https://gcc.gnu.org/onlinedocs/gcc/Cygwin-and-MinGW-Options.html), depending on how it was built; in particular, `/ucrt64/bin/libmsvcrt.a` is a copy of `libucrt.a` in MSYS2, but `/mingw64/bin/libmsvcrt.a` is a copy of `libmsvcrt-os.a`. Thus we drop `-lucrt` entirely and rely on the MinGW-w64's build-time configuration to select the appropriate C runtime. * `-mcrtdll` can be used to override the C runtime, and it _should_ be possible to cross-build binaries between the MINGW64 and the UCRT64 environments using this flag. The interpreter now imitates this linker behavior.
1 parent d9cb484 commit 6caea6a

5 files changed

Lines changed: 21 additions & 12 deletions

File tree

.github/workflows/mingw-w64.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ jobs:
8080
shell: msys2 {0}
8181
run: |
8282
mkdir bin
83-
cc crystal.obj -o bin/crystal.exe \
83+
cc crystal.obj -o bin/crystal.exe -municode \
8484
$(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \
8585
$(llvm-config --libs --system-libs --ldflags) \
8686
-lole32 -lWS2_32 -Wl,--stack,0x800000

src/compiler/crystal/loader/mingw.cr

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ require "crystal/system/win32/library_archive"
77
# The core implementation is derived from the MSVC loader. Main deviations are:
88
#
99
# - `.parse` follows GNU `ld`'s style, rather than MSVC `link`'s;
10+
# - `.parse` automatically inserts a C runtime library if `-mcrtdll` isn't
11+
# supplied;
1012
# - `#library_filename` follows the usual naming of the MinGW linker: `.dll.a`
1113
# for DLL import libraries, `.a` for other libraries;
1214
# - `.default_search_paths` relies solely on `.cc_each_library_path`.
@@ -28,6 +30,11 @@ class Crystal::Loader
2830
file_paths = [] of String
2931
extra_search_paths = [] of String
3032

33+
# note that `msvcrt` is a default runtime chosen at MinGW-w64 build time,
34+
# `ucrt` is always UCRT (even in a MINGW64 environment), and
35+
# `msvcrt-os` is always MSVCRT (even in a UCRT64 environment)
36+
crt_dll = "msvcrt"
37+
3138
OptionParser.parse(args.dup) do |parser|
3239
parser.on("-L DIRECTORY", "--library-path DIRECTORY", "Add DIRECTORY to library search path") do |directory|
3340
extra_search_paths << directory
@@ -39,17 +46,21 @@ class Crystal::Loader
3946
raise LoadError.new "static libraries are not supported by Crystal's runtime loader"
4047
end
4148
parser.unknown_args do |args, after_dash|
42-
file_paths.concat args
49+
file_paths.concat args.reject(&.starts_with?("-mcrtdll="))
4350
end
4451

4552
parser.invalid_option do |arg|
46-
unless arg.starts_with?("-Wl,")
53+
if crt_dll_arg = arg.lchop?("-mcrtdll=")
54+
# the GCC spec is `%{!mcrtdll=*:-lmsvcrt} %{mcrtdll=*:-l%*}`
55+
crt_dll = crt_dll_arg
56+
elsif !arg.starts_with?("-Wl,")
4757
raise LoadError.new "Not a recognized linker flag: #{arg}"
4858
end
4959
end
5060
end
5161

5262
search_paths = extra_search_paths + search_paths
63+
libnames << crt_dll
5364

5465
begin
5566
loader = new(search_paths)

src/crystal/system/win32/wmain.cr

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ require "c/stringapiset"
22
require "c/winnls"
33
require "c/stdlib"
44

5-
{% begin %}
6-
# we have both `main` and `wmain`, so we must choose an unambiguous entry point
5+
# we have both `main` and `wmain`, so we must choose an unambiguous entry point
6+
{% if flag?(:msvc) %}
77
@[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})]
8-
{% if flag?(:msvc) %}
9-
@[Link(ldflags: "/ENTRY:wmainCRTStartup")]
10-
{% elsif flag?(:gnu) && !flag?(:interpreted) %}
11-
@[Link(ldflags: "-municode")]
12-
{% end %}
8+
@[Link(ldflags: "/ENTRY:wmainCRTStartup")]
9+
{% elsif flag?(:gnu) && !flag?(:interpreted) %}
10+
@[Link(ldflags: "-municode")]
1311
{% end %}
1412
lib LibCrystalMain
1513
end

src/empty.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
require "primitives"
22

3-
{% if flag?(:win32) %}
3+
{% if flag?(:msvc) %}
44
@[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] # For `mainCRTStartup`
55
{% end %}
66
lib LibCrystalMain

src/lib_c.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% if flag?(:win32) %}
1+
{% if flag?(:msvc) %}
22
@[Link({{ flag?(:static) ? "libucrt" : "ucrt" }})]
33
{% end %}
44
lib LibC

0 commit comments

Comments
 (0)