Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/compiler/crystal/codegen/link.cr
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{% if flag?(:msvc) %}
require "crystal/system/win32/visual_studio"
require "crystal/system/win32/windows_sdk"
{% end %}

module Crystal
struct LinkAnnotation
getter lib : String?
Expand Down Expand Up @@ -272,6 +277,43 @@ module Crystal
end
end

# Detects the current MSVC linker and the relevant linker flags that
# recreate the MSVC developer prompt's standard library paths. If both MSVC
# and the Windows SDK are available, the linker will be an absolute path and
# the linker flags will contain the `/LIBPATH`s for the system libraries.
#
# Has no effect if the host compiler is not using MSVC.
def msvc_compiler_and_flags : {String, Array(String)}
linker = Compiler::MSVC_LINKER
link_args = [] of String

{% if flag?(:msvc) %}
if msvc_path = Crystal::System::VisualStudio.find_latest_msvc_path
if win_sdk_libpath = Crystal::System::WindowsSDK.find_win10_sdk_libpath
host_bits = {{ flag?(:aarch64) ? "ARM64" : flag?(:bits64) ? "x64" : "x86" }}
target_bits = has_flag?("aarch64") ? "arm64" : has_flag?("bits64") ? "x64" : "x86"

# MSVC build tools and Windows SDK found; recreate `LIB` environment variable
# that is normally expected on the MSVC developer command prompt
link_args << "/LIBPATH:#{msvc_path.join("atlmfc", "lib", target_bits)}"
link_args << "/LIBPATH:#{msvc_path.join("lib", target_bits)}"
link_args << "/LIBPATH:#{win_sdk_libpath.join("ucrt", target_bits)}"
link_args << "/LIBPATH:#{win_sdk_libpath.join("um", target_bits)}"

# use exact path for compiler instead of relying on `PATH`, unless
# explicitly overridden by `%CC%`
# (letter case shouldn't matter in most cases but being exact doesn't hurt here)
unless ENV.has_key?("CC")
target_bits = target_bits.sub("arm", "ARM")
linker = msvc_path.join("bin", "Host#{host_bits}", target_bits, "cl.exe").to_s
end
end
end
{% end %}

{linker, link_args}
end

PKG_CONFIG_PATH = Process.find_executable("pkg-config")

# Returns the result of running `pkg-config mod` but returns nil if
Expand Down
35 changes: 5 additions & 30 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ require "colorize"
require "crystal/digest/md5"
{% if flag?(:msvc) %}
require "./loader"
require "crystal/system/win32/visual_studio"
require "crystal/system/win32/windows_sdk"
{% end %}

module Crystal
Expand All @@ -27,8 +25,8 @@ module Crystal
# A Compiler parses source code, type checks it and
# optionally generates an executable.
class Compiler
private DEFAULT_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cc" }}
private MSVC_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cl.exe" }}
DEFAULT_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cc" }}
MSVC_LINKER = ENV["CC"]? || {{ env("CRYSTAL_CONFIG_CC") || "cl.exe" }}

# A source to the compiler: its filename and source code.
record Source,
Expand Down Expand Up @@ -409,32 +407,9 @@ module Crystal
object_arg = Process.quote_windows(object_names)
output_arg = Process.quote_windows("/Fe#{output_filename}")

linker = MSVC_LINKER
link_args = [] of String

# if the compiler and the target both have the `msvc` flag, we are not
# cross-compiling and therefore we should attempt detecting MSVC's
# standard paths
{% if flag?(:msvc) %}
if msvc_path = Crystal::System::VisualStudio.find_latest_msvc_path
if win_sdk_libpath = Crystal::System::WindowsSDK.find_win10_sdk_libpath
host_bits = {{ flag?(:aarch64) ? "ARM64" : flag?(:bits64) ? "x64" : "x86" }}
target_bits = program.has_flag?("aarch64") ? "arm64" : program.has_flag?("bits64") ? "x64" : "x86"

# MSVC build tools and Windows SDK found; recreate `LIB` environment variable
# that is normally expected on the MSVC developer command prompt
link_args << Process.quote_windows("/LIBPATH:#{msvc_path.join("atlmfc", "lib", target_bits)}")
link_args << Process.quote_windows("/LIBPATH:#{msvc_path.join("lib", target_bits)}")
link_args << Process.quote_windows("/LIBPATH:#{win_sdk_libpath.join("ucrt", target_bits)}")
link_args << Process.quote_windows("/LIBPATH:#{win_sdk_libpath.join("um", target_bits)}")

# use exact path for compiler instead of relying on `PATH`
# (letter case shouldn't matter in most cases but being exact doesn't hurt here)
target_bits = target_bits.sub("arm", "ARM")
linker = Process.quote_windows(msvc_path.join("bin", "Host#{host_bits}", target_bits, "cl.exe").to_s) unless ENV.has_key?("CC")
end
end
{% end %}
linker, link_args = program.msvc_compiler_and_flags
linker = Process.quote_windows(linker)
link_args.map! { |arg| Process.quote_windows(arg) }

link_args << "/DEBUG:FULL /PDBALTPATH:%_PDB%" unless debug.none?
link_args << "/INCREMENTAL:NO /STACK:0x800000"
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/crystal/interpreter/context.cr
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,13 @@ class Crystal::Repl::Context
# (MSVC doesn't seem to have this issue)
args.delete("-lgc")

# recreate the MSVC developer prompt environment, similar to how compiled
# code does it in `Compiler#linker_command`
if program.has_flag?("msvc")
_, link_args = program.msvc_compiler_and_flags
args.concat(link_args)
end

Crystal::Loader.parse(args, dll_search_paths: dll_search_paths).tap do |loader|
# FIXME: Part 2: This is a workaround for initial integration of the interpreter:
# We append a handle to the current executable (i.e. the compiler program)
Expand Down