Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NixOS: coreutils are not captured because unthredable #5003

Closed
anki-code opened this issue Nov 28, 2022 · 21 comments · Fixed by #5440 or #5443
Closed

NixOS: coreutils are not captured because unthredable #5003

anki-code opened this issue Nov 28, 2022 · 21 comments · Fixed by #5440 or #5443

Comments

@anki-code
Copy link
Member

anki-code commented Nov 28, 2022

Originally posted by @amacfie in #4214 (comment):

On Debian/Arch:

docker run --rm -it python:3.10-slim /bin/bash -c "pip install 'xonsh[full]' && xonsh"

xonsh --version  
# xonsh/0.13.3

!(echo hi) == 'hi\n'
# True
!(echo hi).out == 'hi\n'
# True

But on NixOS:

docker run --rm -it nixos/nix:2.3.16 /bin/sh

nix-channel --update
nix-shell -p xonsh

xonsh --version  
# xonsh/0.13.3
xonsh

!(echo hi) == 'hi\n'
# hi
# False
!(echo hi).out == 'hi\n'
# hi
# False

For community

⬇️ Please click the 👍 reaction instead of leaving a +1 or 👍 comment

@gforsyth
Copy link
Collaborator

Might be that something in https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/programs/xonsh.nix (maybe in everything that gets sourced) is screwing up our piping.

@SamLukeYes
Copy link

SamLukeYes commented Dec 12, 2022

Might be that something in https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/programs/xonsh.nix (maybe in everything that gets sourced) is screwing up our piping.

Actually, the xonshrc defined in this module is not loaded by default without NixOS/nixpkgs#204252, which was merged a few hours ago and is not yet in nixos-unstable (though will be landed very soon). I can reproduce the same behavior no matter whether /etc/xonshrc is loaded, so I don't think this issue is related to the NixOS module.

@gforsyth
Copy link
Collaborator

Thanks @SamLukeYes -- that at least rules out a bunch of things that would be very annoying to check.

The only thing left that I can think of is that we're getting some behavior from subprocess.PIPE that is different than what we expect.

@yaxollum
Copy link
Member

This issue is specific to the echo command. On NixOS, when I run something like

!(python -c "print('hi')") == 'hi\n'

I get the expected output.

I think the cause of this issue is that xonsh thinks echo is not threadable, so it runs it using the Popen class instead of the PopenThread class and doesn't pipe its stdout/stderr:

xonsh/xonsh/procs/specs.py

Lines 747 to 761 in bcf2592

cmds_cache = XSH.commands_cache
thable = (
env.get("THREAD_SUBPROCS")
and (captured != "hiddenobject" or env.get("XONSH_CAPTURE_ALWAYS"))
and cmds_cache.predict_threadable(last.args)
and cmds_cache.predict_threadable(last.cmd)
)
if captured and thable:
last.cls = PopenThread
elif not thable:
# foreground processes should use Popen
last.threadable = False
if captured == "object" or captured == "hiddenobject":
# CommandPipeline objects should not pipe stdout, stderr
return

I can also reproduce this issue on Fedora by running

!(cat /etc/passwd)

The cat command is not threadable, so its output is not captured.

@anki-code anki-code changed the title nixos: captured subproc is not capturing captured subproc is not capturing Dec 19, 2022
@anki-code anki-code changed the title captured subproc is not capturing NixOS: captured subproc is not capturing May 22, 2024
@anki-code
Copy link
Member Author

Is it right that cmds_cache.predict_threadable returns False for nix cat?
What is the difference between nix cat and gnu cat?

@SamLukeYes
Copy link

Is it right that cmds_cache.predict_threadable returns False for nix cat?

I'm not sure if I'm doing it right, but I observe this on my NixOS:

$ import xonsh

$ xonsh.commands_cache.CommandsCache($(env)).predict_threadable('cat')
True

$ xonsh --version
xonsh/0.15.1

What is the difference between nix cat and gnu cat?

The default cat on NixOS is provided by GNU coreutils. There are also some other packages providing cat in nixpkgs tho, e.g. uutils, toybox, gash-utils, etc.

@anki-code
Copy link
Member Author

anki-code commented May 23, 2024

@SamLukeYes thank you for the answer! I'm not the NixOS user but I can help to investigate the difference because past time I spent to improving threading in xonsh. Could you please give me the list of commands that install xonsh from the main branch in nix docker i.e.

docker run --rm -it nixos/nix:2.3.16 /bin/sh
# install python and vim
# run `pip install git+https://github.com/xonsh/xonsh`
xonsh

After this I can understand what is the difference.

@SamLukeYes
Copy link

I'm not familiar with podman/docker and have trouble accessing the internet inside the container, probably because of my proxy setting. Anyway, I managed to reproduce this issue in a conda environment on NixOS, with the latest xonsh.

$ nix-shell -p conda vim --run conda-shell
$ conda-install    # may be needed before using conda for the first time
$ conda create -n test 'python<3.12'
$ conda activate test
$ pip install git+https://github.com/xonsh/xonsh
$ xonsh --version
xonsh/0.16.0.dev111

$ xonsh
$ !(echo hi) == 'hi\n'
hi
False

@anki-code
Copy link
Member Author

anki-code commented May 23, 2024

Thank you for instructions! I tried to install conda in docker but have fail without any instructions in the net of how to do it right. Sorry.

docker run --rm -it nixos/nix bash
nix-shell -p conda vim --run conda-shell
# building '/nix/store/253kppj13kr016xfdyg2iw0xwiriml2h-conda-shell.drv'...
# bwrap: Creating new namespace failed: Operation not permitted

But after this I found that micromamba (drop in replacement for conda) is working better.
But I faced with the question of how to install root CA certification on clean nix os. May be you can help as well?

docker run --rm -it nixos/nix bash
nix-shell -p micromamba
export MAMBA_ROOT_PREFIX=/myenv
# export MAMBA_SSL_NO_REVOKE=true
micromamba create -n myenv python=3.11 -c conda-forge
# error    libmamba No CA certificates found on system

@anki-code
Copy link
Member Author

anki-code commented May 23, 2024

The default cat on NixOS is provided by GNU coreutils.

It's interesting because I'm using gnu coreutils on mac and have no issues. We need to investigate the NixOS case.

Side thought: is it possible to you to install coreutils from brew on NixOS and test it with xonsh? (optionally)

To be honest it's my first time of working with NixOS.

@bestlem
Copy link
Contributor

bestlem commented May 23, 2024

If using nix what does running the nixpklgs version do e.g. nix run "nixpkgs#xonsh" do?
(For those new to nix nix can just install things so it does not get installed globally so rather like using docker but not packaged off inside a VM).

Unfortunately my nix is rather deeply embedded so can't give a simple case. These are just observations.

I don't understand docker either - although I would note anything using docker is running linux even if running from a mac.

I have xonsh installed via a nix flake on a mac mini apple silicon and I get (and also via nix run

🐚 xonsh --version
xonsh/0.15.1
🐚 !(echo hi).out == 'hi\n'
True
🐚 !(echo hi) == 'hi\n'
True

I would note that - the docker line I think is an old nix version

🐚 nix --version
nix (Nix) 2.19.4

xonsh question - Why don't I get hi printed out
I repeated starting the shell as xonsh -i -l --no-rc and with the nix run and these gave the same. Also using nixpkgs coreutils-prefixed for echo (as echo) I got the same results.

However as I ran xonsh (but xonsh --no-rc works) from an existing shell I get

zsh ❯  xonsh
[1]  + 93454 suspended (tty output)  xonsh

I would note that I have a possibly related issue with a third party program which behave differently if xonsh is the login shell or is run from another shell - in this case the latter is the bit that works)

@gforsyth
Copy link
Collaborator

$ xonsh
$ !(echo hi) == 'hi\n'
hi
False

@SamLukeYes -- we've started stripping newlines from the end of single-line commands, what's the output of

!(echo hi) == "hi"

?

@SamLukeYes
Copy link

what's the output of

!(echo hi) == "hi"
$ !(echo hi) == 'hi'
hi
False

Still the same 😕

@anki-code
Copy link
Member Author

@SamLukeYes can you help me with this - #5003 (comment)?

@SamLukeYes
Copy link

I'm not familiar with podman/docker and have trouble accessing the internet inside the container, probably because of my proxy setting.

The issue was because I use a local http proxy. Passing --net host to podman solves the issue for me.

Also, I found venv works inside the container:

$ nix-shell -p python3
$ python3 -m venv venv
$ source venv/bin/activate

And then you can use pip :)

But I faced with the question of how to install root CA certification on clean nix os.

AFAIK, the docker image nixos/nix is a distroless container with nix package manager available, but not an actual NixOS with its module system. I can still reproduce the issue in the container tho.

@anki-code
Copy link
Member Author

anki-code commented May 23, 2024

I can reproduce this! Many thanks @SamLukeYes!

docker run --rm -it nixos/nix bash
nix-shell -p python3
python3 -m venv venv
source venv/bin/activate
pip install git+https://github.com/xonsh/xonsh
xonsh

$XONSH_TRACE_SUBPROC=2
!(echo 1)
# Trace run_subproc({'cmds': (['echo', '1'],), 'captured': 'object'})
# 0: {'cmd': ['echo', '1'], 'cls': 'subprocess.Popen', 'bin': '/nix/store/k6h0vjh342kqlkq69sxjj8i5y50l6jfr-coreutils-9.3/bin/echo', 'thread': False, 'bg': False}
# 1
# CommandPipeline(
#   returncode=0,
#   pid=233,
#   args=['echo', '1'],
#   executed_cmd=['echo', '1'],
#   timestamps=[1716484360.5753396, 1716484360.5851793],
#   input='',
#   output=''
# )

__xonsh__.commands_cache.predict_threadable(['echo'])
# False

And 'thread': False, hm...

@anki-code
Copy link
Member Author

anki-code commented May 24, 2024

@SamLukeYes please take a look:

docker run --rm -it nixos/nix bash
nix-shell -p python3
python3 -m venv venv
source venv/bin/activate
pip install git+https://github.com/xonsh/xonsh@fix_nix  # @fix_nix
xonsh
!(echo Nix is nice)
# CommandPipeline(
#   returncode=0,
#   pid=148,
#   args=['echo', 'Nix', 'is', 'nice'],
#   executed_cmd=['echo', 'Nix', 'is', 'nice'],
#   timestamps=[1716552319.09322, 1716552319.1041143],
#   input='',
#   output='Nix is nice'
# )

The full case described in PR #5440.

@anki-code anki-code changed the title NixOS: captured subproc is not capturing NixOS: coreutils is not captured because unthredable May 24, 2024
@anki-code anki-code changed the title NixOS: coreutils is not captured because unthredable NixOS: coreutils are not captured because unthredable May 24, 2024
@SamLukeYes
Copy link

SamLukeYes commented May 24, 2024

I can confirm this works in a venv on NixOS. However, in a conda-shell environment, the output is still like this:

Nix is nice
CommandPipeline(
  returncode=0,
  pid=74595,
  args=['echo', 'Nix', 'is', 'nice'],
  executed_cmd=['echo', 'Nix', 'is', 'nice'],
  timestamps=[1716561398.5684817, 1716561398.5894334],
  input='',
  output=''
)

I noticed that echo has more symlink layers in this case, which is /usr/bin/echo -> /nix/store/mh1pbbg0h7swfv5vn0v6rlxh8yx5ga5j-conda-shell-usr-target/bin/echo -> /nix/store/php4qidg2bxzmm79vpri025bqi0fa889-coreutils-9.5/bin/echo -> coreutils, while it is /run/current-system/sw/bin/echo -> /nix/store/php4qidg2bxzmm79vpri025bqi0fa889-coreutils-9.5/bin/echo -> coreutils outside of conda-shell. The difference should be a common case of an FHS environment.

@SamLukeYes
Copy link

Oh, I missed an important point. In fact, coreutils is directly added to PATH inside nix-shell.

$ which echo
/run/current-system/sw/bin/echo

$ nix-shell -p --run 'which echo'
/nix/store/php4qidg2bxzmm79vpri025bqi0fa889-coreutils-9.5/bin/echo

When I start a nix-shell before running xonsh in the conda environment, the expected output can be seen.

CommandPipeline(
  returncode=0,
  pid=300965,
  args=['echo', 'Nix', 'is', 'nice'],
  executed_cmd=['echo', 'Nix', 'is', 'nice'],
  timestamps=[1716576009.7976441, 1716576009.8178146],
  input='',
  output='Nix is nice'
)

@anki-code
Copy link
Member Author

Thanks! But you highlighted the issue with symlink chain. I'm working on adding resolving symlink chains.

@anki-code
Copy link
Member Author

anki-code commented May 24, 2024

@SamLukeYes please test this case with symlink chain again. I've updated branch fix_nix.

gforsyth pushed a commit that referenced this issue May 24, 2024
### Motivation

Closes #5003

### The case

Core utils in Nix are symlinks to one binary file that contains all
utils:

```xsh
docker run --rm -it nixos/nix bash

which echo
# /nix/store/k6h0vjh342kqlkq69sxjj8i5y50l6jfr-coreutils-9.3/bin/echo

ls -la /nix/store/k6h0vjh342kqlkq69sxjj8i5y50l6jfr-coreutils-9.3/bin/
# b2sum -> coreutils
# base32 -> coreutils
# ...
# All tools are symlinks to one binary file - `coreutils`.
```

When
[`default_predictor_readbin`](https://github.com/xonsh/xonsh/blob/61bda708c992a300eab212f877718cf0050a5e3a/xonsh/commands_cache.py#L392)
read `coreutils` it catches `(b'isatty', b'tcgetattr', b'tcsetattr')`
and return `threadable=False` forever.

The list of Nix coreutils tools are exactly the same as in [brew
coreutils](https://formulae.brew.sh/formula/coreutils). So I can check
the real predicts on distinct binaries and see that only 2 tools among
106 are unthreadable and they already covered by
`default_threadable_predictors` (If it's wrong please add unthreadable
tools to the
[default_threadable_predictors](https://github.com/xonsh/xonsh/blob/61bda708c992a300eab212f877718cf0050a5e3a/xonsh/commands_cache.py#L518)):

```xsh
brew install coreutils

ls /opt/homebrew/opt/coreutils/libexec/gnubin/ | wc -l
# 106

for t in p`/opt/homebrew/opt/coreutils/libexec/gnubin/.*`:
    if not (th := __xonsh__.commands_cache.predict_threadable([t.name])):
        print($(which @(t.name)), th)
# /opt/homebrew/opt/coreutils/libexec/gnubin/cat False
# /opt/homebrew/opt/coreutils/libexec/gnubin/yes False

defaults = __import__('xonsh').commands_cache.default_threadable_predictors().keys()
defaults['cat']
# <function xonsh.commands_cache.predict_false>
defaults['yes']
# <function xonsh.commands_cache.predict_false>
```

So the rest of we need is to check the symlink and apply default
prediction if the tools is the symlink to coreutils. This implements
this PR. Test included.

## For community
⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍
comment**

---------

Co-authored-by: a <1@1.1>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
gforsyth added a commit that referenced this issue May 28, 2024
### Motivation

* We have no recommended way to force subprocess command be
(un)threadable
  * #4214
  * #2119
  * #5003
* It's interesting opportunity to have a way to modify specs and CP
using `SpecModifierAlias`.

### Before

```xsh
!(ssh host -T "echo 1")
# output=''  # EXPECTED: 1

__xonsh__.commands_cache.threadable_predictors['ssh'] = lambda *a, **kw: True
!(ssh host -T "echo 1")
```


### After

```xsh
xthread
# Mark command as threadable.

!(xthread ssh host -T "echo 1")
# output='1'
```

Closes:
* Closes #4214
* Closes #2119
* Partially closes #5003

Implementation of `SpecModifierAlias` will help in:
* #2618

JFYI #5413 

## For community
⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍
comment**

---------

Co-authored-by: a <1@1.1>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants