From 6c40274093d83c428930423410ee503dc4981ad1 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Wed, 28 Aug 2024 08:25:44 -0700 Subject: [PATCH 01/11] fix msvc compilation --- lib/bundlex/build_script.ex | 10 +++- lib/bundlex/helper/path_helper.ex | 9 ---- lib/bundlex/toolchain/visual_studio.ex | 63 +++++++------------------- 3 files changed, 25 insertions(+), 57 deletions(-) diff --git a/lib/bundlex/build_script.ex b/lib/bundlex/build_script.ex index aea9cc1..0247b02 100644 --- a/lib/bundlex/build_script.ex +++ b/lib/bundlex/build_script.ex @@ -34,7 +34,7 @@ defmodule Bundlex.BuildScript do def run(%__MODULE__{commands: commands}, platform) do family = Platform.family(platform) - cmd = commands |> join_commands(family) + cmd = commands |> join_commands_run(family) case cmd |> Mix.shell().cmd() do 0 -> :ok @@ -55,6 +55,14 @@ defmodule Bundlex.BuildScript do end end + # TODO: this is obviously awkward but I'm very tired. + # should the toolchains define these? + defp join_commands_run(commands, :windows) do + Enum.join(commands, " && ") + end + + defp join_commands_run(commands, family), do: join_commands(commands, family) + defp join_commands(commands, :windows) do Enum.join(commands, "\r\n") <> "\r\n" end diff --git a/lib/bundlex/helper/path_helper.ex b/lib/bundlex/helper/path_helper.ex index d2ddd9b..ee586af 100644 --- a/lib/bundlex/helper/path_helper.ex +++ b/lib/bundlex/helper/path_helper.ex @@ -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. diff --git a/lib/bundlex/toolchain/visual_studio.ex b/lib/bundlex/toolchain/visual_studio.ex index a1dc05f..01bb66b 100644 --- a/lib/bundlex/toolchain/visual_studio.ex +++ b/lib/bundlex/toolchain/visual_studio.ex @@ -16,10 +16,10 @@ 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" + @program_files System.get_env("ProgramFiles(x86)") |> Path.expand() + @directory_root Path.join([@program_files, "Microsoft Visual Studio"]) + # TODO: These should also include the ability to set the target architecture. @impl true def before_all!(:windows32) do [run_vcvarsall("x86")] @@ -58,16 +58,16 @@ defmodule Bundlex.Toolchain.VisualStudio do dir_part = "\"#{unquoted_dir_part}\"" [ - "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})", + ~s[(cl /LD #{includes_part} #{sources_part} #{libs_part} /link /DLL /OUT:"#{Toolchain.output_path(native.app, native.name, :nif) |> PathHelper.fix_slashes}.dll")] ] end # Runs vcvarsall.bat script defp run_vcvarsall(vcvarsall_arg) do vcvarsall_path = - determine_visual_studio_root() + @directory_root |> build_vcvarsall_path() case File.exists?(vcvarsall_path) do @@ -77,50 +77,19 @@ defmodule Bundlex.Toolchain.VisualStudio do ) true -> - "if not defined VCINSTALLDIR call \"#{vcvarsall_path}\" #{vcvarsall_arg}" + ~s/(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 - 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 - - defp visual_studio_path() do - case :erlang.system_info(:wordsize) do - 4 -> @directory_wildcard_x86 - _word_size -> @directory_wildcard_x64 + vswhere = Path.join([root, "Installer", "vswhere.exe"]) + vswhere_args = ["-property", "installationPath", "-latest"] + 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 From 54e830905243fdc27b8b025e8ac87fd0fbe01077 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Thu, 29 Aug 2024 20:17:59 -0700 Subject: [PATCH 02/11] consolidate command joins I still am not thrilled with this solution, as the build script is harder to read. --- lib/bundlex/build_script.ex | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/bundlex/build_script.ex b/lib/bundlex/build_script.ex index 0247b02..eb6ae48 100644 --- a/lib/bundlex/build_script.ex +++ b/lib/bundlex/build_script.ex @@ -34,7 +34,7 @@ defmodule Bundlex.BuildScript do def run(%__MODULE__{commands: commands}, platform) do family = Platform.family(platform) - cmd = commands |> join_commands_run(family) + cmd = commands |> join_commands(family) case cmd |> Mix.shell().cmd() do 0 -> :ok @@ -55,16 +55,8 @@ defmodule Bundlex.BuildScript do end end - # TODO: this is obviously awkward but I'm very tired. - # should the toolchains define these? - defp join_commands_run(commands, :windows) do - Enum.join(commands, " && ") - end - - defp join_commands_run(commands, family), do: join_commands(commands, family) - 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 From 21d6a495715311a4bfa1f436d810510bf78f9ebc Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Thu, 29 Aug 2024 22:03:08 -0700 Subject: [PATCH 03/11] platform dependent test command --- test/bundlex/integration_test.exs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/bundlex/integration_test.exs b/test/bundlex/integration_test.exs index f002373..8d25bef 100644 --- a/test/bundlex/integration_test.exs +++ b/test/bundlex/integration_test.exs @@ -51,10 +51,16 @@ 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 ) From ec652622cd9922dca8f29ccdde82de3bf3e5e010 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Fri, 30 Aug 2024 01:17:28 -0700 Subject: [PATCH 04/11] get_env -> fetch_env! Co-authored-by: Mateusz Front --- lib/bundlex/toolchain/visual_studio.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundlex/toolchain/visual_studio.ex b/lib/bundlex/toolchain/visual_studio.ex index 01bb66b..3744cb3 100644 --- a/lib/bundlex/toolchain/visual_studio.ex +++ b/lib/bundlex/toolchain/visual_studio.ex @@ -16,7 +16,7 @@ defmodule Bundlex.Toolchain.VisualStudio do alias Bundlex.Native alias Bundlex.Output - @program_files System.get_env("ProgramFiles(x86)") |> Path.expand() + @program_files System.fetch_env!("ProgramFiles(x86)") |> Path.expand() @directory_root Path.join([@program_files, "Microsoft Visual Studio"]) # TODO: These should also include the ability to set the target architecture. From fe7ede39922b285ac9ae1d91249ceb6c4759dbac Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Fri, 30 Aug 2024 10:34:45 -0700 Subject: [PATCH 05/11] inline module attributes they were only used in one place anyway --- lib/bundlex/toolchain/visual_studio.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/bundlex/toolchain/visual_studio.ex b/lib/bundlex/toolchain/visual_studio.ex index 01bb66b..c30e519 100644 --- a/lib/bundlex/toolchain/visual_studio.ex +++ b/lib/bundlex/toolchain/visual_studio.ex @@ -16,9 +16,6 @@ defmodule Bundlex.Toolchain.VisualStudio do alias Bundlex.Native alias Bundlex.Output - @program_files System.get_env("ProgramFiles(x86)") |> Path.expand() - @directory_root Path.join([@program_files, "Microsoft Visual Studio"]) - # TODO: These should also include the ability to set the target architecture. @impl true def before_all!(:windows32) do @@ -66,8 +63,11 @@ defmodule Bundlex.Toolchain.VisualStudio do # Runs vcvarsall.bat script defp run_vcvarsall(vcvarsall_arg) do + program_files = System.get_env("ProgramFiles(x86)") |> Path.expand() + directory_root = Path.join([program_files, "Microsoft Visual Studio"]) + vcvarsall_path = - @directory_root + directory_root |> build_vcvarsall_path() case File.exists?(vcvarsall_path) do From c39dd4bfc8febe825add66540d47436c46b53521 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Fri, 30 Aug 2024 20:24:16 -0700 Subject: [PATCH 06/11] remove obsolete TODO --- lib/bundlex/toolchain/visual_studio.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/bundlex/toolchain/visual_studio.ex b/lib/bundlex/toolchain/visual_studio.ex index e87663e..f0b55fa 100644 --- a/lib/bundlex/toolchain/visual_studio.ex +++ b/lib/bundlex/toolchain/visual_studio.ex @@ -16,7 +16,6 @@ defmodule Bundlex.Toolchain.VisualStudio do alias Bundlex.Native alias Bundlex.Output - # TODO: These should also include the ability to set the target architecture. @impl true def before_all!(:windows32) do [run_vcvarsall("x86")] From 6482bb8a3437681aca7fa22832c3ddc0fb18fc56 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Fri, 30 Aug 2024 22:04:10 -0700 Subject: [PATCH 07/11] add Windows handling for Bundlex.get_target/0 --- lib/bundlex.ex | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/bundlex.ex b/lib/bundlex.ex index 7ecfed9..c21c4b0 100644 --- a/lib/bundlex.ex +++ b/lib/bundlex.ex @@ -32,16 +32,34 @@ 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("-") + case Platform.get_target!() do + platform when platform in [:windows32, :windows64] -> + def get_target() do + os = "windows" + <<^os::binary, architecture::binary>> = Atom.to_string(unquote(platform)) + {architecture, ""} = Integer.parse(architecture) + vendor = "pc" + + %{ + architecture: architecture, + vendor: vendor, + os: os, + abi: "unknown" + } + end + + _ -> + 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} -> From f6e67404b31fc421ff51230e3a3aed094d8d2460 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Fri, 30 Aug 2024 22:49:05 -0700 Subject: [PATCH 08/11] get actual achitecture --- lib/bundlex.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/bundlex.ex b/lib/bundlex.ex index c21c4b0..f2f2332 100644 --- a/lib/bundlex.ex +++ b/lib/bundlex.ex @@ -36,8 +36,7 @@ defmodule Bundlex do platform when platform in [:windows32, :windows64] -> def get_target() do os = "windows" - <<^os::binary, architecture::binary>> = Atom.to_string(unquote(platform)) - {architecture, ""} = Integer.parse(architecture) + architecture = System.fetch_env!("PROCESSOR_ARCHITECTURE") vendor = "pc" %{ From 50e7d69e84b8779fcb9a14731f3bc838e01fc1a9 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Mon, 2 Sep 2024 10:28:53 -0700 Subject: [PATCH 09/11] clean up windows platform handling --- lib/bundlex.ex | 45 ++++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/lib/bundlex.ex b/lib/bundlex.ex index f2f2332..c3edce6 100644 --- a/lib/bundlex.ex +++ b/lib/bundlex.ex @@ -32,33 +32,28 @@ defmodule Bundlex do @spec get_target() :: target() case System.fetch_env("CROSSCOMPILE") do :error -> - case Platform.get_target!() do - platform when platform in [:windows32, :windows64] -> - def get_target() do - os = "windows" - architecture = System.fetch_env!("PROCESSOR_ARCHITECTURE") - vendor = "pc" - - %{ - architecture: architecture, - vendor: vendor, - os: os, - abi: "unknown" - } - end + 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 - _ -> - def get_target() do - [architecture, vendor, os | maybe_abi] = - :erlang.system_info(:system_architecture) |> List.to_string() |> String.split("-") + 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" - } - end + %{ + architecture: architecture, + vendor: vendor, + os: os, + abi: List.first(maybe_abi) || "unknown" + } + end end {:ok, _crosscompile} -> From bef156c82d58c893109417e0f93e3d4b62acc853 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Wed, 4 Sep 2024 10:17:59 -0700 Subject: [PATCH 10/11] fix cnode/port/lib issues --- lib/bundlex/toolchain/visual_studio.ex | 41 ++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/lib/bundlex/toolchain/visual_studio.ex b/lib/bundlex/toolchain/visual_studio.ex index f0b55fa..f7af96a 100644 --- a/lib/bundlex/toolchain/visual_studio.ex +++ b/lib/bundlex/toolchain/visual_studio.ex @@ -27,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( @@ -48,16 +48,45 @@ 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})", - ~s[(cl /LD #{includes_part} #{sources_part} #{libs_part} /link /DLL /OUT:"#{Toolchain.output_path(native.app, native.name, :nif) |> PathHelper.fix_slashes}.dll")] + "(pushd #{dir_part})", + commands, + "(popd)" ] + |> List.flatten() end # Runs vcvarsall.bat script From c185e0eafc36aaf199698eddee794ad2535f17f7 Mon Sep 17 00:00:00 2001 From: Ian Harris Date: Wed, 4 Sep 2024 10:20:29 -0700 Subject: [PATCH 11/11] mix format --- lib/bundlex.ex | 1 - lib/bundlex/toolchain/visual_studio.ex | 44 ++++++++++++++------------ test/bundlex/integration_test.exs | 10 +++--- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/lib/bundlex.ex b/lib/bundlex.ex index c3edce6..162c7db 100644 --- a/lib/bundlex.ex +++ b/lib/bundlex.ex @@ -41,7 +41,6 @@ defmodule Bundlex do abi: "unknown" } end - else def get_target() do [architecture, vendor, os | maybe_abi] = diff --git a/lib/bundlex/toolchain/visual_studio.ex b/lib/bundlex/toolchain/visual_studio.ex index f7af96a..eb8d6e5 100644 --- a/lib/bundlex/toolchain/visual_studio.ex +++ b/lib/bundlex/toolchain/visual_studio.ex @@ -59,25 +59,26 @@ defmodule Bundlex.Toolchain.VisualStudio do 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 + 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})", @@ -112,10 +113,11 @@ defmodule Bundlex.Toolchain.VisualStudio do defp build_vcvarsall_path(root) do vswhere = Path.join([root, "Installer", "vswhere.exe"]) vswhere_args = ["-property", "installationPath", "-latest"] + with true <- File.exists?(vswhere), - {maybe_installation_path, 0} <- System.cmd(vswhere, vswhere_args) - do + {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 diff --git a/test/bundlex/integration_test.exs b/test/bundlex/integration_test.exs index 8d25bef..63fb12e 100644 --- a/test/bundlex/integration_test.exs +++ b/test/bundlex/integration_test.exs @@ -52,10 +52,12 @@ defmodule Bundlex.IntegrationTest do ] family = Bundlex.family() - {cmd, arg} = case family do - f when f in [:unix, :custom] -> {"sh", "-c"} - :windows -> {"cmd", "/c"} - end + + {cmd, arg} = + case family do + f when f in [:unix, :custom] -> {"sh", "-c"} + :windows -> {"cmd", "/c"} + end assert {_output, 0} = System.cmd(