App bundling improvements (#1279)

This commit is contained in:
Wojtek Mach 2022-07-18 11:51:17 +02:00 committed by GitHub
parent 5a3f5ff4b6
commit 4ec919f60b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 110 additions and 54 deletions

View file

@ -46,7 +46,6 @@ defmodule WxDemo.MixProject do
]
]
],
event_handler: WxDemo.Window,
macos: [
build_dmg: macos_notarization != nil,
notarization: macos_notarization

View file

@ -1,6 +1,6 @@
defmodule AppBuilder do
def bundle(release) do
options = validate_options(release.options[:app])
options = validate_options(release.options[:app] || [])
case os() do
:macos ->
@ -19,25 +19,28 @@ defmodule AppBuilder do
end
def init do
{:ok, _} = Registry.register(AppBuilder.Registry, "app_event_subscribers", [])
if input = System.get_env("APP_BUILDER_INPUT") do
__rpc__(self(), input)
__rpc__(input)
end
end
def __rpc__(event_handler) do
input = IO.read(:line) |> String.trim()
__rpc__(event_handler, input)
def __rpc__ do
IO.read(:line)
|> String.trim()
|> __rpc__()
end
def __rpc__(event_handler, "open_app") do
send(event_handler, :open_app)
def __rpc__("open_app") do
dispatch(:open_app)
end
def __rpc__(event_handler, "open_url:" <> url) do
send(event_handler, {:open_url, url})
def __rpc__("open_url:" <> url) do
dispatch({:open_url, url})
end
def __rpc__(event_handler, "open_file:" <> path) do
def __rpc__("open_file:" <> path) do
path =
if os() == :windows do
String.replace(path, "\\", "/")
@ -45,7 +48,13 @@ defmodule AppBuilder do
path
end
send(event_handler, {:open_file, path})
dispatch({:open_file, path})
end
defp dispatch(message) do
Registry.dispatch(AppBuilder.Registry, "app_event_subscribers", fn entries ->
for {pid, _} <- entries, do: send(pid, message)
end)
end
defp validate_options(options) do
@ -55,7 +64,6 @@ defmodule AppBuilder do
all: [
:name,
:icon_path,
:event_handler,
url_schemes: [],
document_types: [],
additional_paths: []
@ -83,15 +91,20 @@ defmodule AppBuilder do
windows: []
}
options = validate_options(options, root_allowed_options, os)
Keyword.update!(options, :document_types, fn document_types ->
options
|> validate_options(root_allowed_options, os)
|> Keyword.put_new_lazy(:name, &default_name/0)
|> Keyword.update!(:document_types, fn document_types ->
Enum.map(document_types, fn options ->
validate_options(options, document_type_allowed_options, os)
end)
end)
end
defp default_name do
Mix.Project.config()[:app] |> to_string |> Macro.camelize()
end
defp validate_options(options, allowed, os) do
{macos_options, options} = Keyword.pop(options, :macos, [])
{windows_options, options} = Keyword.pop(options, :windows, [])

View file

@ -0,0 +1,13 @@
defmodule AppBuilder.Application do
@moduledoc false
use Application
def start(_type, _args) do
children = [
{Registry, keys: :duplicate, name: AppBuilder.Registry}
]
Supervisor.start_link(children, strategy: :one_for_one, name: AppBuilder.Supervisor)
end
end

View file

@ -1,13 +1,8 @@
<%
event_handler = @app_options |> Keyword.fetch!(:event_handler) |> inspect()
additional_paths = Enum.map_join(@app_options[:additional_paths], ":", &"\\(resourcePath)/#{&1}")
additional_paths =
["rel/erts-#{@release.erts_version}/bin"] ++ @app_options[:additional_paths]
|> Enum.map_join(":", &"\\(resourcePath)/#{&1}")
%>
import Cocoa
%>import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
var releaseTask: Process!
@ -45,7 +40,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
}
}
func startRelease(_ input : String) -> Process {
@ -78,7 +72,7 @@ func rpc(_ event: String) {
let task = buildReleaseTask()
task.standardInput = input
input.fileHandleForWriting.write("\(event)\n".data(using: .utf8)!)
task.arguments = ["rpc", "AppBuilder.__rpc__(<%= event_handler %>)"]
task.arguments = ["rpc", "AppBuilder.__rpc__()"]
try! task.run()
task.waitUntilExit()
@ -88,17 +82,17 @@ func rpc(_ event: String) {
}
func buildReleaseTask() -> Process {
let releaseScriptPath = Bundle.main.path(forResource: "rel/bin/<%= @release.name %>", ofType: "")!
let task = Process()
task.launchPath = Bundle.main.path(forResource: "rel/bin/<%= @release.name %>", ofType: "")!
task.environment = ProcessInfo.processInfo.environment
<%= if additional_paths != "" do %>
let resourcePath = Bundle.main.resourcePath ?? ""
let additionalPaths = "<%= additional_paths %>"
let path = task.environment!["PATH"] ?? ""
task.environment!["PATH"] = "\(additionalPaths):\(path)"
<% end %>
var environment = ProcessInfo.processInfo.environment
let path = environment["PATH"] ?? ""
environment["PATH"] = "\(additionalPaths):\(path)"
let task = Process()
task.environment = environment
task.launchPath = releaseScriptPath
task.standardOutput = logFile
task.standardError = logFile
return task

View file

@ -7,6 +7,7 @@ app_name = Keyword.fetch!(@app_options, :name)
;General
Name "<%= app_name %>"
ManifestDPIAware true
OutFile "<%= app_name %>Install.exe"
Unicode True
InstallDir "$LOCALAPPDATA\<%= app_name %>"

View file

@ -1,10 +1,9 @@
<%
event_handler = @app_options |> Keyword.fetch!(:event_handler) |> inspect()
additional_paths =
["rel/erts-#{@release.erts_version}/bin"] ++ @app_options[:additional_paths]
|> Enum.map(&("root & \"" <> String.replace(&1, "/", "\\") <> ";\" & "))
for path <- Keyword.fetch!(@app_options, :additional_paths), into: "" do
"root & \"" <> String.replace(path, "/", "\\") <> ";\" & "
end
%>
@ -19,10 +18,12 @@ End If
Set shell = CreateObject("WScript.Shell")
Set env = shell.Environment("Process")
<%= if additional_paths != "" do %>
env("PATH") = <%= additional_paths %>env("PATH")
<% end %>
' try release rpc, if release is down, this will fail but that's ok.
cmd = "echo " & input & " | """ & script & """ rpc ""AppBuilder.__rpc__(<%= event_handler %>)"""
cmd = "echo " & input & " | """ & script & """ rpc ""AppBuilder.__rpc__()"""
status = shell.Run("cmd /c " & cmd, 0, true)
' try release start, if release is up, this will fail but that's ok.

View file

@ -20,6 +20,7 @@ defmodule AppBuilder.MixProject do
def application do
[
mod: {AppBuilder.Application, []},
extra_applications: [:logger, :eex, :inets, :ssl, :crypto]
]
end

View file

@ -6,6 +6,7 @@ defmodule Livebook.MixProject do
@description "Interactive and collaborative code notebooks - made with Phoenix LiveView"
@app_elixir_version "1.13.4"
@app_rebar3_version "3.19.0"
def project do
[
@ -160,8 +161,8 @@ defmodule Livebook.MixProject do
]
]
],
event_handler: LivebookApp,
additional_paths: [
"rel/erts-#{:erlang.system_info(:version)}/bin",
"rel/vendor/elixir/bin"
],
macos: [
@ -201,5 +202,7 @@ defmodule Livebook.MixProject do
release
|> Standalone.copy_otp()
|> Standalone.copy_elixir(@app_elixir_version)
|> Standalone.copy_hex()
|> Standalone.copy_rebar3(@app_rebar3_version)
end
end

View file

@ -1,5 +1,7 @@
set RELEASE_NODE=livebook_app
set RELEASE_MODE=interactive
set MIX_ARCHIVES=!RELEASE_ROOT!\vendor\archives
set MIX_REBAR3=!RELEASE_ROOT!\vendor\rebar3
set LIVEBOOK_SHUTDOWN_ENABLED=true
set cookie_path="!RELEASE_ROOT!\releases\COOKIE"

View file

@ -1,7 +1,9 @@
export RELEASE_NODE=livebook_app
export RELEASE_MODE=interactive
export LIVEBOOK_SHUTDOWN_ENABLED=true
export MIX_ARCHIVES="${RELEASE_ROOT}/vendor/archives"
export MIX_REBAR3="${RELEASE_ROOT}/vendor/rebar3"
export WX_MACOS_NON_GUI_APP=1
export LIVEBOOK_SHUTDOWN_ENABLED=true
cookie_path="${RELEASE_ROOT}/releases/COOKIE"
if [ ! -f $cookie_path ]; then

View file

@ -7,7 +7,9 @@ defmodule Standalone do
"""
@spec copy_otp(Mix.Release.t()) :: Mix.Release.t()
def copy_otp(release) do
{erts_source, otp_bin_dir, otp_lib_dir} = otp_dirs()
erts_source = Path.join(:code.root_dir(), "erts-#{release.erts_version}")
otp_bin_dir = Path.join(:code.root_dir(), "bin")
otp_lib_dir = :code.lib_dir()
# 1. copy erts/bin
release_erts_bin_dir = Path.join(release.path, "erts-#{release.erts_version}/bin")
@ -39,6 +41,10 @@ defmodule Standalone do
release_lib_dir = Path.join(release.path, "lib")
cp_r!(otp_lib_dir, release_lib_dir)
for dir <- Path.wildcard("#{release_lib_dir}/*/doc/{xml,html,pdf}") do
File.rm_rf!(dir)
end
# 3. copy boot files
release_bin_dir = Path.join(release.path, "bin")
@ -58,7 +64,7 @@ defmodule Standalone do
download_elixir_at_destination(standalone_destination, elixir_version)
filenames =
case os() do
case AppBuilder.os() do
:macos ->
["elixir", "elixirc", "mix", "iex"]
@ -83,12 +89,40 @@ defmodule Standalone do
:zip.unzip(String.to_charlist(path), cwd: destination)
end
defp otp_dirs do
version = :erlang.system_info(:version)
root_dir = :code.root_dir()
@doc """
Copies Hex into the release.
"""
@spec copy_hex(Mix.Release.t()) :: Mix.Release.t()
def copy_hex(release) do
release_archives_dir = Path.join(release.path, "vendor/archives")
File.mkdir_p!(release_archives_dir)
{:filename.join(root_dir, 'erts-#{version}'), :filename.join(root_dir, 'bin'),
:code.lib_dir()}
hex_version = Keyword.fetch!(Application.spec(:hex), :vsn)
source_hex_path = Path.join(Mix.path_for(:archives), "hex-#{hex_version}")
release_hex_path = Path.join(release_archives_dir, "hex-#{hex_version}")
cp_r!(source_hex_path, release_hex_path)
release
end
@doc """
Copies Rebar3 into the release.
"""
@spec copy_rebar3(Mix.Release.t(), version :: String.t()) :: Mix.Release.t()
def copy_rebar3(release, version) do
url = "https://github.com/erlang/rebar3/releases/download/#{version}/rebar3"
path = Path.join(System.tmp_dir!(), "rebar3_#{version}")
unless File.exists?(path) do
binary = fetch_body!(url)
File.write!(path, binary, [:binary])
end
destination = Path.join(release.path, "vendor/rebar3")
File.cp!(path, destination)
make_executable(destination)
release
end
defp fetch_body!(url) do
@ -105,13 +139,6 @@ defmodule Standalone do
defp make_executable(path), do: File.chmod!(path, 0o755)
defp os() do
case :os.type() do
{:unix, :darwin} -> :macos
{:win32, _} -> :windows
end
end
defp cp_r!(source, destination) do
File.cp_r!(source, destination, fn _, _ -> false end)
end