Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Beam splitter #136

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
# This file contains the recommended versions for developing and running
# elixir-ls. However, elixir-ls supports a minumum version of Elixir 1.7.0 with
# OTP 20 and should work with any later releases.
# Using asdf to manage your elixir and OTP versions is not required, you can
# use the system-installed version instead.
#
# The versions selected here are the versions that are used to build a binary
# release for distribution
elixir 1.7.4-otp-20
erlang 20.3.8.23
# This is the version that the Elixir-LS distribution will run on.
elixir 1.10.1-otp-22
erlang 22.1
4 changes: 4 additions & 0 deletions apps/elixir_ls_main/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
24 changes: 24 additions & 0 deletions apps/elixir_ls_main/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
elixir_ls_main-*.tar

5 changes: 5 additions & 0 deletions apps/elixir_ls_main/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ElixirLsMain

Main Elixir-LS application. Pretty much its sole role is to decide at startup whether
the language server or debugger are invoked. This way, we can release this as a simple
single app.
12 changes: 12 additions & 0 deletions apps/elixir_ls_main/lib/elixir_ls_main/application.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule ElixirLsMain.Application do
use Application

def start(_type, _args) do
task_mod = if System.get_env("ELS_STARTUP_TYPE") == "debugger" do
ElixirLS.Debugger.CLI
else
ElixirLS.LanguageServer.CLI
end
Task.start_link(&task_mod.main/0)
end
end
34 changes: 34 additions & 0 deletions apps/elixir_ls_main/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule ElixirLsMain.MixProject do
use Mix.Project

def project do
[
app: :elixir_ls_main,
version: "0.1.0",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.10",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
mod: {ElixirLsMain.Application, []},
extra_applications: [:logger],
bundled_applications: [:debugger, :language_server]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:language_server, in_umbrella: true},
{:debugger, in_umbrella: true}
]
end
end
10 changes: 10 additions & 0 deletions apps/elixir_ls_main/priv/debugger.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
REM TODO test me on Win1rand () {
od -t xS -N 2 -A n /dev/urandom | tr -d " \n"
}
0
@echo off & setlocal enabledelayedexpansion

set ELS_STARTUP_TYPE=debugger
set RELEASE_NODE="elixir-ls-!RANDOM!"

@bin/elixir_ls.bat start
31 changes: 31 additions & 0 deletions apps/elixir_ls_main/priv/debugger.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Launches the language server. Very simple wrapper around the generated
# startup script.

# For now, we assume that this script lives in the root of the unpacked
# distribution (so that the distribution wrapper script lives in bin/).
# TODO this is a manual post-install step so we should thinkg about this.

# TODO lots of duplication with language_server.sh here

readlink_f () {
cd "$(dirname "$1")" > /dev/null
filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
rand () {
od -t xS -N 2 -A n /dev/urandom | tr -d " \n"
}

SCRIPT=$(readlink_f $0)
SCRIPTPATH=`dirname $SCRIPT`/bin

export RELEASE_NODE=elixir-ls-$(rand)

export ELS_STARTUP_TYPE=debugger

$SCRIPTPATH/elixir_ls start
6 changes: 6 additions & 0 deletions apps/elixir_ls_main/priv/language_server.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
REM TODO test me on Win10
@echo off & setlocal enabledelayedexpansion

set RELEASE_NODE="elixir-ls-!RANDOM!"

@bin/elixir_ls.bat start
27 changes: 27 additions & 0 deletions apps/elixir_ls_main/priv/language_server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Launches the language server. Very simple wrapper around the generated
# startup script.

# For now, we assume that this script lives in the root of the unpacked
# distribution (so that the distribution wrapper script lives in bin/).
# TODO this is a manual post-install step so we should thinkg about this.

readlink_f () {
cd "$(dirname "$1")" > /dev/null
filename="$(basename "$1")"
if [ -h "$filename" ]; then
readlink_f "$(readlink "$filename")"
else
echo "`pwd -P`/$filename"
fi
}
rand () {
od -t xS -N 2 -A n /dev/urandom | tr -d " \n"
}

SCRIPT=$(readlink_f $0)
SCRIPTPATH=`dirname $SCRIPT`/bin

export RELEASE_NODE=elixir-ls-$(rand)

$SCRIPTPATH/elixir_ls start
1 change: 1 addition & 0 deletions apps/elixir_ls_main/test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ExUnit.start()
11 changes: 11 additions & 0 deletions apps/elixir_ls_utils/lib/application.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule ElixirLS.Utils.Application do
use Application

def start(_type, _args) do
children = [
ElixirLS.Utils.EelsRunner
]

Supervisor.start_link(children, strategy: :one_for_one)
end
end
57 changes: 57 additions & 0 deletions apps/elixir_ls_utils/lib/eels_runner.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
defmodule ElixirLS.Utils.EelsRunner do
@moduledoc """
Eels wrapper. This starts an inferior VM that (hopefully) runs the
current project's code and forwards commands to it.
"""
use GenServer
require Logger

def start_link([]) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end

# This might be a bottleneck. A simple optimization would be to stash
# the node in ETS and leave the genserver just operate as an ETS owner
# process.
def exec(module, function, arguments) do
GenServer.call(__MODULE__, {:exec, module, function, arguments})
end

def init([]) do
{node, cookie} = generate_state()
start_eels_vm(node, cookie)
exec_ping(node)
Logger.info("Eels runner successfully started")
{:ok, node}
end

def handle_call(call = {:exec, _module, _function, _arguments}, _from, node) do
result = GenServer.call({:eels_server, node}, call)
{:reply, result, node}
end

defp generate_state() do
max = 2_000_000_000_000 # lots of birthdays need to happen...
cookie = :"eels-cookie-#{:rand.uniform(max)}"
host = node |> Atom.to_string() |> String.split("@") |> List.last()
node = :"eels-#{:rand.uniform(max)}@#{host}"
{node, cookie}
end

defp start_eels_vm(node, cookie) do
System.put_env("EELS_COOKIE", Atom.to_string(Node.get_cookie()))
System.put_env("EELS_NODE", Atom.to_string(node))
System.put_env("EELS_VERSION", List.to_string(Keyword.get(Application.spec(:eels), :vsn)))
script = Application.app_dir(:elixir_ls_utils) <> "/priv/start_eels"
# TODO Should we keep the port around?
Port.open({:spawn, script <> " start"}, [])
Process.sleep(2_000) # TODO clean up
Logger.info("Connected: #{inspect Node.connect(node)}")
end

defp exec_ping(node) do
result = GenServer.call({:eels_server, node}, {:exec, Kernel, :node, []})
Logger.info("Result = #{inspect result}")
end

end
11 changes: 8 additions & 3 deletions apps/elixir_ls_utils/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ defmodule ElixirLS.Utils.Mixfile do
end

def application do
# We must NOT start ANY applications as this is taken care in code.
[applications: []]
[mod: {ElixirLS.Utils.Application, []},
# We include Eels basically just for its source code, so prevent it from starting.
included_applications: [:eels]]
end

defp deps do
[
{:jason, "~> 1.1"},
{:mix_task_archive_deps, github: "JakeBecker/mix_task_archive_deps"}
{:mix_task_archive_deps, github: "JakeBecker/mix_task_archive_deps"},
# We include eels as a dependency so that we get it bundled in the release
# All we need is its source code but as long as we don't start it, it
# won't hurt.
{:eels, path: "../../eels"}
]
end
end
4 changes: 0 additions & 4 deletions apps/elixir_ls_utils/priv/debugger.bat

This file was deleted.

20 changes: 0 additions & 20 deletions apps/elixir_ls_utils/priv/debugger.sh

This file was deleted.

4 changes: 0 additions & 4 deletions apps/elixir_ls_utils/priv/language_server.bat

This file was deleted.

20 changes: 0 additions & 20 deletions apps/elixir_ls_utils/priv/language_server.sh

This file was deleted.

15 changes: 15 additions & 0 deletions apps/elixir_ls_utils/priv/start_eels
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Launches a new Elixir Embedded Language Server, which
# runs the Elixir/OTP version-specific bits of Elixir-LS

# See ElixirLS.Utils.EelsRunner for details on the environment
# setup. RELEASE_ROOT is inherited from the release wrapper script
# that started Elixir-LS; we can use it to find our way around.

exec >/tmp/start_eels.out 2>&1

[ -f "$HOME/.asdf/asdf.sh" ] && . "$HOME/.asdf/asdf.sh"

# Run elixir, asdf-vm makes it the project specific version.
# TODO Windows version
elixir --no-halt $RELEASE_ROOT/lib/eels-${EELS_VERSION}/priv/eels_main.exs
4 changes: 4 additions & 0 deletions eels/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
24 changes: 24 additions & 0 deletions eels/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
eels-*.tar

Loading