pyinvoke / invoke

Pythonic task management & command execution.
http://pyinvoke.org
BSD 2-Clause "Simplified" License
4.41k stars 368 forks source link

NixOS don't have `/bin/bash`! #1011

Open shackra opened 1 week ago

shackra commented 1 week ago

On NixOS this package is completely useless and also sabotages the usage of other packages that depend on it to function.

$ wger bootstrap
Traceback (most recent call last):
  File "/home/wger/venv/bin/wger", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/wger/src/wger/__main__.py", line 40, in main
    run(invoke_cmd + ' '.join(args), pty=True)
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/__init__.py", line 50, in run
    return Context().run(command, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/context.py", line 104, in run
    return self._run(runner, command, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/context.py", line 113, in _run
    return runner.run(command, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/runners.py", line 395, in run
    return self._run_body(command, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/runners.py", line 440, in _run_body
    self.start(command, self.opts["shell"], self.env)
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/runners.py", line 1335, in start
    os.execve(shell, [shell, "-c", command], env)
FileNotFoundError: [Errno 2] No such file or directory: '/bin/bash'
Traceback (most recent call last):
  File "/home/wger/venv/bin/wger", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/wger/src/wger/__main__.py", line 40, in main
    run(invoke_cmd + ' '.join(args), pty=True)
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/__init__.py", line 50, in run
    return Context().run(command, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/context.py", line 104, in run
    return self._run(runner, command, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/context.py", line 113, in _run
    return runner.run(command, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/runners.py", line 395, in run
    return self._run_body(command, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/runners.py", line 451, in _run_body
    return self.make_promise() if self._asynchronous else self._finish()
                                                          ^^^^^^^^^^^^^^
  File "/home/wger/venv/lib/python3.11/site-packages/invoke/runners.py", line 518, in _finish
    raise UnexpectedExit(result)
invoke.exceptions.UnexpectedExit: Encountered a bad command exit code!

Command: 'invoke bootstrap'

Exit code: 1

Stdout: already printed

Stderr: n/a (PTYs have no stderr)

On NixOS bash is located here:

$ which bash
/run/current-system/sw/bin/bash
D3f0 commented 14 hours ago

Invoke has a great way to override configuration which can be triggered by a file or an environment variable. You can run invoke with the -d flag for debug information and you can determine where that file would be located in your nix environment or if your INVOKE_XXX_YYY variable is being picked.

Particularly for the shell, you can simply set the INVOKE_RUN_SHELL variable

I haven't found a way to run pip from nix-shell to demonstrate this but you can use uv to test this.

 export INVOKE_RUN_SHELL=/run/current-system/sw/bin/bash

echo '
from invoke import task
from invoke.context import Context
from invoke.collection import Collection
from invoke.program import Program
import os

@task(default=True)
def show_me_config(ctx: Context):
    for key, value in dict(ctx.config.run).items():
        env_var = f'INVOKE_RUN_{key.upper()}'
        changed = '✅' if env_var in os.environ else f'💡 change with {env_var}'
        print(f"{key}={value} ({changed})")

ns = Collection()
ns.add_task(show_me_config)

p = Program("test", ns)
p.run()

' | uv run --with invoke -

The output should be something like:

asynchronous=False (💡 change with INVOKE_RUN_ASYNCHRONOUS)
disown=False (💡 change with INVOKE_RUN_DISOWN)
dry=False (💡 change with INVOKE_RUN_DRY)
echo=False (💡 change with INVOKE_RUN_ECHO)
echo_stdin=None (💡 change with INVOKE_RUN_ECHO_STDIN)
encoding=None (💡 change with INVOKE_RUN_ENCODING)
env=<DataProxy: {}> (💡 change with INVOKE_RUN_ENV)
err_stream=None (💡 change with INVOKE_RUN_ERR_STREAM)
fallback=True (💡 change with INVOKE_RUN_FALLBACK)
hide=None (💡 change with INVOKE_RUN_HIDE)
in_stream=None (💡 change with INVOKE_RUN_IN_STREAM)
out_stream=None (💡 change with INVOKE_RUN_OUT_STREAM)
echo_format={command} (💡 change with INVOKE_RUN_ECHO_FORMAT)
pty=False (💡 change with INVOKE_RUN_PTY)
replace_env=False (💡 change with INVOKE_RUN_REPLACE_ENV)
shell=/run/current-system/sw/bin/bash (✅)
warn=False (💡 change with INVOKE_RUN_WARN)
watchers=[] (💡 change with INVOKE_RUN_WATCHERS)

Note: Only primitive types can be overridden in this way (int, bool, str, but not list or dict)