From 3a3362d381121a7788cab325969218b74f63ea03 Mon Sep 17 00:00:00 2001 From: Bjoern Hiller Date: Tue, 1 Nov 2022 20:40:21 +0100 Subject: [PATCH 1/2] feat(builder): allow environments for windows and panes This fixes #832. --- CHANGES | 23 ++++++- docs/configuration/examples.md | 4 +- examples/session-environment.json | 31 +++++++-- examples/session-environment.yaml | 10 +++ src/tmuxp/workspace/builder.py | 21 ++++++ .../workspace/builder/environment_vars.yaml | 22 ++++++- tests/workspace/test_builder.py | 66 ++++++++++++++++++- 7 files changed, 164 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 3261ec59af3..78c152ec0ee 100644 --- a/CHANGES +++ b/CHANGES @@ -17,7 +17,28 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force ## tmuxp 1.19.x (unreleased) -- Notes on upcoming releases will be added here +### What's new + +- #845: allow to configure window and pane specific environment variables + + Having a setup like: + ```yaml + session_name: env-demo + environment: + DATABASE_URL: "sqlite3:///default.db" + windows: + - window_name: dev + environment: + DATABASE_URL: "sqlite3:///dev-1.db" + panes: + - pane + - environment: + DATABASE_URL: "sqlite3:///dev-2.db" + ``` + will result in a window with two panes. In the first pane `$DATABASE_URL` is + `sqlite3:///dev-1.db`, while in the second pane it is `sqlite3://dev-2.db`. + Any freshly created window gets `sqlite3:///default.db` as this is what was + defined for the session. diff --git a/docs/configuration/examples.md b/docs/configuration/examples.md index 6677c6d64f8..33c85057962 100644 --- a/docs/configuration/examples.md +++ b/docs/configuration/examples.md @@ -262,7 +262,9 @@ please make a ticket on the [issue tracker][issue tracker]. ## Environment variables -tmuxp will set session environment variables. +tmuxp will set session, window and pane environment variables. Note that +setting environment variables for windows and panes requires tmuxp 1.19 or +newer. ````{tab} YAML diff --git a/examples/session-environment.json b/examples/session-environment.json index d4c5c17cf91..3c31e12ec32 100644 --- a/examples/session-environment.json +++ b/examples/session-environment.json @@ -2,15 +2,32 @@ "environment": { "EDITOR": "/usr/bin/vim", "DJANGO_SETTINGS_MODULE": "my_app.settings.local", - "SERVER_PORT": "8009", - }, + "SERVER_PORT": "8009" + }, "windows": [ { "panes": [ - "./manage.py runserver 0.0.0.0:${SERVER_PORT}" - ], + "./manage.py runserver 0.0.0.0:${SERVER_PORT}" + ], "window_name": "Django project" - }, - ], + }, + { + "environment": { + "DJANGO_SETTINGS_MODULE": "my_app.settings.local", + "SERVER_PORT": "8010" + }, + "panes": [ + "./manage.py runserver 0.0.0.0:${SERVER_PORT}", + { + "environment": { + "DJANGO_SETTINGS_MODULE": "my_app.settings.local-testing", + "SERVER_PORT": "8011" + }, + "shell_command": "./manage.py runserver 0.0.0.0:${SERVER_PORT}" + } + ], + "window_name": "Another Django project" + } + ], "session_name": "Environment variables test" -} +} \ No newline at end of file diff --git a/examples/session-environment.yaml b/examples/session-environment.yaml index 1c766b56e4c..a1091e7a181 100644 --- a/examples/session-environment.yaml +++ b/examples/session-environment.yaml @@ -7,3 +7,13 @@ windows: - window_name: Django project panes: - ./manage.py runserver 0.0.0.0:${SERVER_PORT} + - window_name: Another Django project + environment: + DJANGO_SETTINGS_MODULE: my_app.settings.local + SERVER_PORT: "8010" + panes: + - ./manage.py runserver 0.0.0.0:${SERVER_PORT} + - environment: + DJANGO_SETTINGS_MODULE: my_app.settings.local-testing + SERVER_PORT: "8011" + shell_command: ./manage.py runserver 0.0.0.0:${SERVER_PORT} diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index 677ad9f9234..94a41e09d90 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -7,6 +7,7 @@ import logging import time +from libtmux.common import has_gte_version from libtmux.exc import TmuxSessionExists from libtmux.pane import Pane from libtmux.server import Server @@ -346,12 +347,22 @@ def iter_create_windows(self, session, append=False): except (KeyError, IndexError): pass + if has_gte_version("3.0"): + environment = panes[0].get("environment", wconf.get("environment")) + else: + logging.warning( + "Cannot set environment for new window. " + "You need tmux 3.0 or newer for this." + ) + environment = {} + w = session.new_window( window_name=window_name, start_directory=sd, attach=False, # do not move to the new window window_index=wconf.get("window_index", ""), window_shell=ws, + environment=environment, ) if is_first_window_pass: # if first window, use window 1 @@ -418,11 +429,21 @@ def get_pane_shell(): else: return None + if has_gte_version("3.0"): + environment = pconf.get("environment", wconf.get("environment")) + else: + logging.warning( + "Cannot set environment for new pane. " + "You need tmux 3.0 or newer for this." + ) + environment = {} + p = w.split_window( attach=True, start_directory=get_pane_start_directory(), shell=get_pane_shell(), target=p.id, + environment=environment, ) assert isinstance(p, Pane) diff --git a/tests/fixtures/workspace/builder/environment_vars.yaml b/tests/fixtures/workspace/builder/environment_vars.yaml index 1a3e6dddc40..eec3de8e0eb 100644 --- a/tests/fixtures/workspace/builder/environment_vars.yaml +++ b/tests/fixtures/workspace/builder/environment_vars.yaml @@ -1,9 +1,25 @@ session_name: test env vars -start_directory: '~' +start_directory: "~" environment: - FOO: BAR + FOO: SESSION PATH: /tmp windows: -- window_name: editor +- window_name: no_overrides panes: - pane +- window_name: window_overrides + environment: + FOO: WINDOW + panes: + - pane +- window_name: pane_overrides + panes: + - environment: + FOO: PANE +- window_name: both_overrides + environment: + FOO: WINDOW + panes: + - pane + - environment: + FOO: PANE diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 4ee513cbc9d..2852c2f2eb3 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -9,6 +9,7 @@ import libtmux from libtmux.common import has_gte_version, has_lt_version +from libtmux.session import Session from libtmux.test import retry_until, temp_session from libtmux.window import Window from tmuxp import exc @@ -331,6 +332,10 @@ def f(): assert w.name != "top" +@pytest.mark.skipif( + has_lt_version("3.0"), + reason="needs -e flag for new-window and split-window introduced in tmux 3.0", +) def test_environment_variables(session): workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/environment_vars.yaml") @@ -340,9 +345,68 @@ def test_environment_variables(session): builder = WorkspaceBuilder(sconf=workspace) builder.build(session) - assert session.getenv("FOO") == "BAR" + assert session.getenv("FOO") == "SESSION" + assert session.getenv("PATH") == "/tmp" + + no_overrides_win = session.windows[0] + pane = no_overrides_win.panes[0] + pane.send_keys("echo $FOO") + assert pane.capture_pane()[1] == "SESSION" + + window_overrides_win = session.windows[1] + pane = window_overrides_win.panes[0] + pane.send_keys("echo $FOO") + assert pane.capture_pane()[1] == "WINDOW" + + pane_overrides_win = session.windows[2] + pane = pane_overrides_win.panes[0] + pane.send_keys("echo $FOO") + assert pane.capture_pane()[1] == "PANE" + + both_overrides_win = session.windows[3] + pane = both_overrides_win.panes[0] + pane.send_keys("echo $FOO") + assert pane.capture_pane()[1] == "WINDOW" + pane = both_overrides_win.panes[1] + pane.send_keys("echo $FOO") + assert pane.capture_pane()[1] == "PANE" + + +@pytest.mark.skipif( + has_gte_version("3.0"), + reason="warnings are not needed for tmux >= 3.0", +) +def test_environment_variables_logs(session: Session, caplog: pytest.LogCaptureFixture): + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/environment_vars.yaml") + ) + workspace = loader.expand(workspace) + + builder = WorkspaceBuilder(sconf=workspace) + builder.build(session) + + # environment on sessions should work as this is done using set-environment + # on the session itself + assert session.getenv("FOO") == "SESSION" assert session.getenv("PATH") == "/tmp" + assert ( + sum( + 1 + for record in caplog.records + if "Cannot set environment for new window." in record.msg + ) + == 4 + ), "Warning on creating windows missing" + assert ( + sum( + 1 + for record in caplog.records + if "Cannot set environment for new pane." in record.msg + ) + == 1 + ), "Warning on creating panes missing" + def test_automatic_rename_option(session): """With option automatic-rename: on.""" From e841f924d43d81b28e1e055a84c666d39120695d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 19 Nov 2022 09:42:20 -0600 Subject: [PATCH 2/2] test(environment_vars): Sleep at init for slow shells --- tests/workspace/test_builder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 2852c2f2eb3..41e2bdc9d3b 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -336,7 +336,7 @@ def f(): has_lt_version("3.0"), reason="needs -e flag for new-window and split-window introduced in tmux 3.0", ) -def test_environment_variables(session): +def test_environment_variables(session: Session): workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/environment_vars.yaml") ) @@ -350,6 +350,7 @@ def test_environment_variables(session): no_overrides_win = session.windows[0] pane = no_overrides_win.panes[0] + time.sleep(0.3) # To let shells with slow startup catch up pane.send_keys("echo $FOO") assert pane.capture_pane()[1] == "SESSION"