livebook/lib/livebook_cli.ex
Jonatan Kłosko edabdf9d84 Use a non-hidden file to track if escript assets are extracted
When tmpdir is cleaned on macOS, files are removed, directories are
kept, so we use an extra file as a flag to easily check if the files
are extracted. However, files starting with dot are not removed as
part of the cleanup, so we need to use a regular name.
2025-03-21 20:57:06 +09:00

129 lines
3.4 KiB
Elixir

defmodule LivebookCLI do
def usage() do
"""
Usage: livebook [command] [options]
Available commands:
livebook server Starts the Livebook web application
The --help and --version options can be given instead of a command for usage and versioning information.
"""
end
def main(args) do
{:ok, _} = Application.ensure_all_started(:elixir)
extract_priv!()
:ok = Application.load(:livebook)
if unix?() do
Application.put_env(:elixir, :ansi_enabled, true)
end
call(args)
end
defp unix?(), do: match?({:unix, _}, :os.type())
defp call([arg]) when arg in ["--help", "-h"], do: display_help()
defp call([arg]) when arg in ["--version", "-v"], do: display_version()
defp call([task_name | args]) do
case find_task(task_name) do
nil ->
IO.ANSI.format([:red, "Unknown command #{task_name}\n"]) |> IO.puts()
IO.write(usage())
task ->
call_task(task, args)
end
end
defp call(_args), do: IO.write(usage())
defp find_task("server"), do: LivebookCLI.Server
defp find_task(_), do: nil
defp call_task(task, [arg]) when arg in ["--help", "-h"] do
IO.write(task.usage())
end
defp call_task(task, args) do
try do
task.call(args)
rescue
error in OptionParser.ParseError ->
IO.ANSI.format([
:red,
Exception.message(error),
"\n\nFor more information try --help"
])
|> IO.puts()
error ->
IO.ANSI.format([:red, Exception.format(:error, error, __STACKTRACE__), "\n"]) |> IO.puts()
end
end
defp display_help() do
IO.puts("Livebook is an interactive notebook system for Elixir\n")
IO.write(usage())
end
defp display_version() do
IO.puts(:erlang.system_info(:system_version))
IO.puts("Elixir " <> System.build_info()[:build])
version = Livebook.Config.app_version()
IO.puts("\nLivebook #{version}")
end
import Record
defrecord(:zip_file, extract(:zip_file, from_lib: "stdlib/include/zip.hrl"))
defp extract_priv!() do
archive_dir = Path.join(Livebook.Config.tmp_path(), "escript")
extracted_path = Path.join(archive_dir, "extracted")
in_archive_priv_path = ~c"livebook/priv"
# In dev we want to extract fresh directory on every boot
if Livebook.Config.app_version() =~ "-dev" do
File.rm_rf!(archive_dir)
end
# When temporary directory is cleaned by the OS, the directories
# may be left in place, so we use a regular file (extracted) to
# check if the extracted archive is already available
if not File.exists?(extracted_path) do
{:ok, sections} = :escript.extract(:escript.script_name(), [])
archive = Keyword.fetch!(sections, :archive)
file_filter = fn zip_file(name: name) ->
List.starts_with?(name, in_archive_priv_path)
end
case :zip.extract(archive, cwd: String.to_charlist(archive_dir), file_filter: file_filter) do
{:ok, _} ->
:ok
{:error, error} ->
print_error_and_exit(
"Livebook failed to extract archive files, reason: #{inspect(error)}"
)
end
File.touch!(extracted_path)
end
priv_dir = Path.join(archive_dir, in_archive_priv_path)
Application.put_env(:livebook, :priv_dir, priv_dir, persistent: true)
end
@spec print_error_and_exit(String.t()) :: no_return()
defp print_error_and_exit(message) do
IO.ANSI.format([:red, message]) |> IO.puts()
System.halt(1)
end
end