defmodule Evision.MixProject.Metadata do @moduledoc false def app, do: :evision def version, do: "0.2.17" def github_url, do: "https://github.com/cocoa-xu/evision" def opencv_version, do: "4.13.0" # only means compatible. need to write more tests def compatible_opencv_versions, do: [ "4.5.3", "4.5.4", "4.5.5", "4.6.0", "4.7.0", "4.8.0", "4.9.0", "4.10.0", "4.11.0", "4.13.0" ] def default_cuda_version, do: {"12", "9"} def all_cuda_version(target) do cuda11 = [{"11", "8"}, {"11", "9"}] cuda12 = [{"12", "8"}, {"12", "9"}] if String.contains?("msvc", target) do cuda12 else cuda11 ++ cuda12 end end end defmodule Mix.Tasks.Compile.EvisionPrecompiled do @moduledoc false use Mix.Task require Logger alias Evision.MixProject.Metadata @precompiled_lock_stale_seconds 3600 @available_targets [ "aarch64-apple-darwin", "x86_64-apple-darwin", "aarch64-linux-gnu", "aarch64-linux-musl", "x86_64-linux-gnu", "x86_64-linux-musl", "armv7l-linux-gnueabihf", "armv6-linux-gnueabihf", "s390x-linux-gnu", "riscv64-linux-gnu", "riscv64-linux-musl", "i686-linux-gnu", "x86_64-unknown-freebsd", "x86_64-windows-msvc" ] @compile_nif_version "2.16" def get_available_nif_versions do ["2.16", "2.17"] end def available_targets do opencv_version = System.get_env("OPENCV_VER", Metadata.opencv_version()) available_targets = @available_targets if Enum.member?(["4.11.0"], opencv_version) do available_targets -- ["ppc64le-linux-gnu", "aarch64-windows-msvc"] else available_targets end end def available_nif_urls(_host_nif_version, version \\ Metadata.version()) do nif_version = get_compile_nif_version() Enum.reduce(available_targets(), [], fn target, acc -> no_contrib = get_download_url(target, version, nif_version, false, false, "", "") with_contrib = get_download_url(target, version, nif_version, true, false, "", "") with_cuda = if target in ["x86_64-linux-gnu", "aarch64-linux-gnu"] do Enum.map(Metadata.all_cuda_version(target), fn {cuda_ver, cudnn_version} -> get_download_url(target, version, nif_version, true, true, cuda_ver, cudnn_version) end) else [] end [no_contrib, with_contrib] ++ with_cuda ++ acc end) end def current_target_nif_url(_host_nif_version, version \\ Metadata.version()) do {target, _} = get_target() nif_version = get_compile_nif_version() enable_contrib = System.get_env("EVISION_ENABLE_CONTRIB", "true") == "true" if enable_contrib do System.put_env("EVISION_ENABLE_CONTRIB", "true") else System.put_env("EVISION_ENABLE_CONTRIB", "false") end enable_cuda = System.get_env("EVISION_ENABLE_CUDA", "false") == "true" {cuda_version, cudnn_version} = if enable_cuda do {defalt_cuda, default_cudnn} = Metadata.default_cuda_version() System.put_env("EVISION_ENABLE_CUDA", "true") { System.get_env("EVISION_CUDA_VERSION", defalt_cuda), System.get_env("EVISION_CUDNN_VERSION", default_cudnn) } else System.put_env("EVISION_ENABLE_CUDA", "false") {"", ""} end get_download_url( target, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version ) end def checksum_file(app \\ Mix.Project.config()[:app]) when is_atom(app) do # Saves the file in the project root. { Path.join(File.cwd!(), "checksum.exs"), Path.join([File.cwd!(), "checksum.erl"]) } end defp read_checksum_map(app \\ Mix.Project.config()[:app]) when is_atom(app) do {file, _} = checksum_file(app) with {:ok, contents} <- File.read(file), {%{} = contents, _} <- Code.eval_string(contents) do contents else _ -> %{} end end def get_compile_nif_version do @compile_nif_version end def get_nif_version do to_string(:erlang.system_info(:nif_version)) end def get_target do case System.get_env("MIX_TARGET") do "ios" -> {"aarch64-apple-darwin-ios", ["aarch64", "apple", "darwin"]} "xros" -> {"aarch64-apple-darwin-xros", ["aarch64", "apple", "darwin"]} _ -> target = String.split(to_string(:erlang.system_info(:system_architecture)), "-") [arch, os, abi] = case Enum.count(target) do 3 -> target 4 -> [arch, _vendor, os, abi] = target [arch, os, abi] 1 -> with ["win32"] <- target do ["x86_64", "windows", "msvc"] else [unknown_target] -> [unknown_target, "unknown", nil] end end abi = case abi do "darwin" <> _ -> "darwin" "freebsd" <> _ -> "freebsd" "win32" -> {compiler_id, _} = :erlang.system_info(:c_compiler_used) case compiler_id do :msc -> "msvc" _ -> to_string(compiler_id) end _ -> abi end arch = if os == "windows" do case String.downcase(System.get_env("PROCESSOR_ARCHITECTURE")) do "arm64" -> "aarch64" arch when arch in ["x64", "x86_64", "amd64"] -> "x86_64" arch -> arch end else arch end # FreeBSD's system_architecture is `arch-vendor-osversion` (e.g. amd64-portbld-freebsd13.5), # but the 3-tuple parser above puts the vendor in the `os` slot. Canonical precompiled # names use `unknown` as the vendor and normalize arch to LLVM-style. {arch, os} = if abi == "freebsd" do {if(arch == "amd64", do: "x86_64", else: arch), "unknown"} else {arch, os} end abi = System.get_env("TARGET_ABI", abi) os = System.get_env("TARGET_OS", os) arch = if String.match?(System.get_env("TARGET_CPU", ""), ~r/arm11[357]6/) do "armv6" else System.get_env("TARGET_ARCH", arch) end {Enum.join([arch, os, abi], "-"), [arch, os, abi]} end end def use_precompiled?(log? \\ false) do prefer_precompiled_default = if "dev" in Version.parse!(Metadata.version()).pre do "false" else "true" end prefer_precompiled = System.get_env("EVISION_PREFER_PRECOMPILED", prefer_precompiled_default) use_precompiled? = prefer_precompiled == "true" if log? do if use_precompiled? do Logger.info( "EVISION_PREFER_PRECOMPILED: true; try to download and use the precompiled library." ) else Logger.info( "EVISION_PREFER_PRECOMPILED: #{prefer_precompiled}; will not use precompiled binaries." ) end end use_precompiled? end def available_for_target?(target, log? \\ false) do available_for_target? = Enum.member?(available_targets(), target) if log? do if available_for_target? do Logger.info("Current target `#{target}` has precompiled binaries.") else Logger.warning("Current target `#{target}` does not have precompiled binaries.") end end available_for_target? end def available_for_nif_version?(nif_version, log? \\ false) do host_nif_version = case Enum.count(String.split(nif_version, ".")) do 2 -> Version.parse!("#{nif_version}.0") 3 -> Version.parse!(nif_version) _ -> raise RuntimeError, "unknown nif_version: #{nif_version}" end compile_nif_version = get_compile_nif_version() available_for_nif_version? = Version.compare(Version.parse!("#{compile_nif_version}.0"), host_nif_version) != :gt if log? do if available_for_nif_version? do Logger.info( "Current host NIF version is `#{nif_version}`, will use precompiled binaries with NIF version #{compile_nif_version}." ) else Logger.warning( "Current host NIF version `#{nif_version}` does not have precompiled binaries." ) end end available_for_nif_version? end # https_opts and related code are taken from # https://github.com/elixir-cldr/cldr_utils/blob/master/lib/cldr/http/http.ex @certificate_locations [ # Configured cacertfile System.get_env("ELIXIR_MAKE_CACERT"), # A little hack to use cacerts.pem in CAStore/certfi ## when `:evision` is a dependency in other application ## => Mix.ProjectStack.project_file() ## when `:evision` is the top application ## => __ENV__.file Path.join([ Path.dirname(Mix.ProjectStack.project_file() || __ENV__.file), "deps/castore/priv/cacerts.pem" ]), Path.join([ Path.dirname(Mix.ProjectStack.project_file() || __ENV__.file), "deps/certfi/priv/cacerts.pem" ]), # Debian/Ubuntu/Gentoo etc. "/etc/ssl/certs/ca-certificates.crt", # Fedora/RHEL 6 "/etc/pki/tls/certs/ca-bundle.crt", # OpenSUSE "/etc/ssl/ca-bundle.pem", # OpenELEC "/etc/pki/tls/cacert.pem", # CentOS/RHEL 7 "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # Open SSL on MacOS "/usr/local/etc/openssl/cert.pem", # MacOS & Alpine Linux "/etc/ssl/cert.pem" ] |> Enum.reject(&is_nil/1) @doc false def certificate_store do @certificate_locations |> Enum.find(&File.exists?/1) |> warning_if_no_cacertfile! |> :erlang.binary_to_list() end defp warning_if_no_cacertfile!(nil) do Logger.warning(""" No certificate trust store was found. Tried looking for: #{inspect(@certificate_locations)} A certificate trust store is required in order to download locales for your configuration. Since elixir_make could not detect a system installed certificate trust store one of the following actions may be taken: 1. Install the hex package `castore`. It will be automatically detected after recompilation. 2. Install the hex package `certifi`. It will be automatically detected after recomilation. 3. Specify the location of a certificate trust store by configuring it in environment variable: export ELIXIR_MAKE_CACERT="/path/to/cacerts.pem" """) "" end defp warning_if_no_cacertfile!(file) do file end defp https_opts(hostname) do cert_file = certificate_store() if secure_ssl?() and cert_file != [] do [ ssl: [ verify: :verify_peer, cacertfile: certificate_store(), depth: 4, ciphers: preferred_ciphers(), versions: protocol_versions(), eccs: preferred_eccs(), reuse_sessions: true, server_name_indication: hostname, secure_renegotiate: true, customize_hostname_check: [ match_fun: :public_key.pkix_verify_hostname_match_fun(:https) ] ] ] else [ ssl: [ verify: :verify_none, server_name_indication: hostname, secure_renegotiate: true, reuse_sessions: true, versions: protocol_versions(), ciphers: preferred_ciphers() ] ] end end def preferred_ciphers do preferred_ciphers = [ # Cipher suites (TLS 1.3): TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 %{cipher: :aes_128_gcm, key_exchange: :any, mac: :aead, prf: :sha256}, %{cipher: :aes_256_gcm, key_exchange: :any, mac: :aead, prf: :sha384}, %{cipher: :chacha20_poly1305, key_exchange: :any, mac: :aead, prf: :sha256}, # Cipher suites (TLS 1.2): ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256: # ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305: # ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 %{cipher: :aes_128_gcm, key_exchange: :ecdhe_ecdsa, mac: :aead, prf: :sha256}, %{cipher: :aes_128_gcm, key_exchange: :ecdhe_rsa, mac: :aead, prf: :sha256}, %{cipher: :aes_256_gcm, key_exchange: :ecdh_ecdsa, mac: :aead, prf: :sha384}, %{cipher: :aes_256_gcm, key_exchange: :ecdh_rsa, mac: :aead, prf: :sha384}, %{cipher: :chacha20_poly1305, key_exchange: :ecdhe_ecdsa, mac: :aead, prf: :sha256}, %{cipher: :chacha20_poly1305, key_exchange: :ecdhe_rsa, mac: :aead, prf: :sha256}, %{cipher: :aes_128_gcm, key_exchange: :dhe_rsa, mac: :aead, prf: :sha256}, %{cipher: :aes_256_gcm, key_exchange: :dhe_rsa, mac: :aead, prf: :sha384} ] :ssl.filter_cipher_suites(preferred_ciphers, []) end def protocol_versions do if otp_version() < 25 do [:"tlsv1.2"] else [:"tlsv1.2", :"tlsv1.3"] end end def otp_version do :erlang.system_info(:otp_release) |> List.to_integer() end def preferred_eccs do # TLS curves: X25519, prime256v1, secp384r1 preferred_eccs = [:secp256r1, :secp384r1] :ssl.eccs() -- (:ssl.eccs() -- preferred_eccs) end def secure_ssl? do case System.get_env("ELIXIR_MAKE_UNSAFE_HTTPS") do nil -> true "FALSE" -> false "false" -> false "nil" -> false "NIL" -> false _other -> true end end def download!(url, save_as, overwrite \\ false) def download!(url, save_as, false) do unless File.exists?(save_as) do download!(url, save_as, true) end checksum(save_as) end def download!(url, save_as, true) do with {:ok, body} <- download!(url) do File.write!(save_as, body) checksum(save_as) end end def download!(url) do opts = [body_format: :binary] arg = {url, []} # https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/inets # TODO: This may no longer be necessary from Erlang/OTP 26.0 or later. http_options = https_opts(String.to_charlist(URI.parse(url).host)) if proxy = System.get_env("HTTP_PROXY") || System.get_env("http_proxy") do Mix.shell().info("Using HTTP_PROXY: #{proxy}") %{host: host, port: port} = URI.parse(proxy) :httpc.set_options([{:proxy, {{String.to_charlist(host), port}, []}}]) end if proxy = System.get_env("HTTPS_PROXY") || System.get_env("https_proxy") do Mix.shell().info("Using HTTPS_PROXY: #{proxy}") %{host: host, port: port} = URI.parse(proxy) :httpc.set_options([{:https_proxy, {{String.to_charlist(host), port}, []}}]) end Mix.shell().info("Downloading precompiled tarball from: #{url}") case :httpc.request(:get, arg, http_options, opts) do {:ok, {{_, 200, _}, _, body}} -> {:ok, body} {:ok, {{_, status_code, _}, _, _body}} -> raise RuntimeError, "Cannot download file from #{url}: #{status_code}." {:error, reason} -> raise RuntimeError, "Cannot download file from #{url}: #{reason}." end end def filename( target, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version, with_ext \\ "" ) def filename( target, version, nif_version, _enable_contrib = false, _enable_cuda, _cuda_version, _cudnn_version, with_ext ) do "evision-nif_#{nif_version}-#{target}-#{version}#{with_ext}" end def filename( target, version, nif_version, _enable_contrib = true, _enable_cuda = false, _cuda_version, _cudnn_version, with_ext ) do "evision-nif_#{nif_version}-#{target}-contrib-#{version}#{with_ext}" end def filename( target, version, nif_version, _enable_contrib = true, _enable_cuda = true, cuda_version, cudnn_version, with_ext ) do "evision-nif_#{nif_version}-#{target}-contrib-cuda#{cuda_version}-cudnn#{cudnn_version}-#{version}#{with_ext}" end def get_download_url( target, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version ) do tar_file = filename( target, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version, ".tar.gz" ) "#{Metadata.github_url()}/releases/download/v#{version}/#{tar_file}" end def cache_dir do cache_opts = if System.get_env("MIX_XDG"), do: %{os: :linux}, else: %{} cache_dir = :filename.basedir(:user_cache, "", cache_opts) cache_dir = System.get_env("ELIXIR_MAKE_CACHE_DIR", cache_dir) File.mkdir_p!(cache_dir) cache_dir end defp precompiled_lock_dir(cache_dir) do Path.join(cache_dir, "evision-precompiled.lock") end defp with_precompiled_lock(lock_dir, fun) do case File.mkdir(lock_dir) do :ok -> try do fun.() after File.rm_rf(lock_dir) end {:error, :eexist} -> if stale_precompiled_lock?(lock_dir) do File.rm_rf(lock_dir) else Process.sleep(1000) end with_precompiled_lock(lock_dir, fun) {:error, reason} -> raise RuntimeError, "Failed to acquire precompiled install lock #{lock_dir}: #{inspect(reason)}" end end defp stale_precompiled_lock?(lock_dir) do with {:ok, %File.Stat{mtime: mtime}} <- File.stat(lock_dir) do now = :calendar.datetime_to_gregorian_seconds(:calendar.local_time()) modified = :calendar.datetime_to_gregorian_seconds(mtime) now - modified > @precompiled_lock_stale_seconds else _ -> false end end @checksum_algo :sha256 def checksum_algo, do: @checksum_algo def checksum(file_path, algo \\ @checksum_algo) do case File.read(file_path) do {:ok, content} -> file_hash = algo |> :crypto.hash(content) |> Base.encode16(case: :lower) {:ok, "#{algo}", "#{file_hash}"} {:error, reason} -> {:error, "cannot read the file for checksum comparison: #{inspect(file_path)}. " <> "Reason: #{inspect(reason)}"} end end def app_priv(app \\ Metadata.app()) when is_atom(app) do build_path = Mix.Project.build_path() Path.join([build_path, "lib", "#{app}", "priv"]) end def prepare( target, os, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version ) do name = filename( target, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version ) filename = filename( target, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version, ".tar.gz" ) cache_dir = cache_dir() cache_file = Path.join([cache_dir, filename]) unarchive_dest_dir = Path.join([cache_dir, name]) cached_priv_dir = Path.join([cache_dir, name, "priv"]) cached_elixir_dir = Path.join([cache_dir, name, "elixir_generated"]) evision_so_file = if os == "windows" do "evision.dll" else "evision.so" end windows_fix_so_file = if os == "windows" do "windows_fix.dll" else "windows_fix.so" end evision_so_file = Path.join([app_priv(), evision_so_file]) windows_fix_so_file = Path.join([app_priv(), windows_fix_so_file]) with_precompiled_lock(precompiled_lock_dir(cache_dir), fn -> # first we check if we already have the NIF file # if not, then we defintely need to copy it (and other files) from somewhere needs_copy = !File.exists?(evision_so_file) or !File.exists?(windows_fix_so_file) if needs_copy do # to copy the nif file and other files # we first check if they are cached in the cache dir if !File.dir?(unarchive_dest_dir) do # if `unarchive_dest_dir` does not exists # then perhaps we need to extract these files from the precompiled binary tarball # # to extract files from the tarball # we check if the tarball is cached in the cache dir {needs_download, needs_unarchive} = if File.exists?(cache_file) do # and it has to be a regular file if File.regular?(cache_file) do # of course we have to compute its checksum {:ok, algo, cache_file_checksum} = checksum(cache_file) Logger.info( "Precompiled binary tarball cached at #{cache_file}, #{algo}=#{cache_file_checksum}" ) # and verify the checksum in the map {checksum_matched?, algo_in_map, checksum_in_map} = verify_checksum(cache_file, algo, cache_file_checksum) if checksum_matched? do # if these checksum matches # we can extract files from the tarball {false, true} else Logger.error( "Checksum mismatched: #{cache_file}[#{algo}=#{cache_file_checksum}], expected:[#{algo_in_map}=#{checksum_in_map}]" ) Logger.warning("Will delete cached tarball #{cache_file} and re-download") # otherwise we delete the cached file and download it again {true, true} end else # not a regular file # remove it and download again File.rm_rf!(cache_file) Logger.warning( "Cached file at #{cache_file} is not a regular file, will delete it and re-download" ) {true, true} end else {true, true} end if needs_download do download_url = get_download_url( target, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version ) {:ok, _} = Application.ensure_all_started(:inets) {:ok, _} = Application.ensure_all_started(:ssl) # of course we have to verify its checksum too {:ok, algo, checksum} = download!(download_url, cache_file, true) Logger.info( "Precompiled binary tarball downloaded and saved to #{cache_file}, #{algo}=#{checksum}" ) {checksum_matched?, algo_in_map, checksum_in_map} = verify_checksum(cache_file, algo, checksum) if !checksum_matched? do msg = "Checksum mismatched: downloaded file: #{cache_file}[#{algo}=#{checksum}], expected:[#{algo_in_map}=#{checksum_in_map}]" Logger.error(msg) raise RuntimeError, msg end end if needs_unarchive do File.rm_rf!(unarchive_dest_dir) unarchive!(cache_file, cache_dir) end end # if `unarchive_dest_dir` exists and is a directory # then we can simply copy they to the expected locations deploy_from_dir!(cached_priv_dir, cached_elixir_dir) else :ok end end) end def verify_checksum(cache_file, algo, cache_file_checksum) do checksum_map = read_checksum_map() basename = Path.basename(cache_file) case Map.fetch(checksum_map, basename) do {:ok, algo_with_hash} -> [algo_in_map, hash] = String.split(algo_with_hash, ":") {cache_file_checksum == hash and algo_in_map == algo, algo_in_map, hash} :error -> {:error, "the precompiled NIF file does not exist in the checksum file. " <> "Please consider run: `EVISION_FETCH_PRECOMPILED=true mix evision.fetch --all` to generate the checksum file."} end end def deploy_from_dir!(cached_priv_dir, cached_elixir_dir) do app_priv = app_priv() generated_elixir_dir = Path.join([File.cwd!(), "lib", "generated"]) if File.exists?(app_priv) do File.rm_rf!(app_priv) end if File.exists?(generated_elixir_dir) do File.rm_rf!(generated_elixir_dir) end Logger.info("Copying priv directory: #{cached_priv_dir} => #{app_priv}") with {:ok, _} <- File.cp_r(cached_priv_dir, app_priv) do :ok else error -> msg = "Failed to copy priv directory, `File.cp_r`: #{inspect(error)}" Logger.error(msg) raise RuntimeError, msg end Logger.info( "Copying generated Elixir binding files: #{cached_elixir_dir} => #{generated_elixir_dir}" ) with {:ok, _} <- File.cp_r(cached_elixir_dir, generated_elixir_dir) do :ok else error -> msg = "Failed to copy generated Elixir binding files, `File.cp_r`: #{inspect(error)}" Logger.error(msg) raise RuntimeError, msg end end def unarchive!(filepath, to_directory) do File.mkdir_p!(to_directory) with :ok <- :erl_tar.extract(filepath, [:compressed, {:cwd, to_directory}]) do :ok else err -> msg = "Failed to unarchive tarball file: #{filepath}, error: #{inspect(err)}" Logger.error(msg) raise RuntimeError, msg end end def deploy_type(log? \\ false) do {target, [_arch, _os, abi]} = get_target() {:ok, %Version{pre: pre}} = Version.parse(Metadata.version()) nif_version = get_nif_version() if use_precompiled?(log?) and pre != ["dev"] and available_for_target?(target, log?) and available_for_nif_version?(nif_version, log?) do {:precompiled, abi} else {:build_from_source, abi} end end @impl true def run(_args) do {target, [_arch, os, _abi]} = get_target() evision_so_file = if os == "windows" do "evision.dll" else "evision.so" end windows_fix_so_file = if os == "windows" do "windows_fix.dll" else "windows_fix.so" end evision_so_file = Path.join([app_priv(), evision_so_file]) windows_fix_so_file = Path.join([app_priv(), windows_fix_so_file]) if !File.exists?(evision_so_file) or !File.exists?(windows_fix_so_file) do with {:precompiled, _} <- deploy_type(true) do version = Metadata.version() nif_version = get_compile_nif_version() enable_contrib = System.get_env("EVISION_ENABLE_CONTRIB", "true") == "true" enable_cuda = System.get_env("EVISION_ENABLE_CUDA", "false") == "true" {default_cuda_version, default_cudnn_version} = Metadata.default_cuda_version() cuda_version = System.get_env("EVISION_CUDA_VERSION", default_cuda_version) cudnn_version = System.get_env("EVISION_CUDNN_VERSION", default_cudnn_version) prepare( target, os, version, nif_version, enable_contrib, enable_cuda, cuda_version, cudnn_version ) else _ -> raise RuntimeError, "Cannot use precompiled binaries." end else :ok end end end defmodule Evision.MixProject do use Mix.Project require Logger alias Evision.MixProject.Metadata alias Mix.Tasks.Compile.EvisionPrecompiled @source_url "#{Metadata.github_url()}/tree/v#{Metadata.version()}" def project do {compilers, make_env} = if System.get_env("EVISION_FETCH_PRECOMPILED") != "true" do {deploy_type, target_abi} = EvisionPrecompiled.deploy_type(false) if deploy_type == :build_from_source do {cmake_options, enabled_modules} = generate_cmake_options() make_env = %{ "HAVE_NINJA" => "#{System.find_executable("ninja") != nil}", "OPENCV_VER" => opencv_versions(System.get_env("OPENCV_VER", Metadata.opencv_version())), "MAKE_BUILD_FLAGS" => System.get_env("MAKE_BUILD_FLAGS", "-j#{System.schedulers_online()}"), "CMAKE_OPTIONS" => cmake_options, "ENABLED_CV_MODULES" => enabled_modules, "TARGET_ABI" => System.get_env("TARGET_ABI", target_abi), "EVISION_GENERATE_LANG" => System.get_env("EVISION_GENERATE_LANG", "elixir"), "EVISION_PREFER_PRECOMPILED" => "false" } make_env = case :os.type() do {:win32, _} -> Map.put_new( make_env, "PYTHON3_EXECUTABLE", System.find_executable("python3") || System.find_executable("python") ) _ -> make_env end {[:elixir_make] ++ Mix.compilers(), make_env} else {[:evision_precompiled] ++ Mix.compilers(), %{}} end else {Mix.compilers(), %{}} end [ app: Metadata.app(), name: "Evision", version: Metadata.version(), elixir: "~> 1.11", deps: deps(), docs: docs(), aliases: aliases(), elixirc_paths: elixirc_paths(), erlc_paths: erlc_paths(), compilers: compilers, source_url: Metadata.github_url(), description: description(), package: package(), make_executable: make_executable(), make_makefile: make_makefile(), make_env: make_env, xref: [ exclude: [ :wx, :wxBitmap, :wxBoxSizer, :wxDC, :wxFrame, :wxImage, :wxMemoryDC, :wxPanel, :wxSizer, :wxStaticBoxSizer, :wxWindowDC, :wx_object ] ] ] end defp aliases do [ compile_opencv: &compile_opencv/1 ] end defp compile_opencv(_) do config = Mix.Project.config() Mix.shell().print_app() priv? = File.dir?("priv") Mix.Project.ensure_structure() config = Keyword.put_new(config, :make_targets, ["opencv"]) compiler_file = Path.join([ Path.dirname(Mix.ProjectStack.project_file() || __ENV__.file), "deps/elixir_make/lib/elixir_make/compiler.ex" ]) with [{ElixirMake.Compiler, _}] <- Code.require_file(compiler_file) do ElixirMake.Compiler.make(config, []) # IF there was no priv before and now there is one, we assume # the user wants to copy it. If priv already existed and was # written to it, then it won't be copied if build_embedded is # set to true. if not priv? and File.dir?("priv") do Mix.Project.build_structure() end {:ok, []} else _ -> Mix.shell().error("Cannot compile OpenCV only: failed to require file #{compiler_file}") end end def make_executable() do case :os.type() do {:win32, _} -> "nmake" {:unix, :freebsd} -> "gmake" _ -> "make" end end def make_makefile() do case :os.type() do {:win32, _} -> "Makefile.win" _ -> "Makefile" end end def opencv_versions(version) do if Enum.member?(Metadata.compatible_opencv_versions(), version) do version else Logger.warning( "OpenCV version #{version} is not in the compatible list, you may encounter compile errors" ) Logger.warning( "Compatible OpenCV versions: " <> (Metadata.compatible_opencv_versions() |> Enum.join(", ")) ) version end end def application do [ extra_applications: [:logger, :inets, :ssl], mod: {Evision.Application, []} ] end @module_configuration %{ # opencv/opencv_contrib opencv: [ # module name: is_enabled # note that these true values here only mean that we requested # these module to be compiled # some of them can be disabled due to dependency issues calib3d: true, core: true, dnn: true, features2d: true, flann: true, highgui: true, imgcodecs: true, imgproc: true, ml: true, photo: true, stitching: true, ts: true, video: true, videoio: true, gapi: false, world: false, python2: false, python3: false, java: false ], opencv_contrib: [ aruco: true, barcode: true, bgsegm: true, bioinspired: true, dnn_superres: true, face: true, hfs: true, img_hash: true, line_descriptor: true, mcc: true, plot: true, quality: true, rapid: true, reg: true, rgbd: true, saliency: true, shape: true, stereo: true, structured_light: true, surface_matching: true, text: true, tracking: true, wechat_qrcode: System.get_env("MIX_TARGET") != "ios" or System.get_env("MIX_TARGET") != "xros", xfeatures2d: true, ximgproc: true, xphoto: true, # no bindings yet datasets: false, dnn_objdetect: false, dpm: false, optflow: false, sfm: false, videostab: false, xobjdetect: false ], cuda: [ cudaarithm: true, cudabgsegm: true, cudacodec: true, cudafeatures2d: true, cudafilters: true, cudaimgproc: true, cudalegacy: true, cudaobjdetect: true, cudaoptflow: true, cudastereo: true, cudawarping: true, cudev: true ] } defp module_configuration, do: @module_configuration @enabled_img_codecs (if System.get_env("MIX_TARGET") == "ios" or System.get_env("MIX_TARGET") == "xros" do [] else [:png, :jpeg, :tiff, :webp, :openjpeg, :jasper, :openexr] end) # To make things easier, you can set `compile_mode` to `only_enabled_modules` so # that only modules specified in `enabled_modules` will be compiled. Like-wise, # set `except_disabled_modules` to only exclude modules in `disabled_modules`. # By default, the value of `compile_mode` is `auto`, which means to leave unspecified # modules to CMake to decide. @compile_mode :auto defp generate_cmake_options() do mc = module_configuration() enable_cuda = System.get_env("EVISION_ENABLE_CUDA", "false") enable_opencv_cuda = enable_cuda == "true" if enable_opencv_cuda do System.put_env("EVISION_ENABLE_CUDA", "true") else System.put_env("EVISION_ENABLE_CUDA", "false") end enable_contrib = System.get_env("EVISION_ENABLE_CONTRIB", "true") enable_opencv_contrib = enable_contrib == "true" if enable_opencv_cuda and !enable_opencv_contrib do Logger.warning( "EVISION_ENABLE_CUDA is set to true, while EVISION_ENABLE_CONTRIB is set to false. CUDA support will NOT be available." ) end if enable_opencv_contrib do System.put_env("EVISION_ENABLE_CONTRIB", "true") else System.put_env("EVISION_ENABLE_CONTRIB", "false") end all_modules = Enum.map(mc.opencv, fn {m, _} -> m end) ++ Enum.map(mc.opencv_contrib, fn {m, _} -> m end) enabled_modules = Enum.filter(mc.opencv, fn {_, e} -> e end) ++ if enable_opencv_contrib do Enum.filter(mc.opencv_contrib, fn {_, e} -> e end) else [] end ++ if enable_opencv_cuda do Enum.filter(mc.cuda, fn {_, e} -> e end) else [] end disabled_modules = Enum.filter(mc.opencv, fn {_, e} -> !e end) ++ if enable_opencv_contrib do Enum.filter(mc.opencv_contrib, fn {_, e} -> !e end) else [] end enabled_modules = Keyword.keys(enabled_modules) disabled_modules = Keyword.keys(disabled_modules) enabled_img_codecs = Application.get_env(:evision, :enabled_img_codecs, @enabled_img_codecs) compile_mode = Application.get_env(:evision, :compile_mode, @compile_mode) {cmake_options, enabled_modules} = case compile_mode do :auto -> cmake_options = (enabled_modules |> Enum.map(&"-D BUILD_opencv_#{Atom.to_string(&1)}=ON") |> Enum.join(" ")) <> " " <> (disabled_modules |> Enum.map(&"-D BUILD_opencv_#{&1}=OFF") |> Enum.join(" ")) {cmake_options, enabled_modules} :only_enabled_modules -> cmake_options = "-D BUILD_LIST=" <> (enabled_modules |> Enum.map(&Atom.to_string(&1)) |> Enum.join(",")) {cmake_options, enabled_modules} :except_disabled_modules -> enabled_modules = all_modules -- disabled_modules cmake_options = "-D BUILD_LIST=" <> (enabled_modules |> Enum.map(&Atom.to_string(&1)) |> Enum.join(",")) {cmake_options, enabled_modules} unrecognised_mode -> Mix.raise("unrecognised compile_mode for evision: #{inspect(unrecognised_mode)}") end options = cmake_options <> " " <> (enabled_img_codecs |> Enum.map(&"-D BUILD_#{Atom.to_string(&1) |> String.upcase()}=ON") |> Enum.join(" ")) <> " " options = if enable_opencv_cuda and enable_opencv_contrib do "#{options} -D WITH_CUDA=ON" else options end {options, enabled_modules |> Enum.map(&Atom.to_string(&1)) |> Enum.join(",")} end defp deps do [ # compilation {:elixir_make, "~> 0.7", runtime: false}, {:castore, "~> 0.1 or ~> 1.0"}, # runtime {:nx, "~> 0.6"}, # optional ## kino: for smart cells and better output in livebook {:kino, "~> 0.11", optional: true}, ## progress_bar: for the model zoo smart cell {:progress_bar, "~> 2.0 or ~> 3.0", optional: true}, # docs {:ex_doc, "~> 0.34.0", only: :docs, runtime: false}, # test {:scidata, "~> 0.1", only: :test} ] end defp docs do [ main: "Evision", source_ref: "v#{Metadata.version()}", source_url: @source_url, extras: extras(), before_closing_body_tag: &before_closing_body_tag/1, groups_for_extras: groups_for_extras(), groups_for_modules: groups_for_modules(), nest_modules_by_prefix: nest_modules_by_prefix(), groups_for_docs: groups_for_docs() ] end # Categories that ExDoc applies to functions inside a module. The generator # emits `@doc group: ""` for cv:: namespace # functions (the ~1400 that land in the root `Evision` module) so that the # function list mirrors OpenCV's docs taxonomy rather than appearing as one # flat brick. Entries here pull in everything OpenCV's headers tag plus the # binding-only `external`/`Enumerator` buckets. defp groups_for_docs do [ external: &(&1[:namespace] == :external), Enumerator: &(&1[:enum] == true) ] ++ for title <- opencv_doc_group_titles(), do: {String.to_atom(title), &doc_in_group?(&1, title)} end defp doc_in_group?(metadata, title), do: metadata[:group] == title defp opencv_doc_group_titles do [ "Operations on arrays", "Basic structures", "Asynchronous API", "XML/YAML/JSON Persistence", "Clustering", "Utility and system functions and macros", "Logging facilities", "OpenGL interoperability", "Optimization Algorithms", "OpenCL support", "Hardware Acceleration Layer", "Parallel Processing", "Parallel backends API", "Quaternion", "Image Filtering", "Geometric Image Transformations", "Miscellaneous Image Transformations", "Drawing Functions", "Color Space Conversions", "ColorMaps in OpenCV", "Planar Subdivision", "Histograms", "Structural Analysis and Shape Descriptors", "Motion Analysis and Object Tracking", "Feature Detection", "Object Detection", "Image Segmentation", "Image file reading and writing", "High-level GUI", "Qt New Functions", "Video I/O", "Query I/O API backends registry", "Camera Calibration", "Fisheye camera model", "3D vision functionality", "Stereo Correspondence", "Drawing Function of Keypoints and Matches", "Object Tracking", "Legacy Tracking API", "Optical Flow Algorithms", "Deep Neural Network module", "HDR imaging", "Inpainting", "Denoising", "Seamless Cloning", "Non-Photorealistic Rendering", "Contrast Preserving Decolorization", "Images stitching", "Features Finding and Images Matching", "Rotation Estimation", "Autocalibration", "Image Blenders", "Additional photo processing algorithms", "Extended Image Processing", "Fourier descriptors", "Structured forests for fast edge detection", "Superpixels", "EdgeDrawing", "Fast line detector", "Experimental 2D Features Matching Algorithm", "Binary descriptors for lines extracted from an image", "ArUco markers and boards detection for robust camera pose estimation", "Improved Background-Foreground Segmentation Methods", "Face Analysis", "Image segmentation", "Scene Text Detection", "Scene Text Recognition", "Shape Distance and Matching", "Surface Matching", "silhouette based 3D object tracking", "Alpha Matting", "Custom Calibration Pattern for 3D reconstruction", "RGB-Depth Processing", "Module-wrapper for FastCV hardware accelerated functions", "Signal Processing", "Math with F0-transform support", "Math with F1-transform support", "Fuzzy image processing", "The module brings implementations of different image hashing algorithms.", "The module brings implementations of intensity transformation algorithms to adjust image contrast.", "Common functions and classes", "Hierarchical Data Format version 5", "Drawing UTF-8 strings with freetype/harfbuzz" ] end defp extras do [ "README.md", "CHANGELOG.md", "Cheatsheet.cheatmd", "examples/getting_started.livemd", "examples/qrcode.livemd", "examples/warp_perspective.livemd", "examples/warp_polar.livemd", "examples/find_and_draw_contours.livemd", "examples/stitching.livemd", "examples/pca.livemd", "examples/photo-hdr.livemd", "examples/sudoku.livemd", "examples/cifar10.livemd", "examples/dnn-googlenet.livemd", "examples/dnn-detection-model.livemd", "examples/densenet121_benchmark.livemd", "examples/ml-svm.livemd", "examples/ml-decision_tree_and_random_forest.livemd" ] end defp groups_for_extras do [ Introduction: ["Cheatsheet.cheatmd", "CHANGELOG.md"], "Examples: Getting Started": ["examples/getting_started.livemd"], "Examples: Image Processing": [ "examples/qrcode.livemd", "examples/warp_perspective.livemd", "examples/warp_polar.livemd", "examples/find_and_draw_contours.livemd", "examples/stitching.livemd", "examples/pca.livemd", "examples/photo-hdr.livemd", "examples/sudoku.livemd" ], "Examples: Deep Neural Networks": [ "examples/cifar10.livemd", "examples/dnn-googlenet.livemd", "examples/dnn-detection-model.livemd", "examples/densenet121_benchmark.livemd" ], "Examples: Machine Learning": [ "examples/ml-svm.livemd", "examples/ml-decision_tree_and_random_forest.livemd" ] ] end # OpenCV's high-level module taxonomy mapped onto the Elixir module names # emitted by py_src. Order matters: ExDoc walks the list top-down and a # module ends up in the first group whose pattern it matches, so put more # specific patterns above broader ones. defp groups_for_modules do [ "Core Functionality": [ Evision.Constant, Evision.Backend, ~r/^Evision\.(Mat|SparseMat|UMat|UMatData|UMatUsageFlags)(\..*)?$/, ~r/^Evision\.(AsyncArray|Algorithm|AlgorithmHint|RotatedRect|KeyPoint|DMatch|RNG|TickMeter|Subdiv2D|IStreamReader)$/, ~r/^Evision\.(TermCriteria|PCA|SVD|FileNode|FileStorage|Param|Parallel|Samples|Animation)(\..*)?$/, ~r/^Evision\.(Error|Instr|QuatAssumeType|QuatEnum)(\..*)?$/, ~r/^Evision\.(Utils|UtilsFS|Utilslogging|Ipp|Signal|AccessFlag)(\..*)?$/, ~r/^Evision\.Formatter\..+$/ ], "Image Processing": [ ~r/^Evision\.(CLAHE|GeneralizedHough|GeneralizedHoughBallard|GeneralizedHoughGuil|LineSegmentDetector|IntelligentScissorsMB)$/ ], "Image File I/O": [ ~r/^Evision\.(Imread|Imwrite)[A-Z].*$/, Evision.ImageMetadataType ], "Video I/O": [ ~r/^Evision\.Video(Capture|Writer|IORegistry|Acceleration)[A-Z]*.*$/ ], "High-level GUI": [ Evision.HighGui, Evision.Wx, ~r/^Evision\.(Window|MouseEvent|Qt)[A-Z].*$/, ~r/^Evision\.(MarkerTypes|HersheyFonts|LineTypes|DrawMatchesFlags)$/ ], "Video Analysis": [ ~r/^Evision\.(KalmanFilter|BackgroundSubtractor.*|VariationalRefinement)$/, ~r/^Evision\.(DenseOpticalFlow|DISOpticalFlow|FarnebackOpticalFlow|SparseOpticalFlow|SparsePyrLKOpticalFlow)$/ ], "Camera Calibration & 3D Reconstruction": [ ~r/^Evision\.(FishEye|Omnidir|StereoBM|StereoSGBM|StereoMatcher)$/, ~r/^Evision\.(CirclesGridFinderParameters|UsacParams|SolveLPResult|SolvePnPMethod|HandEyeCalibrationMethod|RobotWorldHandEyeCalibrationMethod|LocalOptimMethod|SamplingMethod|NeighborSearchMethod|ScoreMethod|PolishingMethod)(\..*)?$/ ], "Stereo Correspondence": [ ~r/^Evision\.Stereo(\..*)?$/ ], "2D Features Framework": [ ~r/^Evision\.(AKAZE|AffineFeature|AgastFeatureDetector|AffineTransformer|BRISK|BFMatcher|FastFeatureDetector|Feature2D|FlannBasedMatcher|GFTTDetector|KAZE|MSER|ORB|SIFT|SimpleBlobDetector)(\..*)?$/, ~r/^Evision\.BOW[A-Z].*$/, ~r/^Evision\.DescriptorMatcher(\..*)?$/ ], "Object Detection": [ ~r/^Evision\.(CascadeClassifier|BaseCascadeClassifier|HOGDescriptor|FaceDetectorYN|GraphicalCodeDetector)(\..*)?$/, ~r/^Evision\.(QRCodeDetector|QRCodeDetectorAruco|QRCodeEncoder|Barcode)(\..*)?$/ ], "Computational Photography (HDR)": [ ~r/^Evision\.(Tonemap|TonemapDrago|TonemapMantiuk|TonemapReinhard)$/, ~r/^Evision\.(CalibrateCRF|CalibrateDebevec|CalibrateRobertson)$/, ~r/^Evision\.(MergeDebevec|MergeExposures|MergeMertens|MergeRobertson)$/, ~r/^Evision\.(AlignExposures|AlignMTB|SeamlessCloneFlags|Alphamat)$/ ], "Shape Matching": [ ~r/^Evision\.(ShapeContextDistanceExtractor|ShapeDistanceExtractor|ShapeTransformer|ShapeMatchModes|ThinPlateSplineShapeTransformer|HausdorffDistanceExtractor)$/, ~r/^Evision\.(ChiHistogramCostExtractor|NormHistogramCostExtractor|EMDHistogramCostExtractor|EMDL1HistogramCostExtractor|HistogramCostExtractor)$/ ], "Image Stitching": [ ~r/^Evision\.(Stitcher|PyRotationWarper|WarperCreator)(\..*)?$/, ~r/^Evision\.Detail(\..*)?$/ ], "Machine Learning (ML)": [ ~r/^Evision\.ML(\..*)?$/ ], "Deep Neural Networks (DNN)": [ ~r/^Evision\.DNN(\..*)?$/ ], "DNN Super-Resolution": [ ~r/^Evision\.DNNSuperRes(\..*)?$/ ], "Clustering & Search (FLANN)": [ ~r/^Evision\.Flann(\..*)?$/ ], "CUDA Acceleration": [ ~r/^Evision\.CUDA(\..*)?$/ ], "OpenCL Acceleration": [ ~r/^Evision\.OCL(\..*)?$/ ], "OpenGL Interop": [ ~r/^Evision\.OGL(\..*)?$/ ], "Object Tracking": [ ~r/^Evision\.(Tracker|TrackerCSRT|TrackerDaSiamRPN|TrackerGOTURN|TrackerKCF|TrackerMIL|TrackerNano|TrackerVit)(\..*)?$/, ~r/^Evision\.Legacy(\..*)?$/ ], "3D Volumetric Reconstruction (KinFu)": [ ~r/^Evision\.(KinFu|LargeKinfu|ColoredKinFu|DynaFu)(\..*)?$/ ], "ArUco Markers": [ ~r/^Evision\.ArUco(\..*)?$/ ], "Background Segmentation (bgsegm)": [ ~r/^Evision\.BgSegm(\..*)?$/ ], "Bio-Inspired Vision": [ ~r/^Evision\.Bioinspired(\..*)?$/ ], "Color Correction Model (CCM)": [ ~r/^Evision\.CCM(\..*)?$/ ], "Face Analysis": [ ~r/^Evision\.(Face|FaceRecognizerSF)(\..*)?$/ ], "Fuzzy Image Processing": [ ~r/^Evision\.Ft(\..*)?$/ ], "Hierarchical Feature Selection (HFS)": [ ~r/^Evision\.HFS(\..*)?$/ ], "Image Hashing": [ ~r/^Evision\.ImgHash(\..*)?$/ ], "Intensity Transformations": [ ~r/^Evision\.Intensitytransform(\..*)?$/ ], "Line Descriptors": [ ~r/^Evision\.LineDescriptor(\..*)?$/ ], LineMod: [ ~r/^Evision\.LineMod(\..*)?$/ ], "Macbeth Chart (MCC)": [ ~r/^Evision\.MCC(\..*)?$/ ], "Multi-Camera Calibration": [ ~r/^Evision\.MultiCalib(\..*)?$/ ], "Phase Unwrapping": [ ~r/^Evision\.PhaseUnwrapping(\..*)?$/ ], "2D Plotting": [ ~r/^Evision\.Plot(\..*)?$/ ], "Surface Matching (PPF Match 3D)": [ ~r/^Evision\.PPFMatch3D(\..*)?$/ ], "Image Quality Analysis": [ ~r/^Evision\.Quality(\..*)?$/ ], "RAPID 3D Tracking": [ ~r/^Evision\.Rapid(\..*)?$/ ], "Image Registration": [ ~r/^Evision\.Reg(\..*)?$/ ], "RGB-Depth Processing": [ ~r/^Evision\.RGBD(\..*)?$/ ], Saliency: [ ~r/^Evision\.Saliency(\..*)?$/ ], "Image Segmentation": [ ~r/^Evision\.Segmentation(\..*)?$/ ], "Structured Light": [ ~r/^Evision\.StructuredLight(\..*)?$/ ], "Text Detection & Recognition": [ ~r/^Evision\.Text(\..*)?$/ ], "WeChat QR Code": [ ~r/^Evision\.WeChatQRCode(\..*)?$/ ], "Extra 2D Features (xfeatures2d)": [ ~r/^Evision\.XFeatures2D(\..*)?$/ ], "Extended Image Processing (ximgproc)": [ ~r/^Evision\.XImgProc(\..*)?$/ ], "Extra Photo (xphoto)": [ ~r/^Evision\.XPhoto(\..*)?$/ ], "Model Zoo": [ ~r/^Evision\.Zoo(\..*)?$/ ], "Livebook Smart Cells": [ ~r/^Evision\.SmartCell(\..*)?$/ ], "IPC Handles": [ ~r/^Evision\.IPCHandle(\..*)?$/ ], "Enums & Flags": [ ~r/^Evision\..*(Flags|Types|Modes|Codes|Masks|Methods|Classes|Shapes|Mode|Filter)$/ ] ] end # Fold deeply-nested generated namespaces under a single prefix in the # sidebar. `Evision.DNN.Net` shows as `Net` under the `Evision.DNN` heading, # etc. defp nest_modules_by_prefix do [ Evision.ArUco, Evision.BgSegm, Evision.Bioinspired, Evision.CCM, Evision.ColoredKinFu, Evision.CUDA, Evision.Detail, Evision.DNN, Evision.DNNSuperRes, Evision.DynaFu, Evision.Face, Evision.Flann, Evision.HFS, Evision.ImgHash, Evision.IPCHandle, Evision.KinFu, Evision.LargeKinfu, Evision.Legacy, Evision.LineDescriptor, Evision.LineMod, Evision.MCC, Evision.ML, Evision.MultiCalib, Evision.OCL, Evision.OGL, Evision.PhaseUnwrapping, Evision.Plot, Evision.PPFMatch3D, Evision.Quality, Evision.Rapid, Evision.Reg, Evision.RGBD, Evision.Saliency, Evision.Segmentation, Evision.SmartCell, Evision.Stereo, Evision.Stitcher, Evision.StructuredLight, Evision.Text, Evision.Utils, Evision.WeChatQRCode, Evision.XFeatures2D, Evision.XImgProc, Evision.XPhoto, Evision.Zoo ] end defp before_closing_body_tag(:html) do """ """ end defp before_closing_body_tag(_), do: "" defp description() do "OpenCV-Erlang/Elixir binding." end def links do %{ "GitHub" => Metadata.github_url(), "Readme" => "#{Metadata.github_url()}/blob/v#{Metadata.version()}/README.md", "Changelog" => "#{Metadata.github_url()}/blob/v#{Metadata.version()}/CHANGELOG.md" } end defp elixirc_paths, do: ["lib"] defp erlc_paths, do: [] defp package() do [ name: "evision", # These are the default files included in the package files: ~w( c_src/evision_custom_headers c_src/modules c_src/windows_fix c_src/ArgInfo.hpp c_src/erlcompat.hpp c_src/evision_consts.h c_src/evision_custom_headers.h c_src/evision.cpp c_src/nif_utils.hpp cc_toolchain cmake lib/assets lib/evision lib/mix lib/smartcell lib/zoo lib/*.ex patches py_src/*.py scripts src/evision_mat.erl src/evision_highgui.erl src/evision_internal_structurise.erl src/evision.app.src checksum.exs checksum.erl CMakeLists.txt evision_precompiled.erl Makefile Makefile.win .formatter.exs mix.exs rebar.config README* LICENSE* CHANGELOG*), licenses: ["Apache-2.0"], links: links() ] end end