-
Notifications
You must be signed in to change notification settings - Fork 14
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
Precompiled deps #107
Conversation
… should be downloaded. Set the rpath for the created dynamic libraries
…deps (with the use of pkgconfig and precompiled deps). Add Bundlex.OSDeps module.
lib/bundlex/os_deps.ex
Outdated
:curl -> "curl --fail -L -o #{dest} #{url}" | ||
:wget -> "wget -O #{dest} #{url}" | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use an elixir/erlang library for that, for example https://hexdocs.pm/req/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -0,0 +1,69 @@ | |||
defmodule Bundlex.PrecompiledDependency do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we spoke, let's remove the behaviour in favour of accepting config
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as discussed, I will do it later if we assume that this approach is still valid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The behaviour has been removed ;)
test/bundlex/integration_test.exs
Outdated
assert {_output, 0} = System.cmd("sh", ["-c", "mix test 1>&2"], cd: "test_projects/example") | ||
System.cmd("sh", ["-c", "mix test 1>&2"], cd: "test_projects/example") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why remove the assertion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have revoked it ;)
lib/bundlex/os_deps.ex
Outdated
require Logger | ||
alias Bundlex.{Output, PrecompiledDependency} | ||
|
||
@precompiled_path "_build/#{Mix.env()}/precompiled/" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use Mix.Project.build_path()
and name the dir with prefix, e.g. 'bundlex_precompiled'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/bundlex/native.ex
Outdated
Map.take(dependency, [:includes, :libs, :lib_dirs, :pkg_configs, :linker_flags, :deps]), | ||
Map.take(dependency, [:includes, :libs, :lib_dirs, :os_deps, :linker_flags, :deps]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unless we don't want to release a new version, we should still support pkg_configs
, just deprecate them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/bundlex/os_deps.ex
Outdated
defp remove_lib_prefix(libname) do | ||
if String.starts_with?(libname, "lib") do | ||
String.slice(libname, 3..-1) | ||
else | ||
libname | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defp remove_lib_prefix(libname) do | |
if String.starts_with?(libname, "lib") do | |
String.slice(libname, 3..-1) | |
else | |
libname | |
end | |
end | |
defp remove_lib_prefix("lib" <> libname), do: libname | |
defp remove_lib_prefix(libname), do: libname |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea, thanks :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/bundlex/os_deps.ex
Outdated
@@ -0,0 +1,204 @@ | |||
defmodule Bundlex.OSDeps do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about moving this to Budlex.Toolchain.Common.Unix.OsDeps
, as it's quite unix-specific, though the name would get scaringly java-style :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will move it ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/bundlex.ex
Outdated
It consists of: | ||
* architecture - e.g. `x86_64` or `arm64` | ||
* vendor - e.g. `pc` | ||
* operating system - e.g. `linux` or `freebsd` | ||
""" | ||
@type target_triplet :: | ||
{architecture :: String.t(), vendor :: String.t(), operating_system :: String.t()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd make it a struct for more flexibility and easier access to fields. Also, maybe we could put the version of the operating system in a separate field, so we didn't have to pattern match it. I'd consider converting strings to atoms as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about putting the operating system's version as a separate field - we would need to be aware of all possible values of the operating system returned by the :erlang.system_info
, since some of them might be just a system name (e.g. linux
), and some of them contain the version (e.g. darwin20.6.0
).
For a person who needs to use the os
value (that is a person who prepares the precompiled dependency) it's not that much effort to pattern match on something like "darwin"<>_version
since it's already aware what the os string look like on a given system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is also why I wouldn't convert the names into atoms, as it will make such a pattern matching more difficult (or impossible)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so maybe we could have a function like Bundlex.family()
that would convert the OS to one of the well-known values? Then we could gradually improve it to support more systems if needed
test_projects/example/bundlex.exs
Outdated
{PrecompiledFFmpeg, [:libswscale, :libavcodec]}, | ||
:libpng | ||
] | ||
{get_ffmpeg_path(), ["libswscale", "libavcodec"]}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this implicit pkg-config as a default, and maybe we could accept a list of 'providers', which could be either {:precompiled, url}
or :pkg_config
or nil
. So in this case we'd have
{get_ffmpeg_path(), ["libswscale", "libavcodec"]}, | |
{[get_ffmpeg_path(), :pkg_config], ["libswscale", "libavcodec"]}, |
And get_ffmpeg_path()
would return {:precompiled, url}
or nil. This way is more verbose but less implicit and the user can decide whether to take the precompiled or OS package first ;) WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done ;)
@@ -152,7 +136,7 @@ defmodule Bundlex.OSDeps do | |||
try do | |||
File.mkdir(package_path) | |||
temporary_destination = "#{@precompiled_path}/temporary" | |||
download(url, temporary_destination) | |||
download(precompiled_dependency_url, temporary_destination) | |||
System.shell("tar -xf #{temporary_destination} -C #{package_path} --strip-components 1") | |||
System.shell("rm #{temporary_destination}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
System.shell("rm #{temporary_destination}") | |
File.rm!(temporary_destination) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -173,32 +157,19 @@ defmodule Bundlex.OSDeps do | |||
String.split(url, "/") | |||
|> Enum.at(-1) | |||
|> String.split(".") | |||
|> Enum.reject(&(&1 in ["tar", "xz"])) | |||
|> Enum.reject(&(&1 in ["tar", "xz", "gz"])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've bumped into https://github.com/ricn/zarex, maybe it's worth considering?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea, done
case System.shell(command) do | ||
{_, 0} -> :ok | ||
_other -> :error | ||
if response.status == 200 do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: case & pattern match would look better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
require Logger | ||
alias Bundlex.Output | ||
|
||
@precompiled_path "#{Mix.Project.build_path()}/bundlex_precompiled/" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how this path can change, so I'd make it defp
just in case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
[] | ||
end | ||
|
||
defp resolve_single_os_dep(providers, lib_names) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, you can resolve all the flags and paths here. If you add them to existing fields in Native
, there'll be no need for the resolved_os_deps
field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
{{precompiled_dependency, package_path}, lib_names} | ||
end | ||
end) | ||
# defp parse_os_deps(os_deps) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leftover
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed ;)
try do | ||
File.mkdir(package_path) | ||
temporary_destination = "#{@precompiled_path}/temporary" | ||
File.mkdir!(package_path) | ||
temporary_destination = "#{get_precompiled_path()}/temporary" | ||
download(precompiled_dependency_url, temporary_destination) | ||
System.shell("tar -xf #{temporary_destination} -C #{package_path} --strip-components 1") | ||
System.shell("rm #{temporary_destination}") | ||
|
||
{_output, 0} = | ||
System.shell( | ||
"tar -xf #{temporary_destination} -C #{package_path} --strip-components 1" | ||
) | ||
|
||
File.rm!(temporary_destination) | ||
package_path | ||
rescue | ||
e -> | ||
Logger.warning("Couldn't download the dependency due to: #{inspect(e)}.") | ||
IO.warn("Couldn't download the dependency due to: #{inspect(e)}.") | ||
:unavailable | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This try
shouldn't rescue exception from File.mkdir!/1
function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, why do we need try
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there is a possibility that in example, due to network conditions, the dependency won't be downloaded
README.md
Outdated
@@ -128,6 +128,17 @@ Configuration of each native may contain following options: | |||
* `libs` - Names of libraries to link (empty list by default). | |||
* `pkg_configs` - (deprecated) Names of libraries for which the appropriate flags will be | |||
obtained using pkg-config (empty list by default). | |||
* `os_deps` - List of external OS dependencies. It's a list of elements in form of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* `os_deps` - List of external OS dependencies. It's a list of elements in form of | |
* `os_deps` - List of external OS dependencies. It's a list of tuples in the form of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
compiler_flags = | ||
Enum.uniq(cflags_list) | ||
|> Enum.join(" ") | ||
|
||
libs_flags = | ||
Enum.uniq(libs_list) | ||
|> Enum.join(" ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't these be just concatenated with respective native.*_flags
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have slightly modified it
@@ -40,6 +40,7 @@ defmodule Bundlex.Project do | |||
includes: [String.t()], | |||
lib_dirs: [String.t()], | |||
libs: [String.t()], | |||
os_deps: [Bundlex.Native.os_dep()], |
There was a problem hiding this comment.
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?
get_flags_for_pkg_config(lib_names, :libs, app)} | ||
] | ||
rescue | ||
_e -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we silence any error that may have happened. I'd print it, otherwise, there's no way to see what went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we can print something like 'Failed to fetch the OS libs X, Y, Z via pkg-config, trying other ways' and print the original error(s) only if no provider succeeds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
end | ||
|
||
defp resolve_single_os_dep([], lib_names, _app) do | ||
IO.warn("Couldn't load OS dependencies for libraries: #{inspect(lib_names)}.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should raise here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
# defp parse_os_deps(os_deps) do | ||
# Enum.map(os_deps, fn | ||
# {:precompiled, precompiled_dependency_url, lib_name_or_names} -> | ||
# lib_names = Bunch.listify(lib_name_or_names) | ||
# {precompiled_dependency_url, lib_names} | ||
|
||
# lib_name -> | ||
# {:pkg_config, lib_name} | ||
# end) | ||
# end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leftover
try do | ||
File.mkdir!(package_path) | ||
temporary_destination = "#{get_precompiled_path()}/temporary" | ||
download(precompiled_dependency_url, temporary_destination) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IO.warn
in rescue
below will print a misleading warning in case of error thrown from System.shell
or File.rm!
|
||
true -> | ||
raise "Unknown flag type: #{inspect(flags_type)}" | ||
"-I#{full_include_path}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"-I#{full_include_path}" | |
[ "-I#{full_include_path}"] |
Not having a list here might cause an error thrown from Enum.zip
at line 17
providers = Bunch.listify(provider_or_providers) | ||
lib_names = Bunch.listify(lib_name_or_names) | ||
|
||
resolve_single_os_dep(providers, lib_names, native.app) | ||
end) | ||
|> Enum.unzip() | ||
|> then(&{List.flatten(elem(&1, 0)), List.flatten(elem(&1, 1))}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is hard to read, let's do that below, alongside Enum.uniq
compiler_flags = | ||
Enum.uniq(cflags_list) | ||
List.flatten(cflags_list) | ||
|> Enum.uniq() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compiler_flags = | |
Enum.uniq(cflags_list) | |
List.flatten(cflags_list) | |
|> Enum.uniq() | |
compiler_flags = cflags_list |> List.flatten() |> Enum.uniq() |
libs_flags = | ||
Enum.uniq(libs_list) | ||
List.flatten(libs_list) | ||
|> Enum.uniq() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
libs_flags = | |
Enum.uniq(libs_list) | |
List.flatten(libs_list) | |
|> Enum.uniq() | |
libs_flags = libs_list |> List.flatten() |> Enum.uniq() |
closes membraneframework/membrane_core#603
This PR adds an ability to specify precompiled dependencies.
Its usage can be seen here: membraneframework/membrane_h264_ffmpeg_plugin#92