mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-11-10 14:11:29 +08:00
macOS App includes elixir, erlang for standalone mode (#929)
This commit is contained in:
parent
1b7b3080e1
commit
5110f85e73
3 changed files with 133 additions and 5 deletions
|
|
@ -97,10 +97,12 @@ defmodule AppBuilder.MacOS do
|
||||||
:logo_path,
|
:logo_path,
|
||||||
:info_plist,
|
:info_plist,
|
||||||
:url_schemes,
|
:url_schemes,
|
||||||
:document_types
|
:document_types,
|
||||||
|
:additional_paths
|
||||||
])
|
])
|
||||||
|
|
||||||
app_name = Keyword.fetch!(options, :name)
|
app_name = Keyword.fetch!(options, :name)
|
||||||
|
additional_paths = Keyword.get(options, :additional_paths, [])
|
||||||
|
|
||||||
app_bundle_path = Path.join([Mix.Project.build_path(), "rel", "#{app_name}.app"])
|
app_bundle_path = Path.join([Mix.Project.build_path(), "rel", "#{app_name}.app"])
|
||||||
File.rm_rf!(app_bundle_path)
|
File.rm_rf!(app_bundle_path)
|
||||||
|
|
@ -109,7 +111,7 @@ defmodule AppBuilder.MacOS do
|
||||||
|
|
||||||
File.mkdir_p!("tmp")
|
File.mkdir_p!("tmp")
|
||||||
launcher_src_path = "tmp/Launcher.swift"
|
launcher_src_path = "tmp/Launcher.swift"
|
||||||
File.write!(launcher_src_path, launcher())
|
File.write!(launcher_src_path, launcher(additional_paths))
|
||||||
launcher_path = Path.join([app_bundle_path, "Contents", "MacOS", app_name <> "Launcher"])
|
launcher_path = Path.join([app_bundle_path, "Contents", "MacOS", app_name <> "Launcher"])
|
||||||
File.mkdir_p!(Path.dirname(launcher_path))
|
File.mkdir_p!(Path.dirname(launcher_path))
|
||||||
|
|
||||||
|
|
@ -131,7 +133,11 @@ defmodule AppBuilder.MacOS do
|
||||||
release
|
release
|
||||||
end
|
end
|
||||||
|
|
||||||
defp launcher do
|
defp launcher(additional_paths) do
|
||||||
|
additional_paths = additional_paths
|
||||||
|
|> Enum.map(&("\\(resourcePath)#{&1}"))
|
||||||
|
|> Enum.join(":")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import Foundation
|
import Foundation
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
@ -147,7 +153,16 @@ defmodule AppBuilder.MacOS do
|
||||||
|
|
||||||
let releaseScriptPath = Bundle.main.path(forResource: "rel/bin/mac_app", ofType: "")!
|
let releaseScriptPath = Bundle.main.path(forResource: "rel/bin/mac_app", ofType: "")!
|
||||||
|
|
||||||
|
let resourcePath = Bundle.main.resourcePath ?? ""
|
||||||
|
let additionalPaths = "#{additional_paths}"
|
||||||
|
|
||||||
|
var environment = ProcessInfo.processInfo.environment
|
||||||
|
let path = environment["PATH"] ?? ""
|
||||||
|
|
||||||
|
environment["PATH"] = "\\(additionalPaths):\\(path)"
|
||||||
|
|
||||||
let task = Process()
|
let task = Process()
|
||||||
|
task.environment = environment
|
||||||
task.launchPath = releaseScriptPath
|
task.launchPath = releaseScriptPath
|
||||||
task.arguments = ["start"]
|
task.arguments = ["start"]
|
||||||
task.standardOutput = logFile
|
task.standardOutput = logFile
|
||||||
|
|
|
||||||
15
mix.exs
15
mix.exs
|
|
@ -115,13 +115,15 @@ defmodule Livebook.MixProject do
|
||||||
],
|
],
|
||||||
mac_app: [
|
mac_app: [
|
||||||
include_executables_for: [:unix],
|
include_executables_for: [:unix],
|
||||||
|
include_erts: false,
|
||||||
rel_templates_path: "rel/app",
|
rel_templates_path: "rel/app",
|
||||||
steps: [:assemble, &build_mac_app/1]
|
steps: [:assemble, &standalone_erlang_elixir/1, &build_mac_app/1]
|
||||||
],
|
],
|
||||||
mac_app_dmg: [
|
mac_app_dmg: [
|
||||||
include_executables_for: [:unix],
|
include_executables_for: [:unix],
|
||||||
|
include_erts: false,
|
||||||
rel_templates_path: "rel/app",
|
rel_templates_path: "rel/app",
|
||||||
steps: [:assemble, &build_mac_app_dmg/1]
|
steps: [:assemble, &standalone_erlang_elixir/1, &build_mac_app_dmg/1]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
@ -131,11 +133,20 @@ defmodule Livebook.MixProject do
|
||||||
version: @version,
|
version: @version,
|
||||||
logo_path: "rel/app/mac-icon.png",
|
logo_path: "rel/app/mac-icon.png",
|
||||||
url_schemes: ["livebook"],
|
url_schemes: ["livebook"],
|
||||||
|
additional_paths: ["/rel/vendor/bin", "/rel/vendor/elixir/bin"],
|
||||||
document_types: [
|
document_types: [
|
||||||
%{name: "LiveMarkdown", role: "Editor", extensions: ["livemd"]}
|
%{name: "LiveMarkdown", role: "Editor", extensions: ["livemd"]}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
defp standalone_erlang_elixir(release) do
|
||||||
|
Code.require_file("rel/app/standalone.exs")
|
||||||
|
|
||||||
|
release
|
||||||
|
|> Standalone.copy_erlang()
|
||||||
|
|> Standalone.copy_elixir("1.13.2")
|
||||||
|
end
|
||||||
|
|
||||||
defp build_mac_app(release) do
|
defp build_mac_app(release) do
|
||||||
AppBuilder.build_mac_app(release, @app_options)
|
AppBuilder.build_mac_app(release, @app_options)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
102
rel/app/standalone.exs
Normal file
102
rel/app/standalone.exs
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
defmodule Standalone do
|
||||||
|
@moduledoc false
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Copies ERTS into the release.
|
||||||
|
"""
|
||||||
|
@spec copy_erlang(Mix.Release.t()) :: Mix.Release.t()
|
||||||
|
def copy_erlang(release) do
|
||||||
|
{erts_source, erts_bin_dir, erts_lib_dir, _erts_version} = erts_data()
|
||||||
|
|
||||||
|
erts_destination_source = Path.join(release.path, "vendor/bin")
|
||||||
|
File.mkdir_p!(erts_destination_source)
|
||||||
|
|
||||||
|
erts_source
|
||||||
|
|> Path.join("bin")
|
||||||
|
|> File.cp_r!(erts_destination_source, fn _, _ -> false end)
|
||||||
|
|
||||||
|
_ = File.rm(Path.join(erts_destination_source, "erl"))
|
||||||
|
_ = File.rm(Path.join(erts_destination_source, "erl.ini"))
|
||||||
|
|
||||||
|
erts_destination_source
|
||||||
|
|> Path.join("erl")
|
||||||
|
|> File.write!(~S"""
|
||||||
|
#!/bin/sh
|
||||||
|
SELF=$(readlink "$0" || true)
|
||||||
|
if [ -z "$SELF" ]; then SELF="$0"; fi
|
||||||
|
BINDIR="$(cd "$(dirname "$SELF")" && pwd -P)"
|
||||||
|
ROOTDIR="${ERL_ROOTDIR:-"$(dirname "$(dirname "$BINDIR")")"}"
|
||||||
|
EMU=beam
|
||||||
|
PROGNAME=$(echo "$0" | sed 's/.*\///')
|
||||||
|
export EMU
|
||||||
|
export ROOTDIR
|
||||||
|
export BINDIR
|
||||||
|
export PROGNAME
|
||||||
|
exec "$BINDIR/erlexec" ${1+"$@"}
|
||||||
|
""")
|
||||||
|
executable!(Path.join(erts_destination_source, "erl"))
|
||||||
|
|
||||||
|
# Copy lib
|
||||||
|
erts_destination_lib = Path.join(release.path, "lib")
|
||||||
|
File.mkdir_p!(erts_destination_lib)
|
||||||
|
|
||||||
|
erts_lib_dir
|
||||||
|
|> File.cp_r!(erts_destination_lib, fn _, _ -> false end)
|
||||||
|
|
||||||
|
# copy *.boot files to <resource_path>/bin
|
||||||
|
erts_destination_bin = Path.join(release.path, "bin")
|
||||||
|
|
||||||
|
boot_files = erts_bin_dir |> Path.join("*.boot") |> Path.wildcard() |> Enum.map(& String.split(&1, "/") |> List.last())
|
||||||
|
|
||||||
|
File.mkdir_p!(erts_destination_bin)
|
||||||
|
|
||||||
|
for boot_file <- boot_files do
|
||||||
|
erts_bin_dir |> Path.join(boot_file) |> File.cp!(Path.join(erts_destination_bin, boot_file))
|
||||||
|
end
|
||||||
|
|
||||||
|
%{release | erts_source: erts_source}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Copies elixir into the release.
|
||||||
|
"""
|
||||||
|
@spec copy_elixir(Mix.Release.t(), elixir_version :: String.t()) :: Mix.Release.t()
|
||||||
|
def copy_elixir(release, elixir_version) do
|
||||||
|
# download and unzip
|
||||||
|
standalone_destination = Path.join(release.path, "vendor/elixir")
|
||||||
|
download_elixir_at_destination(standalone_destination, elixir_version)
|
||||||
|
|
||||||
|
# make executable
|
||||||
|
["elixir", "elixirc", "mix", "iex"]
|
||||||
|
|> Enum.map(&(executable!(Path.join(standalone_destination, "bin/#{&1}"))))
|
||||||
|
|
||||||
|
release
|
||||||
|
end
|
||||||
|
|
||||||
|
defp download_elixir_at_destination(destination, elixir_version) do
|
||||||
|
url = "https://github.com/elixir-lang/elixir/releases/download/v#{elixir_version}/Precompiled.zip"
|
||||||
|
binary = fetch_body!(url)
|
||||||
|
File.write!("/tmp/elixir_#{elixir_version}.zip", binary, [:binary])
|
||||||
|
:zip.unzip('/tmp/elixir_#{elixir_version}.zip', cwd: destination)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp erts_data do
|
||||||
|
version = :erlang.system_info(:version)
|
||||||
|
{:filename.join(:code.root_dir(), 'erts-#{version}'), :filename.join(:code.root_dir(), 'bin'), :code.lib_dir(), version}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_body!(url) do
|
||||||
|
Logger.debug("Downloading elixir from #{url}")
|
||||||
|
case Livebook.Utils.HTTP.request(:get, url, [timeout: :infinity]) do
|
||||||
|
{:ok, 200, _headers, body} ->
|
||||||
|
body
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
raise "couldn't fetch #{url}: #{inspect(error)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp executable!(path), do: File.chmod!(path, 0o755)
|
||||||
|
|
||||||
|
end
|
||||||
Loading…
Add table
Reference in a new issue