Skip to content

have ld-tcc convert msys2 .dll.a paths to .dll #263

@kzc

Description

@kzc

This issue is related to #259 but is specific to tcc on windows/msys2.

Presently muon converts msys2 pkg-config -l libs into .dll.a paths which error out on tcc. tcc can only handle .dll files and .def files. Changing the .dll.a file extension to .dll alone is not sufficient. The patch below introduces a new linker handler lib_adjust for use by ld-tcc to convert .dll.a paths to (versioned) msys2 .dll paths:

$ dlltool -I c:/msys64/ucrt64/lib/libglib-2.0.dll.a
libglib-2.0-0.dll

$ ls -1 c:/msys64/ucrt64/bin/libglib-2.0-0.dll
c:/msys64/ucrt64/bin/libglib-2.0-0.dll

Note the -0 DLL version in the file name, and the file exists in the bin directory, not the lib directory. The $MINGW_PREFIX of the original .dll.a file (/ucrt64 in this example) is retained.

It is possible that a lib_adjust handler could similarly be added to the msvc 'link' script for the /LIBPATH:xxx issue.

--- a/include/toolchains.h
+++ b/include/toolchains.h
@@ -196,4 +196,5 @@ typedef bool ((*compiler_get_arg_func_1srb)(TOOLCHAIN_SIG_1srb));
 	_(input_output, linker, TOOLCHAIN_PARAMS_2s)           \
 	_(lib, linker, TOOLCHAIN_PARAMS_1s)                    \
+	_(lib_adjust, linker, TOOLCHAIN_PARAMS_1s)             \
 	_(no_undefined, linker, TOOLCHAIN_PARAMS_0)            \
 	_(pgo, linker, TOOLCHAIN_PARAMS_1i)                    \
--- a/src/functions/compiler.c
+++ b/src/functions/compiler.c
@@ -1956,5 +1956,5 @@ FUNC_IMPL(compiler, get_argument_syntax, tc_string, func_impl_flag_impure)
 
 static obj
-find_library_check_dirs(struct workspace *wk, const char *libname, obj libdirs, const char **exts, uint32_t exts_len)
+find_library_check_dirs(struct workspace *wk, obj compiler, const char *libname, obj libdirs, const char **exts, uint32_t exts_len)
 {
 	static const char *pref[] = { "", "lib" };
@@ -1974,4 +1974,13 @@ find_library_check_dirs(struct workspace *wk, const char *libname, obj libdirs,
 
 				if (fs_file_exists(path.buf)) {
+					if (compiler) {
+						const char *adjusted = toolchain_compiler_flatten_one_optional(wk,
+							toolchain_linker_lib_adjust(wk, compiler, path.buf));
+
+						if (adjusted) {
+							return make_str(wk, adjusted);
+						}
+					}
+
 					return tstr_into_str(wk, &path);
 				}
@@ -2008,5 +2017,5 @@ find_library(struct workspace *wk, obj compiler, const char *libname, obj extra_
 	// First check in dirs if the kw is set.
 	if (extra_dirs) {
-		if ((found = find_library_check_dirs(wk, libname, extra_dirs, ext_order, ext_order_len))) {
+		if ((found = find_library_check_dirs(wk, compiler, libname, extra_dirs, ext_order, ext_order_len))) {
 			return (struct find_library_result){ found, find_library_found_location_extra_dirs };
 		}
@@ -2022,5 +2031,5 @@ find_library(struct workspace *wk, obj compiler, const char *libname, obj extra_
 	// Next, check system libdirs
 	if (!found) {
-		if ((found = find_library_check_dirs(wk, libname, comp->libdirs, ext_order, ext_order_len))) {
+		if ((found = find_library_check_dirs(wk, compiler, libname, comp->libdirs, ext_order, ext_order_len))) {
 			return (struct find_library_result){ found, find_library_found_location_system_dirs };
 		}
--- a/src/script/runtime/toolchains.meson
+++ b/src/script/runtime/toolchains.meson
@@ -270,4 +270,6 @@ toolchain.register_linker(
 )
 
+tcc_dll_cache = {}
+
 toolchain.register_linker(
     'ld-tcc',
@@ -293,4 +295,25 @@ toolchain.register_linker(
             return ['-l', lib]
         endfunc,
+        'lib_adjust': func(_c compiler, lib str) -> list[str]
+            # tcc cannot handle msys2 .dll.a files, so convert
+            # the .dll.a path to a .dll path with dlltool.
+            # Cache path pairs to reduce expensive dlltool calls.
+            if tcc_dll_cache.has_key(lib)
+                return [tcc_dll_cache[lib]]
+            endif
+            if lib.endswith('.dll.a') and lib.contains('/msys64/') and lib.contains('/lib/')
+                dlltool = find_program('dlltool', required: false)
+                if dlltool.found()
+                    arr := lib.split('/')
+                    dllname := run_command(dlltool, '-I', lib, check: true).stdout().strip()
+                    if dllname.endswith('.dll') and arr[-2] == 'lib'
+                        dllpath := '/'.join(arr.slice(0, -2) + ['bin', dllname])
+                        tcc_dll_cache[lib] = dllpath
+                        return [dllpath]
+                    endif
+                endif
+            endif
+            return [lib]
+        endfunc,
         'rpath': func(c compiler, s1 str) -> list[str]
             return c.machine().system() == 'windows' ? [] : ['-rpath,' + s1]
--- a/src/toolchains.c
+++ b/src/toolchains.c
@@ -1228,4 +1228,5 @@ toolchain_handler_info_init(struct workspace *wk)
 	doc(input_output, linker, .desc = "Passed in `input` and `output` and orders them properly, optionally adding additional flags.");
 	doc(lib, linker, .desc = "`-l`");
+	doc(lib_adjust, linker, .desc = "Adjust library file path and name.");
 	doc(no_undefined, linker, .desc = "`--no-undefined`");
 	doc(pgo, linker, .desc = "", .enum_arg = "enum compiler_pgo_stage");

Here are the corresponding changes to meson-tests to have the test frameworks/7 gnome work on windows with gcc, clang and tcc:

--- a/common/44 pkgconfig-gen/meson.build	
+++ b/common/44 pkgconfig-gen/meson.build	
@@ -202,5 +202,6 @@ if host_machine.system() == 'windows'
         read1,
         read1
-        + '.replace(\'-lz.dll\',\'-lz\')',
+        + '.replace(\'-lz.dll\',\'-lz\')'
+        + '.replace(\'-lzlib1\',\'-lz\')',
     )
 endif
--- a/frameworks/7 gnome/meson.build	
+++ b/frameworks/7 gnome/meson.build	
@@ -23,4 +23,9 @@ endif
 cc = meson.get_compiler('c')
 
+if cc.get_id() == 'tcc'
+    # Have tcc pretend to be gcc to get glib and gio defines working.
+    add_global_arguments('-D__GNUC__=4', language: 'c')
+endif
+
 add_global_arguments('-DMESON_TEST_1', language: 'c')
 if cc.get_id() == 'intel'
@@ -45,5 +50,9 @@ endif
 gnome = import('gnome')
 gio = dependency('gio-2.0')
-giounix = dependency('gio-unix-2.0')
+if host_machine.system() == 'windows'
+  giounix = dependency('gio-windows-2.0')
+else
+  giounix = dependency('gio-unix-2.0')
+endif
 glib = dependency('glib-2.0')
 gobj = dependency('gobject-2.0')
--- a/frameworks/7 gnome/resources-data/meson.build	
+++ b/frameworks/7 gnome/resources-data/meson.build	
@@ -6,4 +6,5 @@ fake_generator_script = '''
 import os, sys
 assert os.path.exists(sys.argv[1]), "File %s not found" % sys.argv[1]
+sys.stdout.reconfigure(newline='\n') # avoid \r\n on windows
 print("This is a generated resource.")
 '''

Currently muon CI doesn't add the msys2 packages necessary for frameworks/7 gnome to run, so this test is skipped. If the following msys2 packages are added then the test ought to work with all supported msys2 compilers:

libglib-2.0 libintl libgio-2.0 libgobject-2.0 libgmodule-2.0 gio-windows-2.0

Note that doing so would probably add a minute to each msys2 run.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions