From 79c4600b40d85121e360c08d4431e253f7284780 Mon Sep 17 00:00:00 2001 From: doorgan Date: Thu, 21 Aug 2025 20:26:10 -0300 Subject: [PATCH 1/4] Revert "fix: use correct build directory when namespacing expert" This reverts commit b6540ddffa210acd1ac03f9d7317f8baa3bcdc70. --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 92af9f9f..30fe453a 100644 --- a/justfile +++ b/justfile @@ -84,7 +84,7 @@ build-expert: #!/usr/bin/env bash cd apps/expert MIX_ENV={{ env('MIX_ENV', 'prod')}} mix compile - just namespace-expert _build/{{ env('MIX_ENV', 'prod') }} + just namespace-expert [doc('Start the local development server')] start *opts="--port 9000": build-engine From bead8891d2e03dc8fe8675c91295f34a904742b7 Mon Sep 17 00:00:00 2001 From: doorgan Date: Thu, 21 Aug 2025 20:26:11 -0300 Subject: [PATCH 2/4] Revert "fix: properly set the mix env when building expert" This reverts commit 4caf2581ffa480aa87de70b6b9fef20207873414. --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 30fe453a..3264c768 100644 --- a/justfile +++ b/justfile @@ -83,7 +83,7 @@ namespace-expert directory="_build/prod": build-expert: #!/usr/bin/env bash cd apps/expert - MIX_ENV={{ env('MIX_ENV', 'prod')}} mix compile + MIX_ENV=prod mix compile just namespace-expert [doc('Start the local development server')] From 5d4402bcf12a88c5547c6593872f1733fc5363c4 Mon Sep 17 00:00:00 2001 From: doorgan Date: Thu, 21 Aug 2025 20:26:14 -0300 Subject: [PATCH 3/4] Revert "chore: support dev server using tcp transport (#43)" This reverts commit 15581e7025eb911ab64e7bb70f1137980c25aeb6. --- .iex.exs | 7 ++ .iex.namespaced.exs | 2 + apps/engine/lib/engine/plugin/discovery.ex | 6 +- apps/expert/.iex.exs | 14 ++++ apps/expert/lib/expert/application.ex | 20 +---- apps/expert/lib/expert/engine_node.ex | 12 +-- apps/expert/lib/expert/release.ex | 9 +-- apps/expert/mix.exs | 2 +- apps/forge/lib/forge/namespace/abstract.ex | 22 +----- apps/forge/lib/forge/namespace/module.ex | 26 ++---- apps/forge/lib/forge/namespace/path.ex | 40 +++++----- .../namespace/transform/app_directories.ex | 36 ++++++--- .../lib/forge/namespace/transform/apps.ex | 78 ++++++++---------- .../lib/forge/namespace/transform/beams.ex | 40 +++++----- .../lib/forge/namespace/transform/boots.ex | 6 +- .../lib/forge/namespace/transform/configs.ex | 10 +-- .../lib/forge/namespace/transform/scripts.ex | 59 +++++++------- apps/forge/lib/mix/tasks/namespace.ex | 79 ++++--------------- justfile | 78 ++++-------------- 19 files changed, 203 insertions(+), 343 deletions(-) create mode 100644 .iex.exs create mode 100644 .iex.namespaced.exs create mode 100644 apps/expert/.iex.exs diff --git a/.iex.exs b/.iex.exs new file mode 100644 index 00000000..9776e5b0 --- /dev/null +++ b/.iex.exs @@ -0,0 +1,7 @@ +use Expert.IEx.Helpers + +try do + Mix.ensure_application!(:observer) +rescue + _ -> nil +end diff --git a/.iex.namespaced.exs b/.iex.namespaced.exs new file mode 100644 index 00000000..18b495db --- /dev/null +++ b/.iex.namespaced.exs @@ -0,0 +1,2 @@ +use XPExpert.Server.IEx.Helpers +alias XPExpert, as: EXpert diff --git a/apps/engine/lib/engine/plugin/discovery.ex b/apps/engine/lib/engine/plugin/discovery.ex index 99a07fac..d0d1d60f 100644 --- a/apps/engine/lib/engine/plugin/discovery.ex +++ b/apps/engine/lib/engine/plugin/discovery.ex @@ -17,7 +17,7 @@ defmodule Engine.Plugin.Discovery do @namespaced_document_module [:Forge, :Document] |> Module.concat() - |> Forge.Namespace.Module.run(apps: [:forge], roots: [Forge]) + |> Forge.Namespace.Module.apply() def run do for {app_name, _, _} <- :application.loaded_applications(), @@ -49,12 +49,10 @@ defmodule Engine.Plugin.Discovery do end defp namespace_module(module) when is_atom(module) do - app = Application.get_application(module) - module |> :code.which() |> List.to_string() - |> Forge.Namespace.Transform.Beams.apply_to_all(apps: [app]) + |> Forge.Namespace.Transform.Beams.apply() end defp unload_module(module) do diff --git a/apps/expert/.iex.exs b/apps/expert/.iex.exs new file mode 100644 index 00000000..670986db --- /dev/null +++ b/apps/expert/.iex.exs @@ -0,0 +1,14 @@ +alias Forge.Project + +other_project = + [ + File.cwd!(), + "..", + "..", + "..", + "eakins" + ] + |> Path.join() + |> Path.expand() + +project = Forge.Project.new("file://#{other_project}") diff --git a/apps/expert/lib/expert/application.ex b/apps/expert/lib/expert/application.ex index b18dda81..94f639dc 100644 --- a/apps/expert/lib/expert/application.ex +++ b/apps/expert/lib/expert/application.ex @@ -12,31 +12,13 @@ defmodule Expert.Application do @impl true def start(_type, _args) do - {m, f, a} = Application.get_env(:expert, :arg_parser) - - argv = apply(m, f, a) - - {opts, _, _invalid} = - OptionParser.parse(argv, - strict: [port: :integer] - ) - - buffer_opts = - case opts[:port] do - port when is_integer(port) -> - [communication: {GenLSP.Communication.TCP, [port: port]}] - - _ -> - [] - end - children = [ document_store_child_spec(), {DynamicSupervisor, Expert.Project.DynamicSupervisor.options()}, {DynamicSupervisor, name: Expert.DynamicSupervisor}, {GenLSP.Assigns, [name: Expert.Assigns]}, {Task.Supervisor, name: :expert_task_queue}, - {GenLSP.Buffer, [name: Expert.Buffer] ++ buffer_opts}, + {GenLSP.Buffer, name: Expert.Buffer}, {Expert, name: Expert, buffer: Expert.Buffer, diff --git a/apps/expert/lib/expert/engine_node.ex b/apps/expert/lib/expert/engine_node.ex index 14f7f7b4..be2ab967 100644 --- a/apps/expert/lib/expert/engine_node.ex +++ b/apps/expert/lib/expert/engine_node.ex @@ -166,19 +166,11 @@ defmodule Expert.EngineNode do # When Engine is built in CI for a version matrix, we'll need to check if # we have the right version downloaded, and if not, we should download it. defp glob_paths do - engine_path = System.get_env("EXPERT_ENGINE_PATH", default_engine_path()) - - Logger.info("Using Engine path: #{Path.expand(engine_path)}") - - engine_path - |> Path.expand() + :expert + |> :code.priv_dir() |> Path.join("lib/**/ebin") |> Path.wildcard() end - - defp default_engine_path do - :expert |> :code.priv_dir() |> Path.join("engine") - end end @stop_timeout 1_000 diff --git a/apps/expert/lib/expert/release.ex b/apps/expert/lib/expert/release.ex index 997464d8..676f20a3 100644 --- a/apps/expert/lib/expert/release.ex +++ b/apps/expert/lib/expert/release.ex @@ -1,5 +1,7 @@ defmodule Expert.Release do def assemble(release) do + Mix.Task.run(:namespace, [release.path]) + engine_path = Path.expand("../../../engine", __DIR__) source = Path.join([engine_path, "_build/dev_ns"]) @@ -8,13 +10,10 @@ defmodule Expert.Release do Path.join([ release.path, "lib", - "#{release.name}-#{release.version}", - "priv", - "engine" + "xp_expert-#{release.version}", + "priv" ]) - File.mkdir_p!(dest) - File.cp_r!(source, dest) release diff --git a/apps/expert/mix.exs b/apps/expert/mix.exs index a9eb9172..d0cfa286 100644 --- a/apps/expert/mix.exs +++ b/apps/expert/mix.exs @@ -23,7 +23,7 @@ defmodule Expert.MixProject do def application do [ - extra_applications: [:logger, :runtime_tools, :kernel, :erts, :observer, :wx], + extra_applications: [:logger, :runtime_tools, :kernel, :erts, :observer], mod: {Expert.Application, []} ] end diff --git a/apps/forge/lib/forge/namespace/abstract.ex b/apps/forge/lib/forge/namespace/abstract.ex index a4fcaf91..8a2fa074 100644 --- a/apps/forge/lib/forge/namespace/abstract.ex +++ b/apps/forge/lib/forge/namespace/abstract.ex @@ -6,25 +6,6 @@ defmodule Forge.Namespace.Abstract do https://www.erlang.org/doc/apps/erts/absform.html """ - def code_from(path) do - with {:ok, {_orig_module, code_parts}} <- :beam_lib.chunks(path, [:abstract_code]), - {:ok, {:raw_abstract_v1, forms}} <- Keyword.fetch(code_parts, :abstract_code) do - {:ok, forms} - else - _ -> - {:error, :not_found} - end - end - - def run(abstract_format, opts) when is_list(abstract_format) do - fn -> - Process.put(:abstract_code_opts, opts) - Enum.map(abstract_format, fn af -> rewrite(af) end) - end - |> Task.async() - |> Task.await() - end - def rewrite(abstract_format) when is_list(abstract_format) do Enum.map(abstract_format, &rewrite/1) end @@ -313,7 +294,6 @@ defmodule Forge.Namespace.Abstract do end defp rewrite_module(module) do - opts = Process.get(:abstract_code_opts) - Forge.Namespace.Module.run(module, opts) + Forge.Namespace.Module.apply(module) end end diff --git a/apps/forge/lib/forge/namespace/module.ex b/apps/forge/lib/forge/namespace/module.ex index 38927a78..64ba6634 100644 --- a/apps/forge/lib/forge/namespace/module.ex +++ b/apps/forge/lib/forge/namespace/module.ex @@ -1,21 +1,18 @@ defmodule Forge.Namespace.Module do @namespace_prefix "XP" - def run(module_name, opts) do - apps = Keyword.fetch!(opts, :apps) - roots = Keyword.fetch!(opts, :roots) - + def apply(module_name) do cond do prefixed?(module_name) -> module_name - opts[:do_apps] && module_name in apps -> + module_name in Mix.Tasks.Namespace.app_names() -> :"xp_#{module_name}" true -> module_name |> Atom.to_string() - |> apply_namespace(roots) + |> apply_namespace() end end @@ -31,7 +28,7 @@ defmodule Forge.Namespace.Module do def prefixed?(@namespace_prefix <> _), do: true - def prefixed?("xp_" <> _), + def prefixed?("xp" <> _), do: true def prefixed?([?x, ?p, ?_ | _]), do: true @@ -41,9 +38,8 @@ defmodule Forge.Namespace.Module do def prefixed?(_), do: false - defp apply_namespace("Elixir." <> rest, roots) do - roots - |> Enum.filter(fn module -> Macro.classify_atom(module) == :alias end) + defp apply_namespace("Elixir." <> rest) do + Mix.Tasks.Namespace.root_modules() |> Enum.map(fn module -> module |> Module.split() |> List.first() end) |> Enum.reduce_while(rest, fn root_module, module -> if has_root_module?(root_module, module) do @@ -61,14 +57,8 @@ defmodule Forge.Namespace.Module do |> Module.concat() end - defp apply_namespace(erlang_module, roots) do - erlang_module = String.to_atom(erlang_module) - - if erlang_module in roots do - :"xp_#{erlang_module}" - else - erlang_module - end + defp apply_namespace(erlang_module) do + String.to_atom(erlang_module) end defp has_root_module?(root_module, root_module), do: true diff --git a/apps/forge/lib/forge/namespace/path.ex b/apps/forge/lib/forge/namespace/path.ex index 88deb558..060feea8 100644 --- a/apps/forge/lib/forge/namespace/path.ex +++ b/apps/forge/lib/forge/namespace/path.ex @@ -1,35 +1,33 @@ defmodule Forge.Namespace.Path do - alias Forge.Namespace - - def run(path, opts) when is_list(path) do + def apply(path) when is_list(path) do path |> List.to_string() - |> run(opts) + |> apply() |> String.to_charlist() end - def run(path, opts) when is_binary(path) do - apps = Keyword.fetch!(opts, :apps) - + def apply(path) when is_binary(path) do path |> Path.split() - |> Enum.map(fn path_component -> - Enum.reduce(apps, path_component, fn app_name, path -> - [path | vsn] = String.split(path, "-") + |> Enum.map(&replace_namespaced_apps/1) + |> Path.join() + end + + defp replace_namespaced_apps(path_component) do + Enum.reduce(Mix.Tasks.Namespace.app_names(), path_component, fn app_name, path -> + [path | vsn] = String.split(path, "-") - if path == Atom.to_string(app_name) do - new_path = - app_name - |> Namespace.Module.run(opts) - |> Atom.to_string() + if path == Atom.to_string(app_name) do + new_path = + app_name + |> Forge.Namespace.Module.apply() + |> Atom.to_string() - rebuild_path(new_path, vsn) - else - rebuild_path(path, vsn) - end - end) + rebuild_path(new_path, vsn) + else + rebuild_path(path, vsn) + end end) - |> Path.join() end defp rebuild_path(path, []), do: path diff --git a/apps/forge/lib/forge/namespace/transform/app_directories.ex b/apps/forge/lib/forge/namespace/transform/app_directories.ex index 940e678e..fa477581 100644 --- a/apps/forge/lib/forge/namespace/transform/app_directories.ex +++ b/apps/forge/lib/forge/namespace/transform/app_directories.ex @@ -1,21 +1,33 @@ defmodule Forge.Namespace.Transform.AppDirectories do - alias Forge.Namespace - - def apply_to_all(base_directory, opts) do - app_globs = Enum.join(opts[:apps], "*,") - + def apply_to_all(base_directory) do base_directory - |> Path.join("lib/{#{app_globs}*}") - |> Path.wildcard() - |> Enum.each(fn d -> run(d, opts) end) + |> find_app_directories() + |> Enum.each(&apply_transform(base_directory, &1)) end - def run(app_path, opts) do - namespaced_app_path = Namespace.Path.run(app_path, opts) + def apply_transform(base_directory, app_path) do + namespaced_relative_path = + app_path + |> Path.relative_to(base_directory) + |> Forge.Namespace.Path.apply() + + namespaced_app_path = Path.join(base_directory, namespaced_relative_path) - with true <- app_path != namespaced_app_path, - {:ok, _} <- File.rm_rf(namespaced_app_path) do + with {:ok, _} <- File.rm_rf(namespaced_app_path) do File.rename!(app_path, namespaced_app_path) end + catch + e -> + Mix.Shell.IO.error("Failed to rename app directory") + reraise e, __STACKTRACE__ + end + + defp find_app_directories(base_directory) do + app_names = Mix.Tasks.Namespace.app_names() + app_globs = Enum.join(app_names, "*,") + + [base_directory, "lib", "{" <> app_globs <> "*}"] + |> Path.join() + |> Path.wildcard() end end diff --git a/apps/forge/lib/forge/namespace/transform/apps.ex b/apps/forge/lib/forge/namespace/transform/apps.ex index 482fb4ee..28630a32 100644 --- a/apps/forge/lib/forge/namespace/transform/apps.ex +++ b/apps/forge/lib/forge/namespace/transform/apps.ex @@ -3,25 +3,18 @@ defmodule Forge.Namespace.Transform.Apps do Applies namespacing to all modules defined in .app files """ - alias Forge.Namespace - - def apply_to_all(base_directory, opts) do - app_files_glob = Enum.join(opts[:apps], ",") - + def apply_to_all(base_directory) do base_directory - |> Path.join("**/{#{app_files_glob}}.app") - |> Path.wildcard() + |> find_app_files() |> tap(fn app_files -> Mix.Shell.IO.info("Rewriting #{length(app_files)} app files") end) - |> Enum.each(fn f -> run(f, opts) end) + |> Enum.each(&apply/1) end - def run(file_path, opts) do - namespace_app = opts[:do_apps] - - with {:ok, app_definition} <- Namespace.Erlang.path_to_term(file_path), - {:ok, converted} <- convert(app_definition, namespace_app, opts), + def apply(file_path) do + with {:ok, app_definition} <- Forge.Namespace.Erlang.path_to_term(file_path), + {:ok, converted} <- convert(app_definition), :ok <- File.write(file_path, converted) do app_name = file_path @@ -29,17 +22,15 @@ defmodule Forge.Namespace.Transform.Apps do |> Path.rootname() |> String.to_atom() - if namespace_app do - namespaced_app_name = Namespace.Module.run(app_name, opts) - new_filename = "#{namespaced_app_name}.app" + namespaced_app_name = Forge.Namespace.Module.apply(app_name) + new_filename = "#{namespaced_app_name}.app" - new_file_path = - file_path - |> Path.dirname() - |> Path.join(new_filename) + new_file_path = + file_path + |> Path.dirname() + |> Path.join(new_filename) - File.rename!(file_path, new_file_path) - end + File.rename!(file_path, new_file_path) end catch e -> @@ -47,47 +38,44 @@ defmodule Forge.Namespace.Transform.Apps do reraise e, __STACKTRACE__ end - defp convert(app_definition, namespace_app, opts) do + defp find_app_files(base_directory) do + app_files_glob = Enum.join(Mix.Tasks.Namespace.app_names(), ",") + + [base_directory, "**", "{#{app_files_glob}}.app"] + |> Path.join() + |> Path.wildcard() + end + + defp convert(app_definition) do erlang_terms = app_definition - |> visit(namespace_app, opts) + |> visit() |> Forge.Namespace.Erlang.term_to_string() {:ok, erlang_terms} end - defp visit({:application, app_name, keys}, namespace_app, opts) do - app = - if namespace_app do - Namespace.Module.run(app_name, opts) - else - app_name - end - - {:application, app, Enum.map(keys, fn k -> visit(k, namespace_app, opts) end)} + defp visit({:application, app_name, keys}) do + {:application, Forge.Namespace.Module.apply(app_name), Enum.map(keys, &visit/1)} end - defp visit({:applications, app_list} = original, namespace_app, opts) do - if namespace_app do - {:applications, Enum.map(app_list, fn app -> Namespace.Module.run(app, opts) end)} - else - original - end + defp visit({:applications, app_list}) do + {:applications, Enum.map(app_list, &Forge.Namespace.Module.apply/1)} end - defp visit({:modules, module_list}, _, opts) do - {:modules, Enum.map(module_list, fn app -> Namespace.Module.run(app, opts) end)} + defp visit({:modules, module_list}) do + {:modules, Enum.map(module_list, &Forge.Namespace.Module.apply/1)} end - defp visit({:description, desc}, _, _opts) do + defp visit({:description, desc}) do {:description, desc ++ ~c" namespaced by expert."} end - defp visit({:mod, {module_name, args}}, _, opts) do - {:mod, {Namespace.Module.run(module_name, opts), args}} + defp visit({:mod, {module_name, args}}) do + {:mod, {Forge.Namespace.Module.apply(module_name), args}} end - defp visit(key_value, _, _) do + defp visit(key_value) do key_value end end diff --git a/apps/forge/lib/forge/namespace/transform/beams.ex b/apps/forge/lib/forge/namespace/transform/beams.ex index 9fa6c5cc..ca44c363 100644 --- a/apps/forge/lib/forge/namespace/transform/beams.ex +++ b/apps/forge/lib/forge/namespace/transform/beams.ex @@ -3,10 +3,13 @@ defmodule Forge.Namespace.Transform.Beams do A transformer that finds and replaces any instance of a module in a .beam file """ - def apply_to_all(base_directory, opts) do + alias Forge.Namespace.Abstract + alias Forge.Namespace.Code + + def apply_to_all(base_directory) do Mix.Shell.IO.info("Rewriting .beam files") consolidated_beams = find_consolidated_beams(base_directory) - app_beams = find_app_beams(base_directory, opts[:apps]) + app_beams = find_app_beams(base_directory) Mix.Shell.IO.info(" Found #{length(consolidated_beams)} protocols") Mix.Shell.IO.info(" Found #{length(app_beams)} app beam files") @@ -19,9 +22,7 @@ defmodule Forge.Namespace.Transform.Beams do spawn(fn -> all_beams |> Task.async_stream( - fn beam -> - apply_and_update_progress(beam, me, opts) - end, + &apply_and_update_progress(&1, me), ordered: false, timeout: :infinity ) @@ -31,21 +32,13 @@ defmodule Forge.Namespace.Transform.Beams do block_until_done(0, total_files) end - defp apply_and_update_progress(beam_file, caller, opts) do - run(beam_file, opts) - send(caller, :progress) - end - - def run(path, opts) do - do_apps = opts[:do_apps] + def apply(path) do erlang_path = String.to_charlist(path) - Process.put(:do_apps, do_apps) - with {:ok, forms} <- abstract_code(erlang_path), - rewritten_forms = Forge.Namespace.Abstract.run(forms, opts), + rewritten_forms = Abstract.rewrite(forms), true <- changed?(forms, rewritten_forms), - {:ok, module_name, binary} <- Forge.Namespace.Code.compile(rewritten_forms) do + {:ok, module_name, binary} <- Code.compile(rewritten_forms) do write_module_beam(path, module_name, binary) end end @@ -70,17 +63,22 @@ defmodule Forge.Namespace.Transform.Beams do block_until_done(current, max) end + defp apply_and_update_progress(beam_file, caller) do + apply(beam_file) + send(caller, :progress) + end + defp find_consolidated_beams(base_directory) do - [base_directory, "**", "consolidated", "*.beam"] + [base_directory, "releases", "**", "consolidated", "*.beam"] |> Path.join() |> Path.wildcard() end - defp find_app_beams(base_directory, apps) do - namespaced_apps = Enum.join(apps, ",") - apps_glob = "{#{namespaced_apps}}" + defp find_app_beams(base_directory) do + namespaced_apps = Enum.join(Mix.Tasks.Namespace.app_names(), ",") + apps_glob = "{#{namespaced_apps}}*" - [base_directory, "lib", apps_glob, "ebin/**", "*.beam"] + [base_directory, "lib", apps_glob, "**", "*.beam"] |> Path.join() |> Path.wildcard() end diff --git a/apps/forge/lib/forge/namespace/transform/boots.ex b/apps/forge/lib/forge/namespace/transform/boots.ex index c2358713..c66f5897 100644 --- a/apps/forge/lib/forge/namespace/transform/boots.ex +++ b/apps/forge/lib/forge/namespace/transform/boots.ex @@ -2,16 +2,16 @@ defmodule Forge.Namespace.Transform.Boots do @moduledoc """ A transformer that re-builds .boot files by converting a .script file """ - def apply_to_all(base_directory, opts \\ []) do + def apply_to_all(base_directory) do base_directory |> find_boot_files() |> tap(fn boot_files -> Mix.Shell.IO.info("Rebuilding #{length(boot_files)} boot files") end) - |> Enum.each(&run(&1, opts)) + |> Enum.each(&apply/1) end - def run(file_path, _opts \\ []) do + def apply(file_path) do file_path |> Path.rootname() |> String.to_charlist() diff --git a/apps/forge/lib/forge/namespace/transform/configs.ex b/apps/forge/lib/forge/namespace/transform/configs.ex index f2bae223..f64ee44e 100644 --- a/apps/forge/lib/forge/namespace/transform/configs.ex +++ b/apps/forge/lib/forge/namespace/transform/configs.ex @@ -1,5 +1,5 @@ defmodule Forge.Namespace.Transform.Configs do - def apply_to_all(base_directory, opts) do + def apply_to_all(base_directory) do base_directory |> Path.join("**/runtime.exs") |> Path.wildcard() @@ -7,10 +7,10 @@ defmodule Forge.Namespace.Transform.Configs do |> tap(fn paths -> Mix.Shell.IO.info("Rewriting #{length(paths)} config scripts.") end) - |> Enum.each(&run(&1, opts)) + |> Enum.each(&apply/1) end - def run(path, opts) do + def apply(path) do namespaced = path |> File.read!() @@ -20,14 +20,14 @@ defmodule Forge.Namespace.Transform.Configs do namespaced_alias = alias |> Module.concat() - |> Forge.Namespace.Module.run(opts) + |> Forge.Namespace.Module.apply() |> Module.split() |> Enum.map(&String.to_atom/1) {:__aliases__, meta, namespaced_alias} atom when is_atom(atom) -> - Forge.Namespace.Module.run(atom, opts) + Forge.Namespace.Module.apply(atom) ast -> ast diff --git a/apps/forge/lib/forge/namespace/transform/scripts.ex b/apps/forge/lib/forge/namespace/transform/scripts.ex index 4dddf370..95df751c 100644 --- a/apps/forge/lib/forge/namespace/transform/scripts.ex +++ b/apps/forge/lib/forge/namespace/transform/scripts.ex @@ -3,18 +3,18 @@ defmodule Forge.Namespace.Transform.Scripts do A transform that updates any module in .script and .rel files with namespaced versions """ - def apply_to_all(base_directory, opts) do + def apply_to_all(base_directory) do base_directory |> find_scripts() |> tap(fn script_files -> Mix.Shell.IO.info("Rewriting #{length(script_files)} scripts") end) - |> Enum.each(&run(&1, opts)) + |> Enum.each(&apply/1) end - def run(file_path, opts) do + def apply(file_path) do with {:ok, app_definition} <- Forge.Namespace.Erlang.path_to_term(file_path), - {:ok, converted} <- convert(app_definition, opts) do + {:ok, converted} <- convert(app_definition) do File.write(file_path, converted) end end @@ -28,8 +28,8 @@ defmodule Forge.Namespace.Transform.Scripts do |> Path.wildcard() end - defp convert(app_definition, opts) do - converted = visit(app_definition, opts) + defp convert(app_definition) do + converted = visit(app_definition) erlang_terms = Forge.Namespace.Erlang.term_to_string(converted) script = """ @@ -41,58 +41,57 @@ defmodule Forge.Namespace.Transform.Scripts do end # for .rel files - defp visit({:release, release_vsn, erts_vsn, app_versions}, opts) do + defp visit({:release, release_vsn, erts_vsn, app_versions}) do fixed_apps = Enum.map(app_versions, fn {app_name, version, start_type} -> - {Forge.Namespace.Module.run(app_name, opts), version, start_type} + {Forge.Namespace.Module.apply(app_name), version, start_type} end) {:release, release_vsn, erts_vsn, fixed_apps} end - defp visit({:script, script_vsn, keys}, opts) do - {:script, script_vsn, Enum.map(keys, &visit(&1, opts))} + defp visit({:script, script_vsn, keys}) do + {:script, script_vsn, Enum.map(keys, &visit/1)} end - defp visit({:primLoad, app_list}, opts) do - {:primLoad, Enum.map(app_list, &Forge.Namespace.Module.run(&1, opts))} + defp visit({:primLoad, app_list}) do + {:primLoad, Enum.map(app_list, &Forge.Namespace.Module.apply/1)} end - defp visit({:path, paths}, opts) do - {:path, Enum.map(paths, &Forge.Namespace.Path.run(&1, opts))} + defp visit({:path, paths}) do + {:path, Enum.map(paths, &Forge.Namespace.Path.apply/1)} end - defp visit({:apply, {:application, :load, load_apps}}, opts) do - {:apply, {:application, :load, Enum.map(load_apps, &visit(&1, opts))}} + defp visit({:apply, {:application, :load, load_apps}}) do + {:apply, {:application, :load, Enum.map(load_apps, &visit/1)}} end - defp visit({:apply, {:application, :start_boot, apps_to_start}}, opts) do + defp visit({:apply, {:application, :start_boot, apps_to_start}}) do {:apply, - {:application, :start_boot, Enum.map(apps_to_start, &Forge.Namespace.Module.run(&1, opts))}} + {:application, :start_boot, Enum.map(apps_to_start, &Forge.Namespace.Module.apply/1)}} end - defp visit({:application, app_name, app_keys}, opts) do - {:application, Forge.Namespace.Module.run(app_name, opts), - Enum.map(app_keys, &visit(&1, opts))} + defp visit({:application, app_name, app_keys}) do + {:application, Forge.Namespace.Module.apply(app_name), Enum.map(app_keys, &visit/1)} end - defp visit({:application, app_name}, opts) do - {:application, Forge.Namespace.Module.run(app_name, opts)} + defp visit({:application, app_name}) do + {:application, Forge.Namespace.Module.apply(app_name)} end - defp visit({:mod, {module_name, args}}, opts) do - {:mod, {Forge.Namespace.Module.run(module_name, opts), Enum.map(args, &visit(&1, opts))}} + defp visit({:mod, {module_name, args}}) do + {:mod, {Forge.Namespace.Module.apply(module_name), Enum.map(args, &visit/1)}} end - defp visit({:modules, module_list}, opts) do - {:modules, Enum.map(module_list, &Forge.Namespace.Module.run(&1, opts))} + defp visit({:modules, module_list}) do + {:modules, Enum.map(module_list, &Forge.Namespace.Module.apply/1)} end - defp visit({:applications, app_names}, opts) do - {:applications, Enum.map(app_names, &Forge.Namespace.Module.run(&1, opts))} + defp visit({:applications, app_names}) do + {:applications, Enum.map(app_names, &Forge.Namespace.Module.apply/1)} end - defp visit(key_value, _opts) do + defp visit(key_value) do key_value end end diff --git a/apps/forge/lib/mix/tasks/namespace.ex b/apps/forge/lib/mix/tasks/namespace.ex index e112b038..aeb5fa62 100644 --- a/apps/forge/lib/mix/tasks/namespace.ex +++ b/apps/forge/lib/mix/tasks/namespace.ex @@ -1,31 +1,14 @@ defmodule Mix.Tasks.Namespace do @moduledoc """ - This task will apply namespacing to a set of .beam and .app files in the given directory. + This task is used after a release is assembled, and investigates the engine + app for its dependencies, at which point it applies transformers to various parts of the + app. - Primarily works on a list of application and a list of "module roots". + Transformers take a path, find their relevant files and apply transforms to them. For example, + the Beams transformer will find any instances of modules in .beam files, and will apply namepaces + to them if the module is one of the modules defined in a dependency. - A module root is the first segment of an Elixir module, e.g., "Foo" in "Foo.Bar.Baz". - - The initial list of apps and roots (before additional inclusions and exclusions) are derived from - fetching the projects deps via `Mix.Project.deps_apps/0`. From there, each dependency's modules are - fetched via `:application.get_key(dep_app, :modules)`. - - ## Options - - * `--directory` - The active working directory (required) - * `--[no-]dot-apps` - Whether to namespace application names and .app files at all. Useful to disable if you dont need to start the project like a normal application. Defaults to false. - * `--include-app` - Adds the given application to the list of applications to namespace. - * `--exclude-app` - Removes the given application from the list of applications to namespace. - * `--include-root` - Adds the given module "root" to the list of "roots" to namespace. - * `--exclude-root` - Removes the given module "root" from the list of "roots" to namespace. - - - ## Usage - - ```bash - mix namespace --directory _build/prod --include-app engine --include-root Engine --exclude-app namespace --dot-apps - mix namespace --directory _build/dev --include-app expert --exclude-root Expert --exclude-app burrito --exclude-app namespace --exclude-root Jason --include-root Engine - ``` + This task takes a single argument, which is the full path to the release. """ alias Forge.Ast alias Forge.Namespace.Transform @@ -47,55 +30,21 @@ defmodule Mix.Tasks.Namespace do require Logger - def run(argv) do - {options, _rest} = - OptionParser.parse!(argv, - strict: [ - directory: :string, - dot_apps: :boolean, - include_app: :keep, - include_root: :keep, - exclude_app: :keep, - exclude_root: :keep - ] - ) - - base_directory = Keyword.fetch!(options, :directory) - + def run([base_directory]) do # Ensure we cache the loaded apps at the time of namespacing # Otherwise only the @extra_apps will be cached init() - include_apps = options |> Keyword.get_values(:include_app) |> Enum.map(&String.to_atom/1) - include_roots = options |> Keyword.get_values(:include_root) |> Enum.map(&normalize_root/1) - exclude_apps = options |> Keyword.get_values(:exclude_app) |> Enum.map(&String.to_atom/1) - exclude_roots = options |> Keyword.get_values(:exclude_root) |> Enum.map(&normalize_root/1) - - apps = Enum.uniq(Mix.Project.deps_apps() ++ include_apps) -- exclude_apps - - roots_from_apps = - apps |> root_modules_for_apps() |> Map.values() |> List.flatten() |> Enum.uniq() - - roots = (roots_from_apps ++ include_roots) -- exclude_roots - - opts = [apps: apps, roots: roots, do_apps: options[:dot_apps]] - - Transform.Apps.apply_to_all(base_directory, opts) - Transform.Beams.apply_to_all(base_directory, opts) - Transform.Scripts.apply_to_all(base_directory, opts) + Transform.Apps.apply_to_all(base_directory) + Transform.Beams.apply_to_all(base_directory) + Transform.Scripts.apply_to_all(base_directory) # The boot file transform just turns script files into boot files # so it must come after the script file transform - Transform.Boots.apply_to_all(base_directory, opts) - Transform.Configs.apply_to_all(base_directory, opts) - Transform.AppDirectories.apply_to_all(base_directory, opts) - - if options[:dot_apps] do - Transform.AppDirectories.apply_to_all(base_directory, opts) - end + Transform.Boots.apply_to_all(base_directory) + Transform.Configs.apply_to_all(base_directory) + Transform.AppDirectories.apply_to_all(base_directory) end - def normalize_root(module), do: Module.concat([module]) - def app_names do Map.keys(app_to_root_modules()) end diff --git a/justfile b/justfile index 3264c768..6b5e5fe1 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,3 @@ -mix_env := env('MIX_ENV', 'dev') -namespaced_dir := "_build" / mix_env + "_ns" os := if os() == "macos" { "darwin" } else { os() } arch := if arch() =~ "(arm|aarch64)" { "arm64" } else { if arch() =~ "(x86|x86_64)" { "amd64" } else { "unsupported" } } local_target := if os =~ "(darwin|linux|windows)" { os + "_" + arch } else { "unsupported" } @@ -51,95 +49,49 @@ lint *project="all": just mix {{ project }} credo just mix {{ project }} dialyzer -build project *args: (compile project) - #!/usr/bin/env bash - set -euo pipefail - cd apps/{{ project }} - mix build --directory "{{ namespaced_dir }}" {{ args }} - -[private] build-engine: - #!/usr/bin/env bash - set -euxo pipefail - - cd apps/engine - MIX_ENV=dev mix compile - namespaced_dir=_build/dev_ns/ - rm -rf $namespaced_dir - mkdir -p $namespaced_dir - - cp -a _build/dev/. "$namespaced_dir" - - MIX_ENV=dev mix namespace --directory "$namespaced_dir" --include-app engine --include-root Engine --include-root Future --dot-apps - -namespace-expert directory="_build/prod": - #!/usr/bin/env bash - set -euxo pipefail - - cd apps/expert - mix namespace --directory {{ directory }} --include-app expert --exclude-root Expert --exclude-app burrito --exclude-app req --exclude-app finch --exclude-app nimble_options --exclude-app nimble_pool --exclude-root Jason --include-root Engine --include-app engine - -[private] -build-expert: - #!/usr/bin/env bash - cd apps/expert - MIX_ENV=prod mix compile - just namespace-expert + #!/usr/bin/env bash + set -euxo pipefail -[doc('Start the local development server')] -start *opts="--port 9000": build-engine - #!/usr/bin/env bash - set -euxo pipefail + cd apps/engine + MIX_ENV=dev mix compile + namespaced_dir=_build/dev_ns/ + rm -rf $namespaced_dir + mkdir -p $namespaced_dir - cd apps/expert - MIX_ENV=dev mix compile - namespaced_dir=_build/dev_ns - rm -rf $namespaced_dir - mkdir -p $namespaced_dir + cp -a _build/dev/. "$namespaced_dir" - cp -r _build/dev/ $namespaced_dir + MIX_ENV=dev mix namespace "$namespaced_dir" - just namespace-expert $namespaced_dir - - MIX_BUILD_PATH="$namespaced_dir" EXPERT_ENGINE_PATH="{{ "../engine" / namespaced_dir }}" iex -S mix run \ - --no-compile \ - --no-halt \ - -e "Application.ensure_all_started(:expert)" \ - -- {{ opts }} [doc('Build a release for the local system')] [unix] -release-local: (deps "expert") (compile "engine") build-engine build-expert +release-local: (deps "expert") (compile "engine") build-engine #!/usr/bin/env bash - set -euxo pipefail - cd apps/expert if [ "{{ local_target }}" == "unsupported" ]; then echo "unsupported OS/Arch combination: {{ local_target }}" exit 1 fi - - MIX_ENV={{ env('MIX_ENV', 'prod')}} EXPERT_RELEASE_MODE=burrito BURRITO_TARGET="{{ local_target }}" mix release --no-compile --overwrite + MIX_ENV={{ env('MIX_ENV', 'prod')}} EXPERT_RELEASE_MODE=burrito BURRITO_TARGET="{{ local_target }}" mix release --overwrite [windows] -release-local: (deps "expert") (compile "engine") build-engine build-expert - cd apps/expert - +release-local: (deps "expert") (compile "engine") build-engine # idk actually how to set env vars like this on windows, might crash EXPERT_RELEASE_MODE=burrito BURRITO_TARGET="windows_amd64" MIX_ENV={{ env('MIX_ENV', 'prod')}} mix release --no-compile [doc('Build releases for all target platforms')] -release-all: (deps "expert") (compile "engine") build-engine build-expert +release-all: (deps "expert") (compile "engine") build-engine #!/usr/bin/env bash cd apps/expert EXPERT_RELEASE_MODE=burrito MIX_ENV={{ env('MIX_ENV', 'prod')}} mix release --no-compile [doc('Build a plain release without burrito')] -release-plain: (compile "engine") build-engine build-expert +release-plain: (compile "engine") #!/usr/bin/env bash cd apps/expert - MIX_ENV={{ env('MIX_ENV', 'prod')}}mix release plain --no-compile --overwrite + MIX_ENV={{ env('MIX_ENV', 'prod')}} mix release plain --overwrite [doc('Compiles .github/matrix.json')] compile-ci-matrix: From 8d0186ac88247c797c8a9f321e69704d23e608c3 Mon Sep 17 00:00:00 2001 From: doorgan Date: Thu, 21 Aug 2025 20:26:34 -0300 Subject: [PATCH 4/4] chore: remove just start docs --- justfile | 2 ++ pages/development.md | 26 -------------------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/justfile b/justfile index 6b5e5fe1..43c10fc4 100644 --- a/justfile +++ b/justfile @@ -70,6 +70,8 @@ release-local: (deps "expert") (compile "engine") build-engine #!/usr/bin/env bash cd apps/expert + set -euxo pipefail + if [ "{{ local_target }}" == "unsupported" ]; then echo "unsupported OS/Arch combination: {{ local_target }}" exit 1 diff --git a/pages/development.md b/pages/development.md index 054ddc8e..5635ba22 100644 --- a/pages/development.md +++ b/pages/development.md @@ -19,32 +19,6 @@ tail -f .expert/*.log Note: These log files roll over when they reach 1 megabyte, so after a time, it will be necessary to re-run the above command. -## Development server - -To start a development server with an interactive shell, you can run the -following command: - -```sh -just start -``` - -This will launch an IEx session, and it will start Expert listening -in the TCP port `9000`. - -You will need to configure your editor to connect to the Expert LSP -via TCP at that port. After that, opening you project in your editor -will connect it to the running dev server, and it will terminate it -when you close the editor. - -In this dev server you can run `:observer.start()`, and call any -function from Expert to inspect the state of the server, or run -arbitrary code. - -Since Expert needs namespacing to work, modules from the `forge` -application will be namespaced as `XPForge`; the same applies for -any module that is shared between the `expert` and `engine` -applications. - ## Debugging Expert supports a debug shell, which will connect a remote shell to a