Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix msvc compilation #132

Merged
merged 12 commits into from
Sep 9, 2024
29 changes: 20 additions & 9 deletions lib/bundlex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,27 @@ defmodule Bundlex do
@spec get_target() :: target()
case System.fetch_env("CROSSCOMPILE") do
:error ->
def get_target() do
[architecture, vendor, os | maybe_abi] =
:erlang.system_info(:system_architecture) |> List.to_string() |> String.split("-")
if Platform.get_target!() in [:windows32, :windows64] do
def get_target() do
%{
architecture: System.fetch_env!("PROCESSOR_ARCHITECTURE"),
vendor: "pc",
os: "windows",
abi: "unknown"
}
end
else
def get_target() do
[architecture, vendor, os | maybe_abi] =
:erlang.system_info(:system_architecture) |> List.to_string() |> String.split("-")

%{
architecture: architecture,
vendor: vendor,
os: os,
abi: List.first(maybe_abi) || "unknown"
}
%{
architecture: architecture,
vendor: vendor,
os: os,
abi: List.first(maybe_abi) || "unknown"
}
end
end

{:ok, _crosscompile} ->
Expand Down
2 changes: 1 addition & 1 deletion lib/bundlex/build_script.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ defmodule Bundlex.BuildScript do
end

defp join_commands(commands, :windows) do
Enum.join(commands, "\r\n") <> "\r\n"
Enum.join(commands, " && ") <> "\r\n"
end

defp join_commands(commands, _other) do
Expand Down
9 changes: 0 additions & 9 deletions lib/bundlex/helper/path_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,6 @@ defmodule Bundlex.Helper.PathHelper do
@moduledoc false
# Module containing helper functions that ease traversing directories.

@doc """
Tries to find a path that matches given pattern that has the biggest
version number if it is expected to be a suffix.
"""
@spec latest_wildcard(String.t()) :: nil | String.t()
def latest_wildcard(pattern) do
pattern |> Path.wildcard() |> Enum.max(fn -> nil end)
end

@doc """
Fixes slashes in the given path to match convention used on current
operating system.
Expand Down
103 changes: 51 additions & 52 deletions lib/bundlex/toolchain/visual_studio.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ defmodule Bundlex.Toolchain.VisualStudio do
alias Bundlex.Native
alias Bundlex.Output

@directory_wildcard_x64 "c:\\Program Files (x86)\\Microsoft Visual Studio *"
@directory_wildcard_x86 "c:\\Program Files\\Microsoft Visual Studio *"
@directory_env "VISUAL_STUDIO_ROOT"

@impl true
def before_all!(:windows32) do
[run_vcvarsall("x86")]
Expand All @@ -31,16 +27,16 @@ defmodule Bundlex.Toolchain.VisualStudio do
end

@impl true
def compiler_commands(%Native{interface: :nif} = native) do
def compiler_commands(%Native{interface: interface} = native) do
# TODO escape quotes properly

includes_part =
Enum.map_join(native.includes, " ", fn include ->
"/I \"#{PathHelper.fix_slashes(include)}\""
~s(/I "#{PathHelper.fix_slashes(include)}")
end)

sources_part =
Enum.map_join(native.sources, " ", fn source -> "\"#{PathHelper.fix_slashes(source)}\"" end)
Enum.map_join(native.sources, " ", fn source -> ~s("#{PathHelper.fix_slashes(source)}") end)

if not (native.libs |> Enum.empty?()) and not GitHelper.lfs_present?() do
Output.raise(
Expand All @@ -52,22 +48,55 @@ defmodule Bundlex.Toolchain.VisualStudio do

unquoted_dir_part =
native.app
|> Toolchain.output_path(:nif)
|> Toolchain.output_path(interface)
|> PathHelper.fix_slashes()

dir_part = "\"#{unquoted_dir_part}\""
dir_part = ~s("#{unquoted_dir_part}")

common_options = "/nologo"
compile_options = "#{common_options} /EHsc /D__WIN32__ /D_WINDOWS /DWIN32 /O2 /c"
link_options = "#{common_options} /INCREMENTAL:NO /FORCE"

output_path = Toolchain.output_path(native.app, native.name, interface)

commands =
case native do
%Native{type: :native, interface: :nif} ->
[
"(cl #{compile_options} #{includes_part} #{sources_part})",
~s[(link #{link_options} #{libs_part} /DLL /OUT:"#{PathHelper.fix_slashes(output_path)}.dll" *.obj)]
]

%Native{type: :lib} ->
[
"(cl #{compile_options} #{includes_part} #{sources_part})",
~s[(lib /OUT:"#{PathHelper.fix_slashes(output_path)}.lib" *.obj)]
]

%Native{type: type, interface: :nif} when type in [:cnode, :port] ->
[
"(cl #{compile_options} #{includes_part} #{sources_part})",
~s[(link /libpath:"#{:code.root_dir() |> Path.join("lib/erl_interface-5.5.2/lib") |> PathHelper.fix_slashes()}" #{link_options} #{libs_part} /OUT:"#{PathHelper.fix_slashes(output_path)}.exe" *.obj)]
]
end

[
"if EXIST #{dir_part} rmdir /S /Q #{dir_part}",
"mkdir #{dir_part}",
"cl /LD #{includes_part} #{sources_part} #{libs_part} /link /DLL /OUT:\"#{Toolchain.output_path(native.app, native.name, :nif)}.dll\""
"(if exist #{dir_part} rmdir /S /Q #{dir_part})",
"(mkdir #{dir_part})",
"(pushd #{dir_part})",
commands,
"(popd)"
]
|> List.flatten()
end

# Runs vcvarsall.bat script
defp run_vcvarsall(vcvarsall_arg) do
program_files = System.fetch_env!("ProgramFiles(x86)") |> Path.expand()
directory_root = Path.join([program_files, "Microsoft Visual Studio"])

vcvarsall_path =
determine_visual_studio_root()
directory_root
|> build_vcvarsall_path()

case File.exists?(vcvarsall_path) do
Expand All @@ -77,50 +106,20 @@ defmodule Bundlex.Toolchain.VisualStudio do
)

true ->
"if not defined VCINSTALLDIR call \"#{vcvarsall_path}\" #{vcvarsall_arg}"
end
end

# Determines root directory of the Visual Studio.
defp determine_visual_studio_root() do
determine_visual_studio_root(System.get_env(@directory_env))
end

# Determines root directory of the Visual Studio.
# Case when we don't have a root path passed via an environment variable.
defp determine_visual_studio_root(nil) do
visual_studio_path()
|> determine_visual_studio_root_with_wildcard()
end

# Determines root directory of the Visual Studio.
# Case when we have a root path passed via an environment variable.
defp determine_visual_studio_root(directory) do
directory
end

defp determine_visual_studio_root_with_wildcard(wildcard) do
case PathHelper.latest_wildcard(wildcard) do
nil ->
Output.raise(
"Unable to find Visual Studio root directory. Please ensure that it is either located in \"#{wildcard}\" or #{@directory_env} environment variable pointing to its root is set."
)

directory ->
directory
~s/(if not defined VCINSTALLDIR call "#{vcvarsall_path}" #{vcvarsall_arg})/
end
end

# Builds path to the vcvarsall.bat script that can be used to set environment
# variables necessary to use Visual Studio compilers.
defp build_vcvarsall_path(root) do
Path.join([root, "VC", "vcvarsall.bat"])
end
vswhere = Path.join([root, "Installer", "vswhere.exe"])
vswhere_args = ["-property", "installationPath", "-latest"]

defp visual_studio_path() do
case :erlang.system_info(:wordsize) do
4 -> @directory_wildcard_x86
_word_size -> @directory_wildcard_x64
with true <- File.exists?(vswhere),
{maybe_installation_path, 0} <- System.cmd(vswhere, vswhere_args) do
installation_path = String.trim(maybe_installation_path)

Path.join([installation_path, "VC", "Auxiliary", "Build", "vcvarsall.bat"])
|> PathHelper.fix_slashes()
end
end
end
12 changes: 10 additions & 2 deletions test/bundlex/integration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,18 @@ defmodule Bundlex.IntegrationTest do
| env
]

family = Bundlex.family()

{cmd, arg} =
case family do
f when f in [:unix, :custom] -> {"sh", "-c"}
:windows -> {"cmd", "/c"}
end

assert {_output, 0} =
System.cmd(
"sh",
["-c", "#{proj_cmd} 1>&2"],
cmd,
[arg, "#{proj_cmd} 1>&2"],
[cd: project, env: env] ++ opts
)

Expand Down