defmodule Boombox.Mixfile do use Mix.Project @version "0.2.13" @github_url "https://github.com/membraneframework/boombox" def project do [ app: :boombox, version: @version, elixir: "~> 1.13", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps(), dialyzer: dialyzer(), releases: releases(), aliases: aliases(), # hex description: "Boombox", package: package(), # docs name: "Boombox", source_url: @github_url, docs: docs() ] end def application do [ extra_applications: [], mod: if burrito?() do {Boombox.Utils.BurritoApp, []} else {Boombox.Application, []} end ] end defp burrito?, do: System.get_env("BOOMBOX_BURRITO") != nil defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_env), do: ["lib"] defp deps do [ {:membrane_core, "~> 1.2"}, {:membrane_transcoder_plugin, "~> 0.3.2"}, {:membrane_webrtc_plugin, "~> 0.26.1"}, {:membrane_mp4_plugin, "~> 0.36.0"}, {:membrane_realtimer_plugin, "~> 0.11.0"}, {:membrane_http_adaptive_stream_plugin, "~> 0.20.2"}, {:membrane_rtmp_plugin, "~> 0.29.1"}, {:membrane_rtsp_plugin, "~> 0.7.0"}, {:membrane_rtp_plugin, "~> 0.31.1"}, {:membrane_rtp_format, "~> 0.11.0"}, {:membrane_rtp_aac_plugin, "~> 0.9.0"}, {:membrane_rtp_h264_plugin, "~> 0.20.0"}, {:membrane_rtp_opus_plugin, "~> 0.10.0"}, {:membrane_rtp_h265_plugin, "~> 0.5.2"}, {:membrane_vpx_plugin, "~> 0.4.2"}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0"}, {:membrane_hackney_plugin, "~> 0.11.0"}, {:membrane_ffmpeg_swscale_plugin, "~> 0.16.2"}, {:membrane_wav_plugin, "~> 0.10.1"}, {:membrane_ivf_plugin, "~> 0.8.0"}, {:membrane_ogg_plugin, "~> 0.5.0"}, {:membrane_stream_plugin, "~> 0.4.0"}, {:membrane_srt_plugin, "~> 0.1.1"}, {:membrane_portaudio_plugin, "~> 0.19.2"}, {:membrane_sdl_plugin, "~> 0.18.5"}, {:membrane_simple_rtsp_server, "~> 0.1.7", only: :test}, {:image, "~> 0.54.0"}, {:async_test, github: "software-mansion-labs/elixir_async_test", only: :test}, {:playwright, "~> 1.49.1-alpha.2", only: :test}, {:cowlib, "~> 2.16", override: true, only: :test}, {:burrito, "~> 1.0", runtime: burrito?(), optional: true}, {:ex_doc, ">= 0.40.0", only: :dev, runtime: false}, {:dialyxir, ">= 0.0.0", only: :dev, runtime: false}, {:credo, ">= 0.0.0", only: :dev, runtime: false} ] end defp dialyzer() do opts = [ flags: [:error_handling] ] if System.get_env("CI") == "true" do # Store PLTs in cacheable directory for CI File.mkdir_p!(Path.join([__DIR__, "priv", "plts"])) [plt_local_path: "priv/plts", plt_core_path: "priv/plts"] ++ opts else opts end end defp package do [ maintainers: ["Membrane Team"], licenses: ["Apache-2.0"], links: %{ "GitHub" => @github_url, "Membrane Framework Homepage" => "https://membrane.stream" }, files: ["lib", "mix.exs", "README*", "LICENSE*", ".formatter.exs", "bin/boombox"] ] end defp aliases do [docs: [&generate_docs_examples/1, "docs", &append_llms_links/1]] end defp examples do [ {"basic.livemd", "Basic Examples"}, {"streaming.livemd", "Streaming Examples"}, {"stream_processing.livemd", "Stream Processing Examples"}, {"ai.livemd", "AI Examples"} ] end defp generate_docs_examples(_) do docs_install_config = "boombox = :boombox" for {filename, _title} <- examples() do modified_livebook = File.read!("examples/#{filename}") |> String.replace( ~r/# MIX_INSTALL_CONFIG_BEGIN\n(.|\n)*# MIX_INSTALL_CONFIG_END\n/U, docs_install_config, global: false ) File.write!("#{Mix.Project.build_path()}/#{filename}", modified_livebook) end end defp append_llms_links(_args) do output_dir = docs()[:output] || "doc" path = Path.join(output_dir, "llms.txt") if File.exists?(path) do existing = File.read!(path) footer = """ ## See Also - [Membrane Framework AI Skill](https://hexdocs.pm/membrane_core/skill.md) - [Membrane Core](https://hexdocs.pm/membrane_core/llms.txt) """ File.write!(path, String.trim_trailing(existing) <> footer) else IO.warn("#{path} not found — llms.txt was not generated, check your ex_doc configuration") end end defp docs do example_extras = examples() |> Enum.map(fn {filename, title} -> {"#{Mix.Project.build_path()}/#{filename}", title: title} end) [ main: "readme", extras: ["README.md"] ++ example_extras ++ [{"LICENSE", title: "License"}], source_ref: "v#{@version}", nest_modules_by_prefix: [Boombox] ] end defp releases() do {burrito_wrap, burrito_config} = if burrito?() do {&Burrito.wrap/1, burrito_config()} else {& &1, []} end [ boombox: [ steps: [:assemble, &restore_symlinks/1, burrito_wrap] ] ++ burrito_config, server: [ steps: [:assemble, &restore_symlinks/1] ] ] end defp burrito_config() do current_os = case :os.type() do {:win32, _} -> :windows {:unix, :darwin} -> :darwin {:unix, :linux} -> :linux end arch_string = :erlang.system_info(:system_architecture) |> to_string() |> String.downcase() |> String.split("-") |> List.first() current_cpu = case arch_string do "x86_64" -> :x86_64 "arm64" -> :aarch64 "aarch64" -> :aarch64 _ -> :unknown end [ burrito: [ targets: [ current: [os: current_os, cpu: current_cpu] ] ] ] end # mix release doesn't preserve symlinks, but replaces # them with whatever they point to, while # bundlex uses symlinks to provide precompiled deps. # That makes the release size enormous, so this workaroud # recreates the symlinks by replacing the copied data # with new symlinks pointing to bundlex's # priv/shared/precompiled directory defp restore_symlinks(release) do base_dir = Path.join([ __DIR__, "_build", Atom.to_string(Mix.env()), "rel", Atom.to_string(release.name), "lib" ]) shared = Path.join(base_dir, "bundlex*/priv/shared/precompiled/*") |> Path.wildcard() |> Enum.map(&Path.relative_to(&1, base_dir)) |> Map.new(&{Path.basename(&1), &1}) # Restore symlinks # /priv/shared/precompiled/ -> ../../../../bundlex-/priv/shared//lib Path.join(base_dir, "*/priv/bundlex/*/*") |> Path.wildcard() |> Enum.each(fn path -> name = Path.basename(path) if Map.has_key?(shared, name) do ln = Path.join([base_dir, shared[name], "lib"]) |> Path.relative_to(Path.dirname(path), force: true) File.rm_rf!(path) File.ln_s!(ln, path) end end) # Restore symlinks # bundlex-/priv/shared/precompiled//lib/.dylib -> ..dylib # e.g. libvpx.dylib -> libvpx.11.dylib Path.join(base_dir, "bundlex*/priv/shared/precompiled/*/lib") |> Path.wildcard() |> Enum.map(fn lib_dir -> File.ls!(lib_dir) |> Enum.group_by(&(String.split(&1, ".") |> List.first())) |> Enum.each(fn {_lib_name, libs} -> lib_to_symlink_to = Enum.max_by(libs, &String.length/1) libs |> Enum.filter(&(&1 != lib_to_symlink_to)) |> Enum.map(fn lib_to_replace -> lib_to_replace_path = Path.join(lib_dir, lib_to_replace) File.rm_rf!(lib_to_replace_path) File.ln_s!(lib_to_symlink_to, lib_to_replace_path) end) end) end) release end end