Skip to content

Commit

Permalink
Fix the NAMESPACE=1 release issue (#203)
Browse files Browse the repository at this point in the history
* Fix the NAMESPACE=1 release issue

As we are using relative paths, we need to rewrite the boot file and two app-related definitions and references.

* Correct the command in README
  • Loading branch information
scottming authored Jun 3, 2023
1 parent 95f5354 commit 792cca9
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 47 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ To use lexical as a language server while working on lexical source code,
use the following to produce the release and avoid bugs:

```
NAMESPACE=1 mix release lexical
mix release lexical && NAMESPACE=1 mix release lexical --overwrite
```
23 changes: 23 additions & 0 deletions apps/proto/lib/mix/tasks/namespace.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule Mix.Tasks.Namespace do
def app_mappings do
%{
common: Lexical.Common,
remote_control: Lexical.RemoteControl,
lexical_shared: Lexical.Shared,
lexical_plugin: Lexical.Plugin,
sourceror: Sourceror,
path_glob: PathGlob,
server: Lexical.Server,
protocol: Lexical.Protocol,
proto: Proto
}
end

def apps_to_namespace do
Map.keys(app_mappings())
end

def root_modules do
Map.values(app_mappings())
end
end
31 changes: 4 additions & 27 deletions apps/proto/lib/mix/tasks/namespace/abstract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ defmodule Mix.Tasks.Namespace.Abstract do
The abstract syntax is rather tersely defined here:
https://www.erlang.org/doc/apps/erts/absform.html
"""

alias Mix.Tasks.Namespace

def rewrite(abstract_format) when is_list(abstract_format) do
Enum.map(abstract_format, &rewrite/1)
end
Expand Down Expand Up @@ -289,32 +292,6 @@ defmodule Mix.Tasks.Namespace.Abstract do
end

defp rewrite_module(module) do
split_module =
module
|> Atom.to_string()
|> String.split(".")

case split_module do
# namespace app references, e.g. in remote_control.ex
["remote_control"] ->
:lx_remote_control

["common"] ->
:lx_common

["Lexical"] ->
:LXRelease

["Elixir" | rest] ->
rest
|> Enum.map(fn
"Lexical" -> "LXRelease"
other -> other
end)
|> Module.concat()

_ ->
module
end
Namespace.Module.apply(module)
end
end
20 changes: 11 additions & 9 deletions apps/proto/lib/mix/tasks/namespace/beams.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
defmodule Mix.Tasks.Namespace.Beams do
alias Mix.Tasks.Namespace.Transform
alias Mix.Tasks.Namespace

use Mix.Task

def run(_) do
app_names =
for {module, _} <- Mix.Project.apps_paths() do
module
end

app_globs = "{" <> Enum.join(app_names, ",") <> "}"
app_globs = "{" <> Enum.join(Namespace.apps_to_namespace(), ",") <> "}"

module_beams =
[Mix.Project.build_path(), "lib", app_globs, "**", "ebin", "Elixir.*Lexical*.beam"]
[
Mix.Project.build_path(),
"lib",
app_globs,
"**",
"ebin",
"{Elixir.*Lexical,Elixir.Sourceror,Elixir.PathGlob}*.beam"
]
|> Path.join()
|> Path.wildcard()

Expand All @@ -28,7 +30,7 @@ defmodule Mix.Tasks.Namespace.Beams do
beam_files
|> Enum.with_index()
|> Enum.each(fn {beam_file, index} ->
Transform.transform(beam_file)
Namespace.Transform.transform(beam_file)

IO.write("\r")
percent_complete = format_percent(index, file_count)
Expand Down
41 changes: 41 additions & 0 deletions apps/proto/lib/mix/tasks/namespace/module.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule Mix.Tasks.Namespace.Module do
alias Mix.Tasks.Namespace

@deps_to_namespace ~w(Sourceror PathGlob)

def apply(module_name) do
apps_to_namespace = Namespace.apps_to_namespace() -- ~w(proto protocol server)a

if module_name in apps_to_namespace do
:"lx_#{module_name}"
else
module_name
|> Atom.to_string()
|> String.split(".")
|> maybe_namespace_split_module(module_name)
end
end

defp maybe_namespace_split_module(["Lexical"], _) do
LXRelease
end

defp maybe_namespace_split_module(["Elixir" | rest], _) do
rest
|> Enum.map(fn
"Lexical" ->
"LXRelease"

name when name in @deps_to_namespace ->
"LX#{name}"

other ->
other
end)
|> Module.concat()
end

defp maybe_namespace_split_module(_, erlang_module) do
erlang_module
end
end
76 changes: 67 additions & 9 deletions apps/proto/lib/mix/tasks/namespace/release.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@ defmodule Mix.Tasks.Namespace.Release do
@moduledoc """
`remote_control.app` must be namespaced too
"""
alias Mix.Tasks.Namespace
use Mix.Task
@release_files ~w(start.script lexical.rel)
@apps_to_rewrite ~w(remote_control common server protocol proto)

@release_files ~w(start.script lexical.rel start_clean.script)
@boot_files ~w(start.boot start_clean.boot)

def run(_) do
apps_to_namespace = Namespace.apps_to_namespace()
release = Mix.Release.from_config!(:lexical, Mix.Project.config(), [])
Enum.each(@apps_to_rewrite, &update_app(release.path, release.version_path, &1))

Enum.each(
apps_to_namespace,
&update_app(release.path, release.version_path, apps_to_namespace, &1)
)

# namespace .app filenames because the filename is used as identifier by BEAM
Enum.each(@apps_to_rewrite, &namespace_app_file(release.path, &1))
Enum.each(apps_to_namespace, &namespace_app_file(release.path, &1))
Enum.each(@boot_files, &update_boot_file(boot_file_path(release.version_path, &1)))
Mix.Shell.IO.info("\nApplied namespace to release app.")
end

Expand All @@ -27,24 +36,35 @@ defmodule Mix.Tasks.Namespace.Release do
Path.join([ebin_path(release_path, app_name), "#{app_name}.app"])
end

defp update_app(release_path, release_version_path, app_name) do
defp boot_file_path(release_version_path, boot_name) do
Path.join(release_version_path, boot_name)
end

defp update_app(release_path, release_version_path, referencing_apps, app_name) do
# Rename references in release scripts
release_file_paths = Enum.map(@release_files, &Path.join([release_version_path, &1]))
# Rename references in the dependencies of app files
apps_file_paths = Enum.map(@apps_to_rewrite, &app_file_path(release_path, &1))
apps_file_paths = Enum.map(referencing_apps, &app_file_path(release_path, &1))
paths = apps_file_paths ++ release_file_paths

Enum.each(paths, &update_file_contents(&1, app_name))
end

defp update_file_contents(path, app_name) do
contents = File.read!(path)
updated_contents = update_script_contents(contents, app_name)
File.write!(path, updated_contents)
end

defp update_script_contents(contents, app_name) do
# matches if preceding characters involves either of: , " [ { [:blank:]
# this way it doesn't match on substrings or directory names
updated_contents =
String.replace(contents, ~r/([,"\[{[:blank:]])#{app_name}/, "\\1lx_#{app_name}")
contents = String.replace(contents, ~r/([,"\[{[:blank:]])#{app_name}/, "\\1lx_#{app_name}")

File.write!(path, updated_contents)
Enum.reduce(Namespace.root_modules(), contents, fn root_module, contents ->
new_modules = root_module |> Namespace.Module.apply() |> to_string()
String.replace(contents, to_string(root_module), new_modules)
end)
end

defp namespace_app_file(release_path, app_name) do
Expand All @@ -53,4 +73,42 @@ defmodule Mix.Tasks.Namespace.Release do
namespaced_app_file = Path.join([ebin_path, "lx_" <> "#{app_name}.app"])
:ok = File.rename(app_file_path, namespaced_app_file)
end

defp update_boot_file(path) do
term = path |> File.read!() |> :erlang.binary_to_term()
{script, script_info, module_infos} = term
new_module_infos = Enum.map(module_infos, &update_module_list/1)
namespaced_contents = :erlang.term_to_binary({script, script_info, new_module_infos})
File.write!(path, namespaced_contents)
end

defp update_module_list({load_info, modules}) when is_list(modules) do
new_modules = apply_namespace(modules)
{load_info, new_modules}
end

defp update_module_list({:apply, {:application, mode, [{:application, app_name, app_info}]}}) do
new_app_info =
Keyword.update(app_info, :modules, [], fn modules ->
Enum.map(modules, &Namespace.Module.apply/1)
end)

{:apply, {:application, mode, [{:application, app_name, new_app_info}]}}
end

defp update_module_list(original) do
original
end

defp apply_namespace(modules_list) when is_list(modules_list) do
Enum.map(modules_list, &apply_namespace/1)
end

defp apply_namespace(module) when is_atom(module) do
Namespace.Module.apply(module)
end

defp apply_namespace(original) do
original
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ defmodule Lexical.RemoteControl.ProjectNode do
end

defp port_wrapper_executable do
:remote_control
"remote_control"
|> String.to_atom()
|> :code.priv_dir()
|> Path.join("port_wrapper.sh")
end
Expand Down

0 comments on commit 792cca9

Please sign in to comment.