diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 33248e93e19b..b2b827916cbf 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -120,18 +120,18 @@ module Crystal end class Program - def lib_flags - has_flag?("msvc") ? lib_flags_windows : lib_flags_posix + def lib_flags(cross_compiling : Bool = false) + has_flag?("msvc") ? lib_flags_windows(cross_compiling) : lib_flags_posix(cross_compiling) end - private def lib_flags_windows + private def lib_flags_windows(cross_compiling) flags = [] of String # Add CRYSTAL_LIBRARY_PATH locations, so the linker preferentially # searches user-given library paths. if has_flag?("msvc") CrystalLibraryPath.paths.each do |path| - flags << Process.quote_windows("/LIBPATH:#{path}") + flags << quote_flag("/LIBPATH:#{path}", cross_compiling) end end @@ -141,14 +141,14 @@ module Crystal end if libname = ann.lib - flags << Process.quote_windows("#{libname}.lib") + flags << quote_flag("#{libname}.lib", cross_compiling) end end flags.join(" ") end - private def lib_flags_posix + private def lib_flags_posix(cross_compiling) flags = [] of String static_build = has_flag?("static") @@ -158,7 +158,7 @@ module Crystal # Add CRYSTAL_LIBRARY_PATH locations, so the linker preferentially # searches user-given library paths. CrystalLibraryPath.paths.each do |path| - flags << Process.quote_posix("-L#{path}") + flags << quote_flag("-L#{path}", cross_compiling) end link_annotations.reverse_each do |ann| @@ -173,17 +173,25 @@ module Crystal elsif (lib_name = ann.lib) && (flag = pkg_config(lib_name, static_build)) flags << flag elsif (lib_name = ann.lib) - flags << Process.quote_posix("-l#{lib_name}") + flags << quote_flag("-l#{lib_name}", cross_compiling) end if framework = ann.framework - flags << "-framework" << Process.quote_posix(framework) + flags << "-framework" << quote_flag(framework, cross_compiling) end end flags.join(" ") end + private def quote_flag(flag, cross_compiling) + if cross_compiling + has_flag?("windows") ? Process.quote_windows(flag) : Process.quote_posix(flag) + else + Process.quote(flag) + end + end + # Searches among CRYSTAL_LIBRARY_PATH, the compiler's directory, and PATH # for every DLL specified in the used `@[Link]` annotations. Yields the # absolute path and `true` if found, the base name and `false` if not found. diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index f620fe2fb312..aa11ef1dc47e 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -354,7 +354,7 @@ module Crystal run_dsymutil(output_filename) unless debug.none? {% end %} - {% if flag?(:windows) %} + {% if flag?(:msvc) %} copy_dlls(program, output_filename) unless static? {% end %} end @@ -424,26 +424,8 @@ module Crystal private def linker_command(program : Program, object_names, output_filename, output_dir, expand = false) if program.has_flag? "msvc" - lib_flags = program.lib_flags - # Execute and expand `subcommands`. - if expand - lib_flags = lib_flags.gsub(/`(.*?)`/) do - command = $1 - begin - error_io = IO::Memory.new - output = Process.run(command, shell: true, output: :pipe, error: error_io) do |process| - process.output.gets_to_end - end - unless $?.success? - error_io.rewind - error "Error executing subcommand for linker flags: #{command.inspect}: #{error_io}" - end - output - rescue exc - error "Error executing subcommand for linker flags: #{command.inspect}: #{exc}" - end - end - end + lib_flags = program.lib_flags(@cross_compile) + lib_flags = expand_lib_flags(lib_flags) if expand object_arg = Process.quote_windows(object_names) output_arg = Process.quote_windows("/Fe#{output_filename}") @@ -487,15 +469,63 @@ module Crystal {linker, cmd, nil} elsif program.has_flag? "wasm32" link_flags = @link_flags || "" - {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags}), object_names} + {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags(@cross_compile)}), object_names} elsif program.has_flag? "avr" link_flags = @link_flags || "" link_flags += " --target=avr-unknown-unknown -mmcu=#{@mcpu} -Wl,--gc-sections" - {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} + {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names} + elsif program.has_flag?("win32") && program.has_flag?("gnu") + link_flags = @link_flags || "" + lib_flags = program.lib_flags(@cross_compile) + lib_flags = expand_lib_flags(lib_flags) if expand + cmd = %(#{DEFAULT_LINKER} #{Process.quote_windows(object_names)} -o #{Process.quote_windows(output_filename)} #{link_flags} #{lib_flags}) + + if cmd.size > 32000 + # The command line would be too big, pass the args through a file instead. + # GCC response file does not interpret those args as shell-escaped + # arguments, we must rebuild the whole command line + args_filename = "#{output_dir}/linker_args.txt" + File.open(args_filename, "w") do |f| + object_names.each do |object_name| + f << object_name.gsub(GCC_RESPONSE_FILE_TR) << ' ' + end + f << "-o " << output_filename.gsub(GCC_RESPONSE_FILE_TR) << ' ' + f << link_flags << ' ' << lib_flags + end + cmd = "#{DEFAULT_LINKER} #{Process.quote_windows("@" + args_filename)}" + end + + {DEFAULT_LINKER, cmd, nil} else link_flags = @link_flags || "" link_flags += " -rdynamic" - {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names} + {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names} + end + end + + private GCC_RESPONSE_FILE_TR = { + " ": %q(\ ), + "'": %q(\'), + "\"": %q(\"), + "\\": "\\\\", + } + + private def expand_lib_flags(lib_flags) + lib_flags.gsub(/`(.*?)`/) do + command = $1 + begin + error_io = IO::Memory.new + output = Process.run(command, shell: true, output: :pipe, error: error_io) do |process| + process.output.gets_to_end + end + unless $?.success? + error_io.rewind + error "Error executing subcommand for linker flags: #{command.inspect}: #{error_io}" + end + output.chomp + rescue exc + error "Error executing subcommand for linker flags: #{command.inspect}: #{exc}" + end end end diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index 71383c66a88a..3dd64f9c1b92 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -4,7 +4,12 @@ require "c/stdlib" {% begin %} # we have both `main` and `wmain`, so we must choose an unambiguous entry point - @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }}, ldflags: "/ENTRY:wmainCRTStartup")] + @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] + {% if flag?(:msvc) %} + @[Link(ldflags: "/ENTRY:wmainCRTStartup")] + {% elsif flag?(:gnu) %} + @[Link(ldflags: "-municode")] + {% end %} {% end %} lib LibCrystalMain end diff --git a/src/lib_c/x86_64-windows-msvc/c/stdio.cr b/src/lib_c/x86_64-windows-msvc/c/stdio.cr index f23bba8503f6..ddfa97235d87 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stdio.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stdio.cr @@ -1,6 +1,8 @@ require "./stddef" -@[Link("legacy_stdio_definitions")] +{% if flag?(:msvc) %} + @[Link("legacy_stdio_definitions")] +{% end %} lib LibC # unused fun printf(format : Char*, ...) : Int