From 6bd1264d5206dd72a9e2c2dbe65552644ae905dd Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Tue, 24 Oct 2017 13:08:19 -0700 Subject: [PATCH 1/7] Build release as archives instead of escript --- ...cli.ex => mix.tasks.elixir_ls.debugger.ex} | 9 +---- apps/elixir_ls_utils/lib/mix.tasks.release.ex | 34 +++++++++++++++++ apps/elixir_ls_utils/mix.exs | 3 +- ...=> mix.tasks.elixir_ls.language_server.ex} | 10 +---- bin/debugger.bat | 9 ----- bin/debugger.sh | 15 -------- bin/exscript.bat | 38 ------------------- bin/exscript.sh | 27 ------------- mix.lock | 1 + 9 files changed, 41 insertions(+), 105 deletions(-) rename apps/debugger/lib/{debugger/cli.ex => mix.tasks.elixir_ls.debugger.ex} (70%) create mode 100644 apps/elixir_ls_utils/lib/mix.tasks.release.ex rename apps/language_server/lib/{language_server/cli.ex => mix.tasks.elixir_ls.language_server.ex} (73%) delete mode 100755 bin/debugger.bat delete mode 100755 bin/debugger.sh delete mode 100755 bin/exscript.bat delete mode 100755 bin/exscript.sh diff --git a/apps/debugger/lib/debugger/cli.ex b/apps/debugger/lib/mix.tasks.elixir_ls.debugger.ex similarity index 70% rename from apps/debugger/lib/debugger/cli.ex rename to apps/debugger/lib/mix.tasks.elixir_ls.debugger.ex index a5426f0a..cdf2a358 100644 --- a/apps/debugger/lib/debugger/cli.ex +++ b/apps/debugger/lib/mix.tasks.elixir_ls.debugger.ex @@ -1,15 +1,10 @@ -defmodule ElixirLS.Debugger.CLI do +defmodule Mix.Tasks.ElixirLs.Debugger do alias ElixirLS.Utils.WireProtocol alias ElixirLS.Debugger.{Output, Server} - def main(_args) do + def run(_args) do WireProtocol.intercept_output(&Output.print/1, &Output.print_err/1) - Application.ensure_all_started(:debugger, :permanent) - - Mix.Local.append_archives - Mix.Local.append_paths - WireProtocol.stream_packets(&Server.receive_packet/1) end end diff --git a/apps/elixir_ls_utils/lib/mix.tasks.release.ex b/apps/elixir_ls_utils/lib/mix.tasks.release.ex new file mode 100644 index 00000000..b81e3bdb --- /dev/null +++ b/apps/elixir_ls_utils/lib/mix.tasks.release.ex @@ -0,0 +1,34 @@ +defmodule Mix.Tasks.Release do + @switches [destination: :string] + @aliases [o: :destination] + + def run(args) do + version_warning() + {opts, _} = OptionParser.parse!(args, aliases: @aliases, switches: @switches) + destination = opts[:destination] || "release" + + Path.join(destination, "*.ez") + |> Path.wildcard() + |> Enum.each(&File.rm(&1)) + + Mix.Task.run("archive.build.deps", [ + "--skip", + "mix_task_archive_deps", + "--destination", + destination + ]) + end + + defp version_warning do + {otp_version, _} = Integer.parse(to_string(:erlang.system_info(:otp_release))) + + if otp_version > 19 do + IO.warn( + "Building with Erlang/OTP #{otp_version}. Make sure to build with OTP 19 if " <> + "publishing the compiled packages because modules built with higher versions are not " <> + "backwards-compatible.", + [] + ) + end + end +end diff --git a/apps/elixir_ls_utils/mix.exs b/apps/elixir_ls_utils/mix.exs index 79b05a42..d213e8aa 100644 --- a/apps/elixir_ls_utils/mix.exs +++ b/apps/elixir_ls_utils/mix.exs @@ -39,6 +39,7 @@ defmodule ElixirLS.Utils.Mixfile do # # Type "mix help deps" for more examples and options defp deps do - [{:poison, "~> 3.0"}] + [{:poison, "~> 3.0"}, + {:mix_task_archive_deps, "~> 0.4.0"}] end end diff --git a/apps/language_server/lib/language_server/cli.ex b/apps/language_server/lib/mix.tasks.elixir_ls.language_server.ex similarity index 73% rename from apps/language_server/lib/language_server/cli.ex rename to apps/language_server/lib/mix.tasks.elixir_ls.language_server.ex index 5acf449c..ba9b5ecb 100644 --- a/apps/language_server/lib/language_server/cli.ex +++ b/apps/language_server/lib/mix.tasks.elixir_ls.language_server.ex @@ -1,17 +1,11 @@ -defmodule ElixirLS.LanguageServer.CLI do +defmodule Mix.Tasks.ElixirLs.LanguageServer do alias ElixirLS.Utils.WireProtocol alias ElixirLS.LanguageServer.JsonRpc - def main(_args) do + def run(_args) do WireProtocol.intercept_output(&JsonRpc.print/1, &JsonRpc.print_err/1) - Application.ensure_all_started(:language_server, :permanent) - - Mix.Local.append_archives - Mix.Local.append_paths - Mix.shell(ElixirLS.LanguageServer.MixShell) - WireProtocol.stream_packets(&JsonRpc.receive_packet/1) end end diff --git a/bin/debugger.bat b/bin/debugger.bat deleted file mode 100755 index 0e79bf03..00000000 --- a/bin/debugger.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off & setlocal enabledelayedexpansion -rem // VS Code starts the debugger with the cwd set to -rem // C:\\Program Files (x86)\Microsoft VS Code -rem // so we have to use the batch script's directory to find the escript file. -rem // (TL;DR: debugger.bat must be in the same folder as exscript.bat and debugger) - -set SCRIPT_DIR=%~dp0 -shift -%SCRIPT_DIR%exscript.bat %SCRIPT_DIR%debugger %* \ No newline at end of file diff --git a/bin/debugger.sh b/bin/debugger.sh deleted file mode 100755 index 4dd6ae9c..00000000 --- a/bin/debugger.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env sh - -readlink_f () { - cd "$(dirname "$1")" > /dev/null - filename="$(basename "$1")" - if [ -h "$filename" ]; then - readlink_f "$(readlink "$filename")" - else - echo "`pwd -P`/$filename" - fi -} - -SCRIPT=$(readlink_f $0) -SCRIPTPATH=`dirname $SCRIPT` -"$SCRIPTPATH/exscript.sh" "$SCRIPTPATH/debugger" $@ \ No newline at end of file diff --git a/bin/exscript.bat b/bin/exscript.bat deleted file mode 100755 index f14b50db..00000000 --- a/bin/exscript.bat +++ /dev/null @@ -1,38 +0,0 @@ -@echo off & setlocal enabledelayedexpansion - -rem // Runs an Elixir escript that was compiled without embedding Elixir. -rem // It looks for the system Elixir installation and includes it in the VM's code path. - -rem // Get path to elixir.bat -SET COMMAND=where elixir -FOR /F "delims=" %%A IN ('%COMMAND%') DO ( - SET ELIXIR_PATH=%%A - GOTO :found_elixir -) -:found_elixir - -rem // Get folder containing elixir.bat -For %%A in ("%ELIXIR_PATH%") do ( - set ELIXIR_DIR=%%~dpA -) - -rem // Get relative path of lib folder -set REL_PATH="%ELIXIR_DIR%..\lib" - -rem // Save current directory and change to target directory -pushd %REL_PATH% - -rem // Save value of CD variable (current directory) -set ABS_PATH=%CD% - -rem // Restore original directory -popd - -rem // Set ERL_LIBS to include lib folder -set ERL_LIBS=%ERL_LIBS%;%ABS_PATH% - -rem // Remove first arg (this script) and shift over other args -SHIFT - -rem // Run escript with the args that were passed to this script -escript %* \ No newline at end of file diff --git a/bin/exscript.sh b/bin/exscript.sh deleted file mode 100755 index 7261eed0..00000000 --- a/bin/exscript.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env sh -# Runs an Elixir escript that was compiled without embedding Elixir. -# It looks for the system Elixir installation and includes it in the VM's code path. - -readlink_f () { - cd "$(dirname "$1")" > /dev/null - filename="$(basename "$1")" - if [ -h "$filename" ]; then - readlink_f "$(readlink "$filename")" - else - echo "`pwd -P`/$filename" - fi -} - -REL_DIR="$(dirname $(readlink_f $(which elixir)))/../lib" - -# If directory does not exist, it could be because the `elixir` in the PATH runs a script which runs -# elixir. (Installations via the "asdf" tool work this way.) We run Elixir and ask it its path as a -# fallback. This is slower. -if [ -d "$REL_DIR/elixir" ]; then - ELIXIR_LIB=$(cd "$REL_DIR"; pwd) -else - ELIXIR_LIB=`elixir -e "IO.puts Path.expand(Path.join(Application.app_dir(:elixir), '..'))"` -fi - -export ERL_LIBS="$ERL_LIBS:$ELIXIR_LIB" -/usr/bin/env escript $@ \ No newline at end of file diff --git a/mix.lock b/mix.lock index 23957a46..a8460749 100644 --- a/mix.lock +++ b/mix.lock @@ -16,6 +16,7 @@ "logger_file_backend": {:hex, :logger_file_backend, "0.0.9", "5c2f7d4a28431e695cdf94d191523dbafe609321a67bb654254897f546c393db", [:mix], []}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, + "mix_task_archive_deps": {:hex, :mix_task_archive_deps, "0.4.0", "95cf15d1b04e10c6319e16a82b88109db2e8164f63e733c1742dfaf8ed9cace5", [], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, } From 604b93c3a598144408c5be2290eb264bba65da9e Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Tue, 24 Oct 2017 13:29:07 -0700 Subject: [PATCH 2/7] Configure logger in language server mix task --- .../lib/mix.tasks.elixir_ls.language_server.ex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/language_server/lib/mix.tasks.elixir_ls.language_server.ex b/apps/language_server/lib/mix.tasks.elixir_ls.language_server.ex index ba9b5ecb..f75d1959 100644 --- a/apps/language_server/lib/mix.tasks.elixir_ls.language_server.ex +++ b/apps/language_server/lib/mix.tasks.elixir_ls.language_server.ex @@ -4,8 +4,24 @@ defmodule Mix.Tasks.ElixirLs.LanguageServer do def run(_args) do WireProtocol.intercept_output(&JsonRpc.print/1, &JsonRpc.print_err/1) + configure_logger() Application.ensure_all_started(:language_server, :permanent) Mix.shell(ElixirLS.LanguageServer.MixShell) WireProtocol.stream_packets(&JsonRpc.receive_packet/1) end + + defp configure_logger do + use Mix.Config + + Mix.Config.persist(logger: [ + handle_otp_reports: true, + handle_sasl_reports: true, + level: :warn, + backends: [{ElixirLS.LanguageServer.LoggerBackend, :lsp_logger_backend}] + ]) + + logger = Process.whereis(Logger) + if logger, do: Logger.App.stop() + Logger.App.start() + end end From 24455e0314fbfd4ad508b7329dfbb7d3d9d92e6d Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Wed, 25 Oct 2017 13:21:16 -0700 Subject: [PATCH 3/7] Use JsonRpc.log_message instead of Logger.info so we can set the log level to :warn to filter SASL reports but not our own logs --- apps/language_server/lib/language_server/build.ex | 2 +- apps/language_server/lib/language_server/dialyzer.ex | 6 +++--- .../lib/language_server/dialyzer/manifest.ex | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/language_server/lib/language_server/build.ex b/apps/language_server/lib/language_server/build.ex index 8f1d9ccf..8b5ef00e 100644 --- a/apps/language_server/lib/language_server/build.ex +++ b/apps/language_server/lib/language_server/build.ex @@ -17,7 +17,7 @@ defmodule ElixirLS.LanguageServer.Build do Server.build_finished(parent, {:error, mixfile_diagnostics}) end end - Logger.info("Compile took #{div(us, 1000)} milliseconds") + JsonRpc.log_message(:info, "Compile took #{div(us, 1000)} milliseconds") end, [:monitor]) end end diff --git a/apps/language_server/lib/language_server/dialyzer.ex b/apps/language_server/lib/language_server/dialyzer.ex index d5caec6e..8db1fd63 100644 --- a/apps/language_server/lib/language_server/dialyzer.ex +++ b/apps/language_server/lib/language_server/dialyzer.ex @@ -106,7 +106,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer do def handle_call({:analyze, warn_opts}, _from, state) do state = if Mix.Project.get() do - Logger.info("[ElixirLS Dialyzer] Checking for stale beam files") + JsonRpc.log_message(:info, "[ElixirLS Dialyzer] Checking for stale beam files") new_timestamp = adjusted_timestamp() {removed_files, file_changes} = @@ -281,7 +281,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer do end # Analyze! - Logger.info( + JsonRpc.log_message(:info, "[ElixirLS Dialyzer] Analyzing #{Enum.count(modules_to_analyze)} modules: " <> "#{inspect(modules_to_analyze)}" ) @@ -299,7 +299,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer do {active_plt, mod_deps, md5, warnings, timestamp} end) - Logger.info("[ElixirLS Dialyzer] Analysis finished in #{div(us, 1000)} milliseconds") + JsonRpc.log_message(:info, "[ElixirLS Dialyzer] Analysis finished in #{div(us, 1000)} milliseconds") analysis_finished(parent, :ok, active_plt, mod_deps, md5, warnings, timestamp) end diff --git a/apps/language_server/lib/language_server/dialyzer/manifest.ex b/apps/language_server/lib/language_server/dialyzer/manifest.ex index 97060137..a556b1d7 100644 --- a/apps/language_server/lib/language_server/dialyzer/manifest.ex +++ b/apps/language_server/lib/language_server/dialyzer/manifest.ex @@ -53,13 +53,13 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do fn -> # Because the manifest file can be several megabytes, we do a write-then-rename # to reduce the likelihood of corrupting the manifest - Logger.info("[ElixirLS Dialyzer] Writing manifest...") + JsonRpc.log_message(:info, "[ElixirLS Dialyzer] Writing manifest...") File.mkdir_p!(Path.dirname(manifest_path)) tmp_manifest_path = manifest_path <> ".new" File.write!(tmp_manifest_path, :erlang.term_to_binary(manifest_data, compressed: 9)) File.rename(tmp_manifest_path, manifest_path) File.touch!(manifest_path, timestamp) - Logger.info("[ElixirLS Dialyzer] Done writing manifest.") + JsonRpc.log_message(:info, "[ElixirLS Dialyzer] Done writing manifest.") end, [] ) From 781cdb67290890593b5845923b3c9bd4f4be9cde Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Wed, 25 Oct 2017 14:56:18 -0700 Subject: [PATCH 4/7] Reload logger config after loading user's project config --- apps/language_server/lib/language_server/build.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/language_server/lib/language_server/build.ex b/apps/language_server/lib/language_server/build.ex index 8b5ef00e..5accaedf 100644 --- a/apps/language_server/lib/language_server/build.ex +++ b/apps/language_server/lib/language_server/build.ex @@ -96,7 +96,11 @@ defmodule ElixirLS.LanguageServer.Build do end Mix.Task.clear() + + # The project may override our logger config, so we reset it after loading their config + logger_config = Application.get_all_env(:logger) Mix.Task.run("loadconfig") + Mix.Config.persist(logger: logger_config) # If using Elixir 1.6 or higher, we can get diagnostics if Mixfile fails to load if Version.match?(System.version(), ">= 1.6.0-dev") do From c5daaa818a391488393544f308403a49d5e966ad Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Wed, 25 Oct 2017 15:17:23 -0700 Subject: [PATCH 5/7] Update test to expect output via JsonRpc.log_message instead of Logger --- apps/language_server/test/dialyzer_test.exs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/language_server/test/dialyzer_test.exs b/apps/language_server/test/dialyzer_test.exs index b7cfc6ad..a5f93eff 100644 --- a/apps/language_server/test/dialyzer_test.exs +++ b/apps/language_server/test/dialyzer_test.exs @@ -66,10 +66,12 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do end )) - assert capture_log(fn -> - Server.receive_packet(server, did_save(SourceFile.path_to_uri("lib/b.ex"))) - assert_receive publish_diagnostics_notif(^file_a, []), 20000 - end) =~ "[ElixirLS Dialyzer] Analyzing 2 modules: [A, B]" + Server.receive_packet(server, did_save(SourceFile.path_to_uri("lib/b.ex"))) + assert_receive publish_diagnostics_notif(^file_a, []), 20000 + + assert_receive notification("window/logMessage", %{ + "message" => "[ElixirLS Dialyzer] Analyzing 2 modules: [A, B]" + }) end) # Stop while we're still capturing logs to avoid log leakage From f3ca5bf2d423c87dcfbd8e4bc7007698ad8d0390 Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Tue, 24 Oct 2017 21:19:20 -0700 Subject: [PATCH 6/7] Remove escript build stuff --- apps/debugger/mix.exs | 17 +---------------- apps/language_server/mix.exs | 17 +---------------- release.sh | 16 ---------------- 3 files changed, 2 insertions(+), 48 deletions(-) delete mode 100755 release.sh diff --git a/apps/debugger/mix.exs b/apps/debugger/mix.exs index 4b53a072..4ab60d83 100644 --- a/apps/debugger/mix.exs +++ b/apps/debugger/mix.exs @@ -13,8 +13,7 @@ defmodule ElixirLS.Debugger.Mixfile do start_permanent: true, build_per_environment: false, consolidate_protocols: false, - deps: deps(), - escript: escript()] + deps: deps()] end # Configuration for the OTP application @@ -42,18 +41,4 @@ defmodule ElixirLS.Debugger.Mixfile do [{:elixir_sense, github: "JakeBecker/elixir_sense"}, {:elixir_ls_utils, in_umbrella: true}] end - - defp escript do - [main_module: ElixirLS.Debugger.CLI, - app: nil, - embed_elixir: false, - path: "../../release/debugger", - strip_beam: false, - comment: escript_comment()] - end - - defp escript_comment do - "Requires Elixir but does not embed it. Run via the included exscript.sh (Unix) or \ - exscript.bat (Windows, TODO) scripts to set ERL_LIBS." - end end diff --git a/apps/language_server/mix.exs b/apps/language_server/mix.exs index 7b1658b5..61896f3b 100644 --- a/apps/language_server/mix.exs +++ b/apps/language_server/mix.exs @@ -13,8 +13,7 @@ defmodule ElixirLS.LanguageServer.Mixfile do start_permanent: true, build_per_environment: false, consolidate_protocols: false, - deps: deps(), - escript: escript()] + deps: deps()] end # Configuration for the OTP application @@ -39,18 +38,4 @@ defmodule ElixirLS.LanguageServer.Mixfile do {:elixir_sense, github: "JakeBecker/elixir_sense"}, {:forms, "~> 0.0.1"}] end - - defp escript do - [main_module: ElixirLS.LanguageServer.CLI, - app: nil, - embed_elixir: false, - path: "../../release/language_server", - strip_beam: false, - comment: escript_comment()] - end - - defp escript_comment do - "Requires Elixir but does not embed it. Run via the included exscript.sh (Unix) or \ - exscript.bat (Windows) scripts to set ERL_LIBS." - end end diff --git a/release.sh b/release.sh deleted file mode 100755 index 1000e11d..00000000 --- a/release.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env sh -SCRIPT=`realpath $0` -SCRIPTPATH=`dirname $SCRIPT` - -cd "$SCRIPTPATH" -mix deps.get -mix deps.clean --unused - -rm -rf "$SCRIPTPATH"/release -mkdir release -cp bin/* release/ - -cd "$SCRIPTPATH"/apps/language_server -mix escript.build -cd "$SCRIPTPATH"/apps/debugger -mix escript.build From 17d3a097206edaab6fec50636d8089d250f105ef Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Wed, 25 Oct 2017 13:14:08 -0700 Subject: [PATCH 7/7] Update docs --- CONTRIBUTING.md | 6 ------ README.md | 7 +++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6688bb35..6ee77257 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1 @@ While you can develop ElixirLS on its own, it's easiest to test out changes if you clone the [vscode-elixir-ls](https://github.com/JakeBecker/vscode-elixir-ls) repository instead. It includes ElixirLS as a Git submodule, so you can make your changes in the submodule directory and launch the extension from the parent directory via the included "Launch Extension" configuration in `launch.json`. See the README on that repo for more details. - -### Building and running - -To build for release, run the `release.sh` script. It compiles these two apps to escripts in the `release` folder. If doing a public release, compile it with Erlang OTP 19, since OTP 20 builds are not backwards-compatible with earlier versions of Erlang. - -Elixir escripts typically embed Elixir, which is not what we want because users will want to run it against their own system Elixir installation. In order to support this, there are scripts in the `bin` folder that look for the system-wide Elixir installation and set the `ERL_LIBS` environment variable prior to running the escripts. These scripts are copied to the `release` folder by `release.sh`. diff --git a/README.md b/README.md index 38625f9a..65a08e61 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,13 @@ You can control which warnings are shown using the `elixirLS.dialyzerWarnOpts` s ElixirLS's Dialyzer integration uses internal, undocumented Dialyzer APIs, and so it won't be robust against changes to these APIs in future Erlang versions. + +## Building and running + +Run `mix release -o ` (which is defined in the `elixir_ls_utils` umbrella child) to build the language server and debugger as a set of `.ez` archives. Then you can set `ERL_LIBS= mix elixir_ls.language_server` or `ERL_LIBS= mix elixir_ls.debugger` to launch. (On Windows, the command would be `SET ERL_LIBS=; mix elixir_ls.language_server`) + +If you're packaging these archives in an IDE plugin, make sure to build using Erlang/OTP 19, not OTP 20, because OTP 20 beam files are not backwards-compatible with earlier Erlang versions. + ## Acknowledgements and related projects ElixirLS isn't the first frontend-independent server for Elixir language support. The original was [Alchemist Server](https://github.com/tonini/alchemist-server/), which powers the [Alchemist](https://github.com/tonini/alchemist.el) plugin for Emacs. Another project, [Elixir Sense](https://github.com/msaraiva/elixir_sense), builds upon Alchemist and powers the [Elixir plugin for Atom](https://github.com/msaraiva/atom-elixir) as well as another VS Code plugin, [VSCode Elixir](https://github.com/fr1zle/vscode-elixir). ElixirLS uses Elixir Sense for several code insight features. Credit for those projects goes to their respective authors.