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

Expand cross compilation #124

Merged
merged 22 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/bundlex/)
[![CircleCI](https://circleci.com/gh/membraneframework/bundlex.svg?style=svg)](https://circleci.com/gh/membraneframework/bundlex)

Bundlex is a multi-platform tool for compiling C code along with elixir projects, for use in NIFs, CNodes and Ports. The tool also provides a convenient way of accessing compiled code in elixir modules.
Bundlex is a multi-platform tool for compiling C and C++ code along with elixir projects, for use in NIFs, CNodes and Ports. The tool also provides a convenient way of accessing compiled code in elixir modules.

Bundlex has been tested on Linux, Mac OS and FreeBSD. There's some support for Windows as well, but it's experimental and unstable (see issues for details).

Bundlex also supports cross-compilation and has been tested with platforms running Nerves.

This tool is maintained by the [Membrane Framework](https://membraneframework.org/) team.

## Installation
Expand All @@ -29,7 +31,7 @@ defmodule MyApp.Mixfile do

defp deps() do
[
{:bundlex, "~> 1.4"}
{:bundlex, "~> 1.5"}
]
end
end
Expand Down Expand Up @@ -172,6 +174,20 @@ As in the case of NIFs, CNodes compiled with Bundlex can be used like any other
Similarly to CNodes Bundlex provides `Bundlex.Port` module for a little easier interacting with Ports.
Please refer to the module's documentation to see how to use it.

### Cross-compilation

With proper setup Bundlex can support cross-compilation. When using Nerves the it should work out of the box.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
With proper setup Bundlex can support cross-compilation. When using Nerves the it should work out of the box.
With proper setup, Bundlex can support cross-compilation. When using Nerves it should work out of the box.


Not relying in Nerves and using your own toolchain is also possible, although it wasn't tested. In this scenario the following environment variables need to be set, since they're not handled by Nerves:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Not relying in Nerves and using your own toolchain is also possible, although it wasn't tested. In this scenario the following environment variables need to be set, since they're not handled by Nerves:
Not relying on Nerves and using your own toolchain is also possible, although it wasn't tested. In this scenario, the following environment variables need to be set:

- `CROSSCOMPILE`
FelonEkonom marked this conversation as resolved.
Show resolved Hide resolved
- `CC` - path to the C compiler for cross-compiling to the target
- `CFLAGS` - C compilation flags
- `CXX` - path to the C++ compiler for cross-compiling to the target
- `CXXFLAGS` - C++ compilation flags
- `LDFLAGS` - Linker flags

When cross-compiling some warnings may be raised about not being able to load nifs, but that's expected, since they are most likely built for different architecture
FelonEkonom marked this conversation as resolved.
Show resolved Hide resolved

### Documentation of the native code

Bundlex provides a way to generate documentation of the native code. The documentation is generated using [Doxygen](http://www.doxygen.nl/).
Expand Down
66 changes: 50 additions & 16 deletions lib/bundlex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,73 @@ defmodule Bundlex do
alias Bundlex.Helper.MixHelper
alias Bundlex.Platform

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

@typedoc """
A map containing four fields that describe the platform.
A map containing four fields that describe the target 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`
* abi - application binary interface, e.g. `musl` or `gnu` (nil if unknown / non-existent)
* abi - application binary interface, e.g. `musl` or `gnu`
"""
@type target ::
%{architecture: String.t(), vendor: String.t(), os: String.t(), abi: String.t() | nil}
%{
architecture: String.t() | :unknown,
vendor: String.t() | :unknown,
os: String.t() | :unknown,
abi: String.t() | :unknown
Copy link
Member

Choose a reason for hiding this comment

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

I'd either use nil or "unknown"

}

@doc """
A function returning a target triplet for the environment on which it is run.
A function returning information about the target platform. In case of cross-compilation the
information can be provided by setting appropriate environment variables.
"""
@spec get_target() :: target()
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)
}
case System.fetch_env("CROSSCOMPILE") do
mat-hek marked this conversation as resolved.
Show resolved Hide resolved
:error ->
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

{:ok, _} ->
def get_target() do
unquote(
Enum.map(
[
{:architecture, "TARGET_ARCH"},
{:vendor, "TARGET_VENDOR"},
{:os, "TARGET_OS"},
{:abi, "TARGET_ABI"}
mat-hek marked this conversation as resolved.
Show resolved Hide resolved
],
fn {key, env} ->
value =
case System.fetch_env(env) do
{:ok, value} -> value
:error -> :unknown
end

{key, value}
end
)
)
|> Enum.into(%{})
end
end

@doc """
Returns current platform name.
"""
@deprecated "Use Bundlex.get_target/0 instead"
@spec platform() :: platform_t()
def platform() do
Platform.get_target!()
Expand All @@ -47,7 +81,7 @@ defmodule Bundlex do
@doc """
Returns family of the platform obtained with `platform/0`.
"""
@spec family() :: :unix | :windows
@spec family() :: :unix | :windows | :custom
Copy link
Member

Choose a reason for hiding this comment

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

Since we deprecate this function anyway, maybe let's keep it unchanged for better compatibility?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we deprecate platform, not family

def family() do
Platform.family(platform())
end
Expand Down
39 changes: 21 additions & 18 deletions lib/bundlex/platform.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,25 @@ defmodule Bundlex.Platform do
Otherwise raises Mix error.
"""
@spec get_target!() :: name_t
mix_target = Mix.target()

if mix_target == :host do
def get_target!(), do: get_host!()
else
def get_target!() do
case System.fetch_env("NERVES_APP") do
{:ok, _app} ->
:nerves

:error ->
Output.warn(
"MIX_TARGET #{inspect(unquote(mix_target))} is not supported. Bundlex will compile for the host platform."
)

get_host!()

case System.fetch_env("CROSSCOMPILE") do
:error ->
def get_target!(), do: get_host!()

{:ok, _} ->
def get_target!() do
case System.fetch_env("NERVES_APP") do
{:ok, _app} ->
:nerves

:error ->
Output.warn(
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Output.warn(
Output.info(

"Cross-compiling without using Nerves. Make sure necessary environment variables are set correctly."
)

:custom
end
end
end
end

@spec get_host!() :: name_t
Expand Down Expand Up @@ -107,12 +108,14 @@ defmodule Bundlex.Platform do
def family(:macosx), do: :unix
def family(:freebsd), do: :unix
def family(:nerves), do: :unix
def family(:custom), do: :custom

@spec get_module(family_name_t) :: module
def get_module(:windows32), do: Bundlex.Platform.Windows32
def get_module(:windows64), do: Bundlex.Platform.Windows64
def get_module(:macosx), do: Bundlex.Platform.MacOSX
def get_module(:linux), do: Bundlex.Platform.Linux
def get_module(:freebsd), do: Bundlex.Platform.Freebsd
def get_module(:nerves), do: Bundlex.Platform.Nerves
def get_module(:nerves), do: Bundlex.Platform.Custom
def get_module(:custom), do: Bundlex.Platform.Custom
end
4 changes: 2 additions & 2 deletions lib/bundlex/platform/nerves.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
defmodule Bundlex.Platform.Nerves do
defmodule Bundlex.Platform.Custom do
@moduledoc false
use Bundlex.Platform

@impl true
def toolchain_module() do
Bundlex.Toolchain.Nerves
Bundlex.Toolchain.Custom
end
end
4 changes: 2 additions & 2 deletions lib/bundlex/toolchain/common/unix/os_deps.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Bundlex.Toolchain.Common.Unix.OSDeps do
@moduledoc false

require Logger
alias Bundlex.Output
alias Bundlex.{Output, Platform}

@spec resolve_os_deps(Bundlex.Native.t()) :: Bundlex.Native.t()
def resolve_os_deps(native) do
Expand Down Expand Up @@ -147,7 +147,7 @@ defmodule Bundlex.Toolchain.Common.Unix.OSDeps do

# TODO: pass the platform via arguments
# $ORIGIN must be escaped so that it's not treated as an ENV variable
rpath_root = if Bundlex.platform() == :macosx, do: "@loader_path", else: "\\$ORIGIN"
rpath_root = if Platform.get_target!() == :macosx, do: "@loader_path", else: "\\$ORIGIN"
Copy link
Member

Choose a reason for hiding this comment

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

What is the difference between Platform.get_target!() and Bundlex.get_target!()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now deprecated Bundlex.get_platform/0 invoked Platform.get_target!/0, it gives a bit different information than Bundlex.get_target/0, we probably want to get rid of it eventually and rely just on the Bundlex.get_target/0

Copy link
Member

Choose a reason for hiding this comment

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

So why won't you use Bundlex.get_target/0 instead of Platform.get_target!/0?


[
"-L#{Path.join(dep_path, "lib")}",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Bundlex.Toolchain.Nerves do
defmodule Bundlex.Toolchain.Custom do
@moduledoc false

use Bundlex.Toolchain
Expand All @@ -7,7 +7,7 @@ defmodule Bundlex.Toolchain.Nerves do

@impl Toolchain
def compiler_commands(native) do
{compiler, nerves_cflags} =
{compiler, custom_cflags} =
case native.language do
:c -> {System.fetch_env!("CC"), System.fetch_env!("CFLAGS")}
:cpp -> {System.fetch_env!("CXX"), System.fetch_env!("CXXFLAGS")}
Expand All @@ -16,13 +16,13 @@ defmodule Bundlex.Toolchain.Nerves do
{cflags, lflags} =
case native do
%Native{type: :native, interface: :nif} ->
{nerves_cflags <> " -fPIC", System.fetch_env!("LDFLAGS") <> " -rdynamic -shared"}
{custom_cflags <> " -fPIC", System.fetch_env!("LDFLAGS") <> " -rdynamic -shared"}

%Native{type: :lib} ->
{nerves_cflags <> " -fPIC", System.fetch_env!("LDFLAGS")}
{custom_cflags <> " -fPIC", System.fetch_env!("LDFLAGS")}

%Native{} ->
{nerves_cflags, System.fetch_env!("LDFLAGS")}
{custom_cflags, System.fetch_env!("LDFLAGS")}
end

Unix.compiler_commands(
Expand Down
2 changes: 1 addition & 1 deletion lib/mix/tasks/compile.bundlex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule Mix.Tasks.Compile.Bundlex do
commands = []

app = MixHelper.get_app!()
platform = Bundlex.platform()
platform = Platform.get_target!()
mat-hek marked this conversation as resolved.
Show resolved Hide resolved

project =
with {:ok, project} <- Project.get(app) do
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Bundlex.Mixfile do
use Mix.Project

@version "1.4.6"
@version "1.5.0"
@github_url "https://github.com/membraneframework/bundlex"

def project do
Expand Down