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

Precompiled deps #107

Merged
merged 41 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
791a9ce
WIP
varsill Aug 3, 2023
0c0cc52
Implement POC
varsill Aug 4, 2023
3de5dc7
Refactor the code. Add Bundlex.Precompiled module
varsill Aug 4, 2023
04b8311
Add API to specify precompiled dependencies repository
varsill Aug 28, 2023
7c1b73b
Allow to specify the repository from which the precompiled dependency…
varsill Aug 29, 2023
8d151d5
Refactor the code. Improve and unify the API for specification of os …
varsill Aug 29, 2023
153dba6
Repair the test
varsill Aug 29, 2023
73b23bd
Remove redundant joining
varsill Aug 29, 2023
2971e56
Add documentation and typespecs
varsill Aug 30, 2023
f7e3856
Save the package path after the precompiled dependency is downloaded
varsill Aug 30, 2023
547c67d
Fix a bug with get_package_path failing for :unavailable input. Remov…
varsill Aug 30, 2023
ca23050
Remove unused variable
varsill Aug 30, 2023
44ad467
Format the code
varsill Aug 30, 2023
09b9316
Make credo happy
varsill Aug 30, 2023
da40f4d
Update the bundlex test with new precompiled deps API. Use --disable-…
varsill Aug 31, 2023
f07ba87
Implement reviewers suggestions.
varsill Sep 4, 2023
03d1b68
Make os_deps accept lib names in form of strings
varsill Sep 4, 2023
e4a04ef
Update mix.lock
varsill Sep 4, 2023
92fa45f
Get rid of PrecompiledDependency behaviour
varsill Sep 4, 2023
723110c
Add get_target()
varsill Sep 4, 2023
d4608b4
Implement reviewers suggestions.
varsill Sep 7, 2023
1003434
get rid of do_get_flags_for_pkg_config
varsill Sep 7, 2023
5f970c4
Fix dialyzer
varsill Sep 7, 2023
679a5a6
Wrap target triplet into a structure
varsill Sep 7, 2023
a0bb656
Fix a bug with fallback to pkg_config
varsill Sep 8, 2023
1837f76
Let the user specify the priorities of the providers
varsill Sep 8, 2023
6701746
Improve the typespec
varsill Sep 8, 2023
1db09c1
Remove redundant Native type. Add resolved_os_deps field to native
varsill Sep 8, 2023
a7c4b47
Implement reviewers suggestions.
varsill Sep 19, 2023
2cc088d
Remove os_resolved_deps
varsill Sep 19, 2023
afc0a75
make dialyzer happy
varsill Sep 19, 2023
e6c925d
Fix a bug
varsill Sep 19, 2023
370a9e0
Implement reviewers suggestions.
varsill Sep 19, 2023
b4557f1
Fix unix compile command
Noarkhh Sep 26, 2023
a18ddd3
Stop warning
Noarkhh Sep 26, 2023
430f180
Apply code review suggestions
Noarkhh Sep 29, 2023
74c21a8
Fix stderr_to_stdout
Noarkhh Oct 2, 2023
fc67e2f
Update example for test
Noarkhh Oct 2, 2023
5c5a89d
Add -Wno-unused-function flag
Noarkhh Oct 2, 2023
91c559b
Try not inhibiting warnings
Noarkhh Oct 5, 2023
e56a0a4
Inhibit all warnings
Noarkhh Oct 5, 2023
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
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,19 @@ Configuration of each native may contain following options:
* `includes` - Paths to look for header files (empty list by default).
* `lib_dirs` - Absolute paths to look for libraries (empty list by default).
* `libs` - Names of libraries to link (empty list by default).
* `pkg_configs` - Names of libraries for which the appropriate flags will be
* `pkg_configs` - (deprecated) Names of libraries for which the appropriate flags will be
mat-hek marked this conversation as resolved.
Show resolved Hide resolved
obtained using pkg-config (empty list by default).
* `os_deps` - List of external OS dependencies. It's a list of tuples in the form of
`{provider | [provider], lib_name | [lib_name]}` where `provider` is either `:pkg_config`
or a tuple `{:precompiled, <string representing URL to the precompiled library>}` and `lib_name`
is a string representing a library that should be provided by a given providers.
In case `pkg_config` provider is used, the `pkg-config` will be used to resolve the compilation
and linking flags (note that the given libary needs to be previously installed in the system).
In case `{:precompiled, <string representing URL to the precompiled library>}` is used,
the precompiled library will be first downloaded from the provider URL and appropriate compilation
and linking flags will be set.
The list of providers is resolved with the order of elements in the providers list -
in case one provider fails to provide a library, the next provider from the list is tried.
* `deps` - Dependencies in the form of `{app, lib_name}`, where `app`
is the application name of the dependency, and `lib_name` is the name of lib
specified in Bundlex project of this dependency. Empty list by default. See _Dependencies_ section below
Expand Down
25 changes: 25 additions & 0 deletions lib/bundlex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ defmodule Bundlex do

@type platform_t :: :linux | :macosx | :windows32 | :windows64

@typedoc """
A map containing three fields that describe the platform.

It consists of:
* architecture - e.g. `x86_64` or `arm64`
* vendor - e.g. `pc`
* os - operating system, e.g. `linux` or `darwin20.6.0`
"""
@type target ::
%{architecture: String.t(), vendor: String.t(), os: String.t()}
@doc """
A function returning a target triplet for the environment on which it is run.
"""
@spec get_target() :: target()
def get_target() do
[architecture, vendor, os | _rest] =
:erlang.system_info(:system_architecture) |> List.to_string() |> String.split("-")

%{
architecture: architecture,
vendor: vendor,
os: os
}
end

@doc """
Returns current platform name.
"""
Expand Down
4 changes: 2 additions & 2 deletions lib/bundlex/cnode/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule Bundlex.CNode.Server do
{:spawn_executable, Bundlex.build_path(opts.app, opts.native_name, :cnode)},
args: [host_name(), name, cnode, "#{creation}"],
line: 2048,
env: [{'BUNDLEX_ERLANG_COOKIE', cookie}]
env: [{~c"BUNDLEX_ERLANG_COOKIE", cookie}]
)

Process.send_after(self(), :timeout, 5000)
Expand All @@ -39,7 +39,7 @@ defmodule Bundlex.CNode.Server do

@impl true
def handle_info(
{port, {:data, {:eol, 'ready'}}},
{port, {:data, {:eol, ~c"ready"}}},
%{port: port, status: :waiting, msg_part?: false} = state
) do
case Node.connect(state.cnode) do
Expand Down
1 change: 1 addition & 0 deletions lib/bundlex/loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ defmodule Bundlex.Loader do
quote do
app = unquote(app || MixHelper.get_app!())
nif_name = unquote(nif_name)

path = Bundlex.build_path(app, nif_name, :nif)

with :ok <- :erlang.load_nif(path |> PathHelper.fix_slashes() |> to_charlist(), 0) do
Expand Down
37 changes: 35 additions & 2 deletions lib/bundlex/native.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,31 @@ defmodule Bundlex.Native do

use Bunch

require Logger
alias Bundlex.Helper.ErlangHelper
alias Bundlex.{Output, Platform, Project}
alias Bundlex.Project.Preprocessor
alias Bundlex.Toolchain.Common.Unix.OSDeps

@type name_t :: atom()
@type interface_t :: :nif | :cnode | :port
@type language_t :: :c | :cpp

@type lib_name :: String.t()
@type precompiled_dependency_url :: String.t()
@type os_dep_provider :: :pkg_config | {:precompiled, precompiled_dependency_url} | nil

@type os_dep ::
{os_dep_provider() | [os_dep_provider()], lib_name() | [lib_name()]}

@type t :: %__MODULE__{
name: atom,
app: Application.app(),
type: :native | :lib,
includes: [String.t()],
libs: [String.t()],
lib_dirs: [String.t()],
os_deps: [os_dep()],
pkg_configs: [String.t()],
sources: [String.t()],
deps: [t],
Expand All @@ -38,6 +48,7 @@ defmodule Bundlex.Native do
includes: [],
libs: [],
lib_dirs: [],
os_deps: [],
pkg_configs: [],
sources: [],
deps: [],
Expand Down Expand Up @@ -104,7 +115,21 @@ defmodule Bundlex.Native do
|> Map.update!(:sources, &Enum.uniq/1)
|> Map.update!(:deps, fn deps -> Enum.uniq_by(deps, &{&1.app, &1.name}) end)

commands = Platform.get_module(platform).toolchain_module.compiler_commands(native)
native =
if native.pkg_configs != [] do
IO.warn("`pkg_configs` option has been deprecated. Please use `os_deps` option.")
%{native | os_deps: [{:pkg_config, native.pkg_configs} | native.os_deps]}
else
native
end

native_with_resolved_os_deps = OSDeps.resolve_os_deps(native)

commands =
Platform.get_module(platform).toolchain_module.compiler_commands(
native_with_resolved_os_deps
)

{:ok, commands}
end
end
Expand Down Expand Up @@ -197,7 +222,15 @@ defmodule Bundlex.Native do
defp merge_dep(%__MODULE__{type: :lib} = dependency, %__MODULE__{} = native) do
Map.merge(
native,
Map.take(dependency, [:includes, :libs, :lib_dirs, :pkg_configs, :linker_flags, :deps]),
Map.take(dependency, [
:includes,
:libs,
:lib_dirs,
:os_deps,
:pkg_configs,
:linker_flags,
:deps
]),
fn _k, v1, v2 -> v2 ++ v1 end
)
end
Expand Down
7 changes: 5 additions & 2 deletions lib/bundlex/platform.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ defmodule Bundlex.Platform do
case :os.type() do
{:win32, _} ->
{:ok, reg} = :win32reg.open([:read])
:ok = :win32reg.change_key(reg, '\\hklm\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion')
{:ok, build} = :win32reg.value(reg, 'BuildLabEx')

:ok =
:win32reg.change_key(reg, ~c"\\hklm\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")

{:ok, build} = :win32reg.value(reg, ~c"BuildLabEx")

platform_name =
if build |> to_string |> String.contains?("amd64") do
Expand Down
4 changes: 3 additions & 1 deletion lib/bundlex/project.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule Bundlex.Project do
* `includes` - Paths to look for header files (empty list by default).
* `lib_dirs` - Paths to look for libraries (empty list by default).
* `libs` - Names of libraries to link (empty list by default).
* `pkg_configs` - Names of libraries for which the appropriate flags will be
* `pkg_configs` - (deprecated) Names of libraries for which the appropriate flags will be
obtained using pkg-config (empty list by default).
* `deps` - Dependencies in the form of `{app, lib_name}`, where `app`
is the application name of the dependency, and `lib_name` is the name of lib
Expand All @@ -40,6 +40,7 @@ defmodule Bundlex.Project do
includes: [String.t()],
lib_dirs: [String.t()],
libs: [String.t()],
os_deps: [Bundlex.Native.os_dep()],
Copy link
Member

@mat-hek mat-hek Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not documented in the typedoc. Right now, this typedoc is copied to readme so we have to update both places. Maybe we can replace it in the readme with an example and a link to the typedoc?

pkg_configs: [String.t()],
deps: [{Application.app(), native_name_t | [native_name_t]}],
src_base: String.t(),
Expand All @@ -57,6 +58,7 @@ defmodule Bundlex.Project do
:libs,
:lib_dirs,
:pkg_configs,
:os_deps,
:sources,
:deps,
:compiler_flags,
Expand Down
46 changes: 6 additions & 40 deletions lib/bundlex/toolchain/common/unix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Bundlex.Toolchain.Common.Unix do
@moduledoc false

use Bunch
alias Bundlex.{Native, Output, Toolchain}
alias Bundlex.{Native, Toolchain}
alias Bundlex.Toolchain.Common.Compilers

@spec compiler_commands(
Expand All @@ -14,7 +14,7 @@ defmodule Bundlex.Toolchain.Common.Unix do
) :: [String.t()]
def compiler_commands(native, compile, link, lang, options \\ []) do
includes = native.includes |> paths("-I")
pkg_config_cflags = pkg_config(native, :cflags)

compiler_flags = resolve_compiler_flags(native.compiler_flags, native.interface, lang)
output = Toolchain.output_path(native.app, native.name, native.interface)
output_obj = output <> "_obj"
Expand All @@ -34,7 +34,7 @@ defmodule Bundlex.Toolchain.Common.Unix do
|> Enum.map(fn {source, object} ->
"""
#{compile} -Wall -Wextra -c -O2 -g #{compiler_flags} \
-o #{path(object)} #{includes} #{pkg_config_cflags} #{path(source)}
-o #{path(object)} #{includes} #{path(source)}
"""
end)

Expand Down Expand Up @@ -95,8 +95,8 @@ defmodule Bundlex.Toolchain.Common.Unix do

[
"""
#{link} #{linker_flags} -o #{path(output <> extension)} \
#{deps} #{paths(objects)} #{libs(native)}
#{link} -o #{path(output <> extension)} \
#{deps} #{paths(objects)} #{libs(native)} #{linker_flags}
"""
]
end
Expand All @@ -113,41 +113,7 @@ defmodule Bundlex.Toolchain.Common.Unix do
defp libs(native) do
lib_dirs = native.lib_dirs |> paths("-L")
libs = native.libs |> Enum.map_join(" ", fn lib -> "-l#{lib}" end)
pkg_config_libs = pkg_config(native, :libs)
"#{pkg_config_libs} #{lib_dirs} #{libs}"
end

defp pkg_config(%Native{pkg_configs: []}, _options), do: ""

defp pkg_config(%Native{pkg_configs: packages, app: app}, options) do
options = options |> Bunch.listify() |> Enum.map(&"--#{&1}")
System.put_env("PATH", System.get_env("PATH", "") <> ":/usr/local/bin:/opt/homebrew/bin")

case System.cmd("which", ["pkg-config"]) do
{_path, 0} ->
:ok

{_path, _error} ->
Output.raise("""
pkg-config not found. Bundlex needs pkg-config to find packages in system.
On Mac OS, you can install pkg-config via Homebrew by typing `brew install pkg-config`.
""")
end

Enum.map_join(packages, " ", fn package ->
case System.cmd("pkg-config", options ++ [package], stderr_to_stdout: true) do
{output, 0} ->
String.trim_trailing(output)

{output, error} ->
Output.raise("""
Couldn't find system package #{package} with pkg-config. Check whether it's installed.
Installation instructions may be available in the readme of package #{app}.
Output from pkg-config:
Error: #{error}
#{output}
""")
end
end)
"#{lib_dirs} #{libs}"
end
end
Loading