Skip to content

Commit 3a3362d

Browse files
committed
feat(builder): allow environments for windows and panes
This fixes #832.
1 parent d63f9f5 commit 3a3362d

File tree

7 files changed

+164
-13
lines changed

7 files changed

+164
-13
lines changed

CHANGES

+22-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,28 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force
1717

1818
## tmuxp 1.19.x (unreleased)
1919

20-
- Notes on upcoming releases will be added here
20+
### What's new
21+
22+
- #845: allow to configure window and pane specific environment variables
23+
24+
Having a setup like:
25+
```yaml
26+
session_name: env-demo
27+
environment:
28+
DATABASE_URL: "sqlite3:///default.db"
29+
windows:
30+
- window_name: dev
31+
environment:
32+
DATABASE_URL: "sqlite3:///dev-1.db"
33+
panes:
34+
- pane
35+
- environment:
36+
DATABASE_URL: "sqlite3:///dev-2.db"
37+
```
38+
will result in a window with two panes. In the first pane `$DATABASE_URL` is
39+
`sqlite3:///dev-1.db`, while in the second pane it is `sqlite3://dev-2.db`.
40+
Any freshly created window gets `sqlite3:///default.db` as this is what was
41+
defined for the session.
2142

2243
<!-- Maintainers, insert changes / features for the next release here -->
2344

docs/configuration/examples.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,9 @@ please make a ticket on the [issue tracker][issue tracker].
262262

263263
## Environment variables
264264

265-
tmuxp will set session environment variables.
265+
tmuxp will set session, window and pane environment variables. Note that
266+
setting environment variables for windows and panes requires tmuxp 1.19 or
267+
newer.
266268

267269
````{tab} YAML
268270

examples/session-environment.json

+24-7
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,32 @@
22
"environment": {
33
"EDITOR": "/usr/bin/vim",
44
"DJANGO_SETTINGS_MODULE": "my_app.settings.local",
5-
"SERVER_PORT": "8009",
6-
},
5+
"SERVER_PORT": "8009"
6+
},
77
"windows": [
88
{
99
"panes": [
10-
"./manage.py runserver 0.0.0.0:${SERVER_PORT}"
11-
],
10+
"./manage.py runserver 0.0.0.0:${SERVER_PORT}"
11+
],
1212
"window_name": "Django project"
13-
},
14-
],
13+
},
14+
{
15+
"environment": {
16+
"DJANGO_SETTINGS_MODULE": "my_app.settings.local",
17+
"SERVER_PORT": "8010"
18+
},
19+
"panes": [
20+
"./manage.py runserver 0.0.0.0:${SERVER_PORT}",
21+
{
22+
"environment": {
23+
"DJANGO_SETTINGS_MODULE": "my_app.settings.local-testing",
24+
"SERVER_PORT": "8011"
25+
},
26+
"shell_command": "./manage.py runserver 0.0.0.0:${SERVER_PORT}"
27+
}
28+
],
29+
"window_name": "Another Django project"
30+
}
31+
],
1532
"session_name": "Environment variables test"
16-
}
33+
}

examples/session-environment.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,13 @@ windows:
77
- window_name: Django project
88
panes:
99
- ./manage.py runserver 0.0.0.0:${SERVER_PORT}
10+
- window_name: Another Django project
11+
environment:
12+
DJANGO_SETTINGS_MODULE: my_app.settings.local
13+
SERVER_PORT: "8010"
14+
panes:
15+
- ./manage.py runserver 0.0.0.0:${SERVER_PORT}
16+
- environment:
17+
DJANGO_SETTINGS_MODULE: my_app.settings.local-testing
18+
SERVER_PORT: "8011"
19+
shell_command: ./manage.py runserver 0.0.0.0:${SERVER_PORT}

src/tmuxp/workspace/builder.py

+21
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import logging
88
import time
99

10+
from libtmux.common import has_gte_version
1011
from libtmux.exc import TmuxSessionExists
1112
from libtmux.pane import Pane
1213
from libtmux.server import Server
@@ -346,12 +347,22 @@ def iter_create_windows(self, session, append=False):
346347
except (KeyError, IndexError):
347348
pass
348349

350+
if has_gte_version("3.0"):
351+
environment = panes[0].get("environment", wconf.get("environment"))
352+
else:
353+
logging.warning(
354+
"Cannot set environment for new window. "
355+
"You need tmux 3.0 or newer for this."
356+
)
357+
environment = {}
358+
349359
w = session.new_window(
350360
window_name=window_name,
351361
start_directory=sd,
352362
attach=False, # do not move to the new window
353363
window_index=wconf.get("window_index", ""),
354364
window_shell=ws,
365+
environment=environment,
355366
)
356367

357368
if is_first_window_pass: # if first window, use window 1
@@ -418,11 +429,21 @@ def get_pane_shell():
418429
else:
419430
return None
420431

432+
if has_gte_version("3.0"):
433+
environment = pconf.get("environment", wconf.get("environment"))
434+
else:
435+
logging.warning(
436+
"Cannot set environment for new pane. "
437+
"You need tmux 3.0 or newer for this."
438+
)
439+
environment = {}
440+
421441
p = w.split_window(
422442
attach=True,
423443
start_directory=get_pane_start_directory(),
424444
shell=get_pane_shell(),
425445
target=p.id,
446+
environment=environment,
426447
)
427448

428449
assert isinstance(p, Pane)
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
session_name: test env vars
2-
start_directory: '~'
2+
start_directory: "~"
33
environment:
4-
FOO: BAR
4+
FOO: SESSION
55
PATH: /tmp
66
windows:
7-
- window_name: editor
7+
- window_name: no_overrides
88
panes:
99
- pane
10+
- window_name: window_overrides
11+
environment:
12+
FOO: WINDOW
13+
panes:
14+
- pane
15+
- window_name: pane_overrides
16+
panes:
17+
- environment:
18+
FOO: PANE
19+
- window_name: both_overrides
20+
environment:
21+
FOO: WINDOW
22+
panes:
23+
- pane
24+
- environment:
25+
FOO: PANE

tests/workspace/test_builder.py

+65-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import libtmux
1111
from libtmux.common import has_gte_version, has_lt_version
12+
from libtmux.session import Session
1213
from libtmux.test import retry_until, temp_session
1314
from libtmux.window import Window
1415
from tmuxp import exc
@@ -331,6 +332,10 @@ def f():
331332
assert w.name != "top"
332333

333334

335+
@pytest.mark.skipif(
336+
has_lt_version("3.0"),
337+
reason="needs -e flag for new-window and split-window introduced in tmux 3.0",
338+
)
334339
def test_environment_variables(session):
335340
workspace = ConfigReader._from_file(
336341
test_utils.get_workspace_file("workspace/builder/environment_vars.yaml")
@@ -340,9 +345,68 @@ def test_environment_variables(session):
340345
builder = WorkspaceBuilder(sconf=workspace)
341346
builder.build(session)
342347

343-
assert session.getenv("FOO") == "BAR"
348+
assert session.getenv("FOO") == "SESSION"
349+
assert session.getenv("PATH") == "/tmp"
350+
351+
no_overrides_win = session.windows[0]
352+
pane = no_overrides_win.panes[0]
353+
pane.send_keys("echo $FOO")
354+
assert pane.capture_pane()[1] == "SESSION"
355+
356+
window_overrides_win = session.windows[1]
357+
pane = window_overrides_win.panes[0]
358+
pane.send_keys("echo $FOO")
359+
assert pane.capture_pane()[1] == "WINDOW"
360+
361+
pane_overrides_win = session.windows[2]
362+
pane = pane_overrides_win.panes[0]
363+
pane.send_keys("echo $FOO")
364+
assert pane.capture_pane()[1] == "PANE"
365+
366+
both_overrides_win = session.windows[3]
367+
pane = both_overrides_win.panes[0]
368+
pane.send_keys("echo $FOO")
369+
assert pane.capture_pane()[1] == "WINDOW"
370+
pane = both_overrides_win.panes[1]
371+
pane.send_keys("echo $FOO")
372+
assert pane.capture_pane()[1] == "PANE"
373+
374+
375+
@pytest.mark.skipif(
376+
has_gte_version("3.0"),
377+
reason="warnings are not needed for tmux >= 3.0",
378+
)
379+
def test_environment_variables_logs(session: Session, caplog: pytest.LogCaptureFixture):
380+
workspace = ConfigReader._from_file(
381+
test_utils.get_workspace_file("workspace/builder/environment_vars.yaml")
382+
)
383+
workspace = loader.expand(workspace)
384+
385+
builder = WorkspaceBuilder(sconf=workspace)
386+
builder.build(session)
387+
388+
# environment on sessions should work as this is done using set-environment
389+
# on the session itself
390+
assert session.getenv("FOO") == "SESSION"
344391
assert session.getenv("PATH") == "/tmp"
345392

393+
assert (
394+
sum(
395+
1
396+
for record in caplog.records
397+
if "Cannot set environment for new window." in record.msg
398+
)
399+
== 4
400+
), "Warning on creating windows missing"
401+
assert (
402+
sum(
403+
1
404+
for record in caplog.records
405+
if "Cannot set environment for new pane." in record.msg
406+
)
407+
== 1
408+
), "Warning on creating panes missing"
409+
346410

347411
def test_automatic_rename_option(session):
348412
"""With option automatic-rename: on."""

0 commit comments

Comments
 (0)