diff --git a/assets/css/components.css b/assets/css/components.css index 10f908ecf..ab20207bb 100644 --- a/assets/css/components.css +++ b/assets/css/components.css @@ -233,6 +233,16 @@ @apply bottom-0 left-0 transform translate-y-full -mb-1; } + .menu__content--top-left.menu__content--distant, + .menu__content--top-right.menu__content--distant { + @apply -mt-2; + } + + .menu__content--bottom-left.menu__content--distant, + .menu__content--bottom-right.menu__content--distant { + @apply -mb-2; + } + .menu:not(.menu--open) > .menu__overlay, .menu:not(.menu--open) > .menu__content { @apply hidden; diff --git a/assets/js/hooks/session.js b/assets/js/hooks/session.js index e4e627f32..5b3fd60a0 100644 --- a/assets/js/hooks/session.js +++ b/assets/js/hooks/session.js @@ -526,7 +526,10 @@ const Session = { ); if (evalButton) { const cellId = evalButton.getAttribute("data-cell-id"); - this.queueCellEvaluation(cellId); + const disableDependenciesCache = evalButton.hasAttribute( + "data-disable-dependencies-cache" + ); + this.queueCellEvaluation(cellId, disableDependenciesCache); } const hash = window.location.hash; @@ -740,9 +743,12 @@ const Session = { } }, - queueCellEvaluation(cellId) { + queueCellEvaluation(cellId, disableDependenciesCache) { this.dispatchQueueEvaluation(() => { - this.pushEvent("queue_cell_evaluation", { cell_id: cellId }); + this.pushEvent("queue_cell_evaluation", { + cell_id: cellId, + disable_dependencies_cache: disableDependenciesCache, + }); }); }, diff --git a/lib/livebook/runtime.ex b/lib/livebook/runtime.ex index d2b0495c6..e14cdfba9 100644 --- a/lib/livebook/runtime.ex +++ b/lib/livebook/runtime.ex @@ -504,6 +504,13 @@ defprotocol Livebook.Runtime do @spec search_packages(t(), pid(), String.t()) :: reference() def search_packages(runtime, send_to, search) + @doc """ + Disables dependencies cache, so they are fetched and compiled from + scratch. + """ + @spec disable_dependencies_cache(t()) :: :ok + def disable_dependencies_cache(runtime) + @doc """ Sets the given environment variables. """ diff --git a/lib/livebook/runtime/attached.ex b/lib/livebook/runtime/attached.ex index f6266898f..882224824 100644 --- a/lib/livebook/runtime/attached.ex +++ b/lib/livebook/runtime/attached.ex @@ -137,6 +137,10 @@ defimpl Livebook.Runtime, for: Livebook.Runtime.Attached do raise "not supported" end + def disable_dependencies_cache(runtime) do + RuntimeServer.disable_dependencies_cache(runtime.server_pid) + end + def put_system_envs(runtime, envs) do RuntimeServer.put_system_envs(runtime.server_pid, envs) end diff --git a/lib/livebook/runtime/elixir_standalone.ex b/lib/livebook/runtime/elixir_standalone.ex index c6e618666..4de6a497d 100644 --- a/lib/livebook/runtime/elixir_standalone.ex +++ b/lib/livebook/runtime/elixir_standalone.ex @@ -300,6 +300,10 @@ defimpl Livebook.Runtime, for: Livebook.Runtime.ElixirStandalone do Livebook.Runtime.Dependencies.search_packages_on_hex(send_to, search) end + def disable_dependencies_cache(runtime) do + RuntimeServer.disable_dependencies_cache(runtime.server_pid) + end + def put_system_envs(runtime, envs) do RuntimeServer.put_system_envs(runtime.server_pid, envs) end diff --git a/lib/livebook/runtime/embedded.ex b/lib/livebook/runtime/embedded.ex index b8f4d4ac7..6fd84c636 100644 --- a/lib/livebook/runtime/embedded.ex +++ b/lib/livebook/runtime/embedded.ex @@ -122,6 +122,10 @@ defimpl Livebook.Runtime, for: Livebook.Runtime.Embedded do Livebook.Runtime.Dependencies.search_packages_in_list(packages, send_to, search) end + def disable_dependencies_cache(runtime) do + RuntimeServer.disable_dependencies_cache(runtime.server_pid) + end + def put_system_envs(runtime, envs) do RuntimeServer.put_system_envs(runtime.server_pid, envs) end diff --git a/lib/livebook/runtime/erl_dist/runtime_server.ex b/lib/livebook/runtime/erl_dist/runtime_server.ex index 27c894314..42b085240 100644 --- a/lib/livebook/runtime/erl_dist/runtime_server.ex +++ b/lib/livebook/runtime/erl_dist/runtime_server.ex @@ -180,6 +180,14 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do GenServer.cast(pid, {:stop_smart_cell, ref}) end + @doc """ + Disables dependencies cache globally. + """ + @spec disable_dependencies_cache(pid()) :: :ok + def disable_dependencies_cache(pid) do + GenServer.cast(pid, :disable_dependencies_cache) + end + @doc """ Sets the given environment variables. """ @@ -497,6 +505,12 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do {:noreply, state} end + def handle_cast(:disable_dependencies_cache, state) do + System.put_env("MIX_INSTALL_FORCE", "true") + + {:noreply, state} + end + def handle_cast({:put_system_envs, envs}, state) do envs |> Enum.map(fn diff --git a/lib/livebook/session.ex b/lib/livebook/session.ex index 0575211bc..820de9fdc 100644 --- a/lib/livebook/session.ex +++ b/lib/livebook/session.ex @@ -361,6 +361,14 @@ defmodule Livebook.Session do GenServer.cast(pid, {:add_dependencies, dependencies}) end + @doc """ + Sends disable dependencies cache request to the server. + """ + @spec disable_dependencies_cache(pid()) :: :ok + def disable_dependencies_cache(pid) do + GenServer.cast(pid, :disable_dependencies_cache) + end + @doc """ Sends cell evaluation request to the server. """ @@ -854,6 +862,14 @@ defmodule Livebook.Session do {:noreply, do_add_dependencies(state, dependencies)} end + def handle_cast(:disable_dependencies_cache, state) do + if Runtime.connected?(state.data.runtime) do + Runtime.disable_dependencies_cache(state.data.runtime) + end + + {:noreply, state} + end + def handle_cast({:queue_cell_evaluation, client_pid, cell_id}, state) do client_id = client_id(state, client_pid) operation = {:queue_cells_evaluation, client_id, [cell_id]} diff --git a/lib/livebook_web/live/live_helpers.ex b/lib/livebook_web/live/live_helpers.ex index 9734bc7f8..954272599 100644 --- a/lib/livebook_web/live/live_helpers.ex +++ b/lib/livebook_web/live/live_helpers.ex @@ -447,6 +447,9 @@ defmodule LivebookWeb.LiveHelpers do * `:position` - which side of the clickable the menu menu should be attached to, either `"left"` or `"right"`. Defaults to `"right"` + * `:distant` - whether the menu should be further from the anchor + element. Defaults to `false` + * `:secondary_click` - whether secondary click (usually right mouse click) should open the menu. Defaults to `false` @@ -466,6 +469,7 @@ defmodule LivebookWeb.LiveHelpers do assigns |> assign_new(:disabled, fn -> false end) |> assign_new(:position, fn -> "bottom-right" end) + |> assign_new(:distant, fn -> false end) |> assign_new(:secondary_click, fn -> false end) ~H""" @@ -480,7 +484,7 @@ defmodule LivebookWeb.LiveHelpers do diff --git a/lib/livebook_web/live/session_live.ex b/lib/livebook_web/live/session_live.ex index 008910213..0c9bdc923 100644 --- a/lib/livebook_web/live/session_live.ex +++ b/lib/livebook_web/live/session_live.ex @@ -1144,7 +1144,7 @@ defmodule LivebookWeb.SessionLive do {:noreply, socket} end - def handle_event("queue_cell_evaluation", %{"cell_id" => cell_id}, socket) do + def handle_event("queue_cell_evaluation", %{"cell_id" => cell_id} = params, socket) do assert_policy!(socket, :execute) data = socket.private.data @@ -1157,6 +1157,10 @@ defmodule LivebookWeb.SessionLive do _ -> {:ok, socket} end + if params["disable_dependencies_cache"] do + Session.disable_dependencies_cache(socket.assigns.session.pid) + end + if status == :ok do Session.queue_cell_evaluation(socket.assigns.session.pid, cell_id) end diff --git a/lib/livebook_web/live/session_live/cell_component.ex b/lib/livebook_web/live/session_live/cell_component.ex index 755a53cae..01ec93eb8 100644 --- a/lib/livebook_web/live/session_live/cell_component.ex +++ b/lib/livebook_web/live/session_live/cell_component.ex @@ -112,6 +112,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do cell_id={@cell_view.id} validity={@cell_view.eval.validity} status={@cell_view.eval.status} + runtime={@runtime} /> <:secondary> @@ -339,19 +340,42 @@ defmodule LivebookWeb.SessionLive.CellComponent do defp setup_cell_evaluation_button(%{status: :ready} = assigns) do ~H""" - + <%= unless Livebook.Runtime.fixed_dependencies?(@runtime) do %> + <.menu id="setup-menu" position="bottom-left" distant> + <:toggle> + + + <:content> + + + <% end %> - + """ end diff --git a/test/livebook_web/live/session_live_test.exs b/test/livebook_web/live/session_live_test.exs index 5d71a14ff..23df55008 100644 --- a/test/livebook_web/live/session_live_test.exs +++ b/test/livebook_web/live/session_live_test.exs @@ -143,6 +143,39 @@ defmodule LivebookWeb.SessionLiveTest do assert_receive {:operation, {:set_runtime, _pid, %{} = _runtime}} end + test "reevaluting the setup cell with dependencies cache disabled", + %{conn: conn, session: session} do + Session.subscribe(session.id) + + # Start the standalone runtime, to encapsulate env var changes + {:ok, runtime} = Runtime.ElixirStandalone.new() |> Runtime.connect() + Session.set_runtime(session.pid, runtime) + + evaluate_setup(session.pid) + + {:ok, view, _} = live(conn, "/sessions/#{session.id}") + + view + |> element(~s{[data-el-session]}) + |> render_hook("queue_cell_evaluation", %{ + "cell_id" => "setup", + "disable_dependencies_cache" => true + }) + + section_id = insert_section(session.pid) + + cell_id = + insert_text_cell(session.pid, section_id, :code, ~s/System.get_env("MIX_INSTALL_FORCE")/) + + view + |> element(~s{[data-el-session]}) + |> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id}) + + assert_receive {:operation, + {:add_cell_evaluation_response, _, ^cell_id, {:text, "\e[32m\"true\"\e[0m"}, + _}} + end + test "cancelling cell evaluation", %{conn: conn, session: session} do section_id = insert_section(session.pid) cell_id = insert_text_cell(session.pid, section_id, :code, "Process.sleep(2000)") diff --git a/test/support/noop_runtime.ex b/test/support/noop_runtime.ex index f6a0e91c1..000309ee4 100644 --- a/test/support/noop_runtime.ex +++ b/test/support/noop_runtime.ex @@ -39,6 +39,8 @@ defmodule Livebook.Runtime.NoopRuntime do def search_packages(_, _, _), do: make_ref() + def disable_dependencies_cache(_), do: :ok + def put_system_envs(_, _), do: :ok def delete_system_envs(_, _), do: :ok end