GHC 9.4+: Clang on Windows doesn't define _UCRT macro
(Originally spun out of a discussion at https://github.com/Mistuke/CabalChoco/issues/5.)
I discovered recently that hsc2hs-0.68.8
fails to build on Windows with GHC 9.4.2 when /mingw64/include
is added to the C_INCLUDE_PATH
:
$ cabal install hsc2hs -w ghc-9.4.2
Resolving dependencies...
Build profile: -w ghc-9.4.2 -O1
In order, the following will be built (use -v for more details):
- hsc2hs-0.68.8 (exe:hsc2hs) (requires build)
Starting hsc2hs-0.68.8 (exe:hsc2hs)
Building hsc2hs-0.68.8 (exe:hsc2hs)
Failed to build exe:hsc2hs from hsc2hs-0.68.8.
Build log (
C:\cabal\logs\ghc-9.4.2\hsc2hs-0.68.8-dca6bac3c0afc2e67bbbc32a66df4cfb082b5015.log
):
Preprocessing executable 'hsc2hs' for hsc2hs-0.68.8..
Building executable 'hsc2hs' for hsc2hs-0.68.8..
[ 1 of 12] Compiling ATTParser ( src\ATTParser.hs, dist\build\hsc2hs\hsc2hs-tmp\ATTParser.o )
[ 2 of 12] Compiling Compat.ResponseFile ( src\Compat\ResponseFile.hs, dist\build\hsc2hs\hsc2hs-tmp\Compat\ResponseFile.o )
[ 3 of 12] Compiling Compat.TempFile ( src\Compat\TempFile.hs, dist\build\hsc2hs\hsc2hs-tmp\Compat\TempFile.o )
[ 4 of 12] Compiling Common ( src\Common.hs, dist\build\hsc2hs\hsc2hs-tmp\Common.o )
[ 5 of 12] Compiling Flags ( src\Flags.hs, dist\build\hsc2hs\hsc2hs-tmp\Flags.o )
[ 6 of 12] Compiling HSCParser ( src\HSCParser.hs, dist\build\hsc2hs\hsc2hs-tmp\HSCParser.o )
[ 7 of 12] Compiling C ( src\C.hs, dist\build\hsc2hs\hsc2hs-tmp\C.o )
[ 8 of 12] Compiling CrossCodegen ( src\CrossCodegen.hs, dist\build\hsc2hs\hsc2hs-tmp\CrossCodegen.o )
[ 9 of 12] Compiling Paths_hsc2hs ( dist\build\hsc2hs\autogen\Paths_hsc2hs.hs, dist\build\hsc2hs\hsc2hs-tmp\Paths_hsc2hs.o )
[10 of 12] Compiling UtilsCodegen ( src\UtilsCodegen.hs, dist\build\hsc2hs\hsc2hs-tmp\UtilsCodegen.o )
[11 of 12] Compiling DirectCodegen ( src\DirectCodegen.hs, dist\build\hsc2hs\hsc2hs-tmp\DirectCodegen.o )
[12 of 12] Compiling Main ( src\Main.hs, dist\build\hsc2hs\hsc2hs-tmp\Main.o )
[13 of 13] Linking dist\\build\\hsc2hs\\hsc2hs.exe
ld.lld: error: undefined symbol: swprintf_s
>>> referenced by dist\build\hsc2hs\hsc2hs-tmp\cbits\utils.o:(__get_temp_file_name)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ghc-9.4.2.exe: `clang.exe' failed in phase `Linker'. (Exit code: 1)
Error: cabal-3.8.1.0.exe: Failed to build exe:hsc2hs from hsc2hs-0.68.8. See
the build log above for details.
This is unexpected, since the Clang toolchain that comes bundled with GHC 9.4.2 uses the same header files as MinGW-w64. I minimized the issue down to the following test case:
// hello.c
#include <stdio.h>
#include <wchar.h>
void hello(wchar_t *buf) {
swprintf_s(buf, 12, L"hello");
}
-- Main.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module Main (main) where
#if defined(i386_HOST_ARCH)
# define WINDOWS_CCONV stdcall
#elif defined(x86_64_HOST_ARCH)
# define WINDOWS_CCONV ccall
#else
# error Unknown mingw32 arch
#endif
import Foreign.C.String (peekCWString)
import Foreign.C.Types (CWchar)
import Foreign.Marshal.Alloc (allocaBytes)
import Foreign.Ptr (Ptr)
foreign import WINDOWS_CCONV "hello" c_hello :: Ptr CWchar -> IO ()
main :: IO ()
main = allocaBytes 12 $ \buf -> do
c_hello buf
str <- peekCWString buf
putStrLn str
If you compile this without C_INCLUDE_PATH
defined, then it succeeds:
$ C_INCLUDE_PATH="" ghc Main.hs hello.c -fforce-recomp -o Main.exe && ./Main.exe
[1 of 2] Compiling Main ( Main.hs, Main.o )
[2 of 2] Linking Main.exe
hello
On the other hand, if you do define C_INCLUDE_PATH
, then it fails to link:
$ C_INCLUDE_PATH=/mingw64/include ghc Main.hs hello.c -fforce-recomp -o Main.exe && ./Main.exe
[1 of 2] Compiling Main ( Main.hs, Main.o )
[2 of 2] Linking Main.exe [Objects changed]
ld.lld: error: undefined symbol: swprintf_s
>>> referenced by hello.o:(hello)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ghc-9.4.2.exe: `clang.exe' failed in phase `Linker'. (Exit code: 1)
According to @Phyx, the reason this happens is because GHC is not defining the _UCRT
CPP macro, which is crucial for linking against UCRT (as Clang is set up to do). Indeed, if I repeat the last step with -D_UCRT
, then it succeeds:
$ C_INCLUDE_PATH=/mingw64/include ghc Main.hs hello.c -fforce-recomp -o Main.exe -D_UCRT && ./Main.exe
[1 of 2] Compiling Main ( Main.hs, Main.o )
[2 of 2] Linking Main.exe
hello
@Phyx says that GHC should define the _UCRT
macro, just like it defines the WINVER
macro.