diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..509bb3f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,31 @@ +name: tests +on: + pull_request_target: + branches: + - main + types: [closed] + push: + branches: + - main + +permissions: + contents: write + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + pip install . + pip install pytest pytest-cov + + - name: test + run: | + pytest --cov=guix_env tests \ No newline at end of file diff --git a/README.md b/README.md index 4539280..0a7dbba 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # Guix & venv environments for reproducible python development -This package give a cli tool to construct and enter environments constructed through guix (for the system-level packages) and pip/venv (for python packages). - -Remark that due to the use of pip, the resulting environment is not perfectly reproducible as it would be better to use guix all the way but this way is much simpler when using dev python packages that are not yet packaged in guix. - +This package give a cli tool to construct and enter environments constructed through guix (for the system-level packages) and optionally a poetry environment (for python packages). ## Usage Guix must be installed on the system, see [the guix manual](https://guix.gnu.org/manual/en/html_node/Binary-Installation.html) to do this. @@ -13,7 +10,7 @@ pip install git+https://github.com/TimotheeMathieu/guix-env guix-env create my_env_name ``` -This should create a directory in `~/.guix-env` containing files needed for the environment to run. Note that every file generated by guix-env will be saved into `~/.guix-env`. Then to spawn a shell in the environment, do +This should create a directory in `~/.guix-env` containing files needed for the environment to run. Note that every file generated by guix-env will be saved into `~/.guix-env` and removing the environment is as simple as removing this directory (also doable through the `guix-env rm` command). Then to spawn a shell in the environment, do ``` guix-env shell my_env_name @@ -21,18 +18,24 @@ guix-env shell my_env_name The first run may be a bit slow because of guix downloading a bunch of packages but the second run should be faster as guix cache the packages it uses in `/gnu/store` (remark: don't forget to use `guix gc` to clear the store periodically). -Then, you are good to go and do anything you wish in your environment. You are in a python virtual environment and you can install new python packages with pip. To add new guix package, use `guix-env add my_env_name my_package_name` from outside the environment. +Then, you are good to go and do anything you wish in your environment. You are in a python virtual environment that is managed with poetry for reproducibility purpose, to install new python package from inside the environment, use `gep add package_name`, `gep` stands for guix-env-poetry and is just an alias of poetry that install at the right place. To add new guix package, use `guix-env add-guix my_env_name my_package_name` from outside the environment. -One of the qualities of guix-env is its *reproducibility*, you can use the three files `manifest.scm`, `channels.scm` and `requirements.txt` that are in `~/.guix-env/my_env_name` to reproduce the environment using the following command: -``` -guix-env create my_env_name --channel-file channels.scm --manifest-file manifest.scm --requirements-file requirements.txt -``` -Remark that it is not perfect reproductibility because the requirements.txt file is created using pip whether it would be better to use a lock file generated by `pip-tools` or `poetry`, or even better to use guix as python package manager. For now this is not implemented. + + +Remark that I made a few opinionated design choices: +- I included some guix and python packages that are convenient for basic shell commands and basic graphical display. +- I use zsh shell in the guix-env environments. +- Every environment has its own .local and .zshrc. They can be accessed in $HOME/.guix_env/env_name +- I do not share the home directory, by default the only directory shared are the current directory and its children (default from guix shell). +- I use the Filesystem Hierarchy Standard (FHS) emulator of guix shell to populate /bin and /lib apropriately for some python compatibility. +- I use a poetry cache specific to guix-env environments. It is shared among the guix envs but not with the host system. ## TODO -- Better documentation -- better requirements.txt -- switch to poetry or pip-tools to handle locks? -- Tests +- Better documentation -- include explanations of how it works: poetry, what do we share (what we do not share, e.g. .local), how to tinker with it, what changes are made... +- Comment more +- Make tests +- Have an alias that install guix_env in a guix shell environment so that we can install & use guix-env in a reproducible maneer and without needing to install anything. +- Feature: rollback, at first this could be through git repo that auto-commit. - Handle GPU ? -- Feature: rollback, similar to what can be done with guix-home. +- Feature: use tmux inside the env and share tmp to make a sort of daemon. https://stackoverflow.com/questions/16398850/create-new-tmux-session-from-inside-a-tmux-session diff --git a/guix_env/__init__.py b/guix_env/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/guix_env/cli.py b/guix_env/cli.py index 5a7cd1f..ec94299 100644 --- a/guix_env/cli.py +++ b/guix_env/cli.py @@ -6,6 +6,7 @@ import shutil from jinja2 import Environment, FileSystemLoader +# TODO: add test that the environment exists before doing anything. main_dir=os.path.join(os.getenv("HOME"), ".guix_env") file_path = os.path.realpath(__file__) @@ -13,14 +14,26 @@ environment = Environment(loader=FileSystemLoader( os.path.join(os.path.dirname(file_path),"template_scripts/"))) -default_guix_packages = [ +guix_python_packages = [ "python", "python-toolchain", + "poetry-next", # this comes from perso channel while waiting for guix to have a newer version of poetry + "xcb-util", # xcb is for matplotlib to be able to plt.show + "xcb-util-wm", + "xcb-util-image", + "xcb-util-keysyms", + "xcb-util-renderutil", + "xcb-util-cursor", + ] + +default_guix_packages = [ "bash", "glibc-locales", "nss-certs", "coreutils", "diffutils", + "findutils", + "curl", "git", "make", "zlib", @@ -28,19 +41,13 @@ "tcl", "gtk", "grep", - "xcb-util", # xcb/dbus is for matplotlib to be able to plt.show - "xcb-util-wm", - "xcb-util-image", - "xcb-util-keysyms", - "xcb-util-renderutil", "dbus", - "xcb-util-cursor", "ncurses", + "nano", + "tmux", "zsh" ] -default_python = ["numpy", "matplotlib", "PyQt5", "scipy"] - @click.group() @click.pass_context def guix_env(ctx): @@ -53,83 +60,87 @@ def guix_env(ctx): @click.argument('name',required = True, type=str) @click.option('--channel-file',required = False, type=str, help="Path to a channel file to be used in the guix install") @click.option('--requirements-file',required = False, type=str, help="Path to a requirements.txt file to be used in the python install") -@click.option('--manifest-file',required = False, type=str, help="Path to a manifest file to be used in the guix install") +@click.option('--without-python', is_flag=True, help="Do an environment without python") +@click.option('--pyproject-file',required = False, type=str, help="Path to a pyproject.toml file to be used in the python install (override requirement file if both are given).") +@click.option('--poetry-lock-file',required = False, type=str, help="Path to a poetry.lock file to be used in the python install") +@click.option('--manifest-file',required = False, type=str, help="Path to a manifest file to be used in the guix install. Will replace the default manifest.") @click.option('--guix-args',required = False, type=str, default="-CFNW", help="arguments to be passed to guix") @click.pass_context -def create(ctx, name, channel_file, requirements_file, manifest_file, guix_args): +def create(ctx, name, channel_file, without_python, requirements_file, pyproject_file, poetry_lock_file, manifest_file, guix_args): """ Create an environment with name `name`. A channel file can be specified, otherwise a channel file will be automatically created. """ + with_python = not without_python assert not os.path.isdir(os.path.join(main_dir, name)), "Environment already exist" os.system('mkdir -p '+os.path.join(main_dir, name, "bin")) + os.system('mkdir -p '+os.path.join(main_dir, name, ".local")) + home = os.getenv("HOME") - zshrc = environment.get_template("zshrc").render(name = name, reqfile = os.path.join(main_dir, name, "requirements.txt")) - - # TBC - - if channel_file is None: - channels = subprocess.run(["guix", "describe", "-f", "channels"], capture_output=True).stdout.decode() - else: - channels = subprocess.run(["cat", channel_file], capture_output=True).stdout.decode() - - template = environment.get_template("activate.sh") - script = template.render(name = name, guix_args = guix_args) + zshrc = environment.get_template("zshrc").render(name = name, reqfile = os.path.join(main_dir, name, "requirements.txt"), with_python=with_python) + run_script = environment.get_template("run_script.sh").render(name=name, guix_args = guix_args, HOME=home, with_python=with_python) - with open(os.path.join(main_dir, name, "bin", ".zshrc"), "w") as myfile: - myfile.write(zshrc) - - with open(os.path.join(main_dir, name, "bin", "activate.sh"), "w") as myfile: - myfile.write(script) - os.system('chmod +x '+os.path.join(main_dir, name, "bin", "activate.sh")) + channels = _make_channel_file(channel_file) - with open(os.path.join(main_dir, name, "bin", "run.sh"), "w") as myfile: - - template_run = environment.get_template("run_script.sh") - run_script = template_run.render(name = name, guix_args = guix_args) + with open(os.path.join(main_dir, name, ".zshrc"), "w") as myfile: + myfile.write(zshrc) + with open(os.path.join(main_dir, name, "bin", "run_script.sh"), "w") as myfile: myfile.write(run_script) - os.system('chmod +x '+os.path.join(main_dir, name, "bin", "run.sh")) - + os.system('chmod +x '+os.path.join(main_dir, name, "bin", "run_script.sh")) + + # Guix manifest and channel files if channel_file is None: with open(os.path.join(main_dir, name, "channels.scm"), "w") as myfile: myfile.write(channels) else: os.system("cp "+channel_file+" "+os.path.join(main_dir, name, "channels.scm")) - + if manifest_file is None: with open(os.path.join(main_dir, name, "manifest.scm"), "w") as myfile: packages = default_guix_packages + if with_python: + packages = packages + guix_python_packages myfile.write( "(specifications->manifest '(\n\"" + '"\n "'.join(packages) + '"\n))' ) else: os.system("cp "+manifest_file+" "+os.path.join(main_dir, name, "manifest.scm")) - - print("Creation of virtual environment") - os.system("guix time-machine --channels=${HOME}/.guix_env/"+name+"/channels.scm -- shell python -- python3 -m venv ~/.guix_env/guix_env_venv/"+name+"_venv") - run_file = os.path.join(main_dir, name, "bin", "run.sh") - if requirements_file is None: - print("Installing default python libs") - with open(os.path.join(main_dir, name, "requirements.txt"), "w") as myfile: - myfile.write("\n".join(default_python)) - - requirements_file = os.path.join(main_dir, name, "requirements.txt") - - os.system(run_file+" python -m pip install -r "+requirements_file) - os.system(run_file+" python -m pip freeze > "+os.path.join(main_dir, name, "requirements.txt")) + # + with open(os.path.join(main_dir, name, "bin", "launch_in_guix.sh"), "w") as myfile: + launcher = environment.get_template("launch_in_guix.sh").render(name=name, guix_args = guix_args,) + myfile.write(launcher) + + os.system("chmod +x "+os.path.join(main_dir, name, "bin", "launch_in_guix.sh")) + + with open(os.path.join(main_dir, name, "bin", "launch_shell.sh"), "w") as myfile: + launcher = environment.get_template("launch_shell.sh").render(name=name, with_python=with_python) + myfile.write(launcher) + os.system("chmod +x "+os.path.join(main_dir, name, "bin", "launch_shell.sh")) + + if with_python: + ### construct a poetry environment optionally with the specified requirements + _make_python_env(main_dir, name, pyproject_file, poetry_lock_file, requirements_file) + + print(f"Guix-env environment {name} has beenn created, its files can be found in {os.path.join(main_dir, name)}") + @guix_env.command() @click.argument('name',required = True, type=str) @click.pass_context def update(ctx, name): """ - Update the channel file (and as a consequence, it will update the packages managed by guix at next shell/run). + Update the channel file to the current guix channel file (and as a consequence, it will update the packages managed by guix at next shell/run). + """ - channels = subprocess.run(["guix", "describe", "-f", "channels"], capture_output=True).stdout.decode() + print("Updating channel file") + channels = _make_channel_file(os.path.join(main_dir, name, "channels.scm")) with open(os.path.join(main_dir, name, "channels.scm"), "w") as myfile: myfile.write(channels) - + if os.path.isfile(os.path.join(main_dir, name, "pyproject.toml")): + print("Found python install, updating") + _launch_cmd(name, "gep update") + @guix_env.command() @click.argument('name',required = True, type=str) @@ -138,17 +149,18 @@ def rm(ctx, name): """ Remove a guix-env environment. """ - shutil.rmtree(os.path.join(main_dir, name)) - shutil.rmtree(os.path.join(main_dir, "guix_env_venv", name+"_venv")) - + if os.path.isdir(os.path.join(main_dir, name)): + print("Removing ", os.path.join(main_dir, name)) + shutil.rmtree(os.path.join(main_dir, name)) @guix_env.command() @click.argument('name',required = True, type=str) @click.argument('pkg',required = True, type=str) @click.pass_context -def add(ctx, name, pkg): +def add_guix(ctx, name, pkg): """ - Add the package `pkg` to the environment named `name`. + Add the guix package `pkg` to the environment named `name`. + Warning: if you add a package from inside an environment, the package will not be available until you reconstruct the environment. """ with open(os.path.join(main_dir, name, "manifest.scm"), "r") as myfile: packages = myfile.read().split("(")[2].split(")")[0] @@ -161,6 +173,17 @@ def add(ctx, name, pkg): myfile.write( "(specifications->manifest '(\n\"" + '"\n "'.join(packages) + '"\n))' ) + print(f"Package {name} added to the manifest") + +@guix_env.command() +@click.argument('name',required = True, type=str) +@click.argument('pkg',required = True, type=str) +@click.pass_context +def add_python(ctx, name, pkg): + """ + Add the python package `pkg` to the environment named `name`. + """ + _launch_cmd(name, f"gep add {pkg}") @guix_env.command() @click.pass_context @@ -178,8 +201,7 @@ def info(ctx, name): Get informations on environment with name `name`. """ click.echo("Environment located in "+os.path.join(main_dir, name)) - run_file = os.path.join(main_dir, name, "bin", "run.sh") - os.system(run_file+" guix describe") + _launch_cmd(name," guix describe") with open(os.path.join(main_dir, name, "manifest.scm"), "r") as myfile: packages = myfile.read().split("(")[2].split(")")[0] @@ -191,7 +213,7 @@ def info(ctx, name): click.echo("\n".join(packages)) click.echo("-"*10) click.echo("Installed python packages") - os.system(run_file+" pip3 freeze") + _launch_cmd(name, "gep run pip3 freeze") @guix_env.command() @click.argument('name',required = True, type=str) @@ -202,23 +224,12 @@ def shell(ctx, name, tmux, cwd): """ Open a shell in the environment with name `name`. """ + assert os.path.isdir(os.path.join(main_dir, name)), "Environment does not exist" + + print(f"Welcome to your guix-env environment: {name}") + print("To install python package, use 'gep add package_name'. gep is ann alias for poetry that install things at the right place.") - activation_file = os.path.join(main_dir, name, "bin", "activate.sh") - - if tmux: - child = subprocess.run(["tmux","has-session", "-t", "guix_env_"+name],capture_output=True,text=True) - rc = child.returncode - if rc != 0: - print("env not launched yet, launching now") - os.system("tmux new-session -d -s guix_env_"+name+" "+activation_file) - - if cwd: - wd = os.getcwd() - os.system("tmux send-keys -t guix_env_"+name+" \" cd "+wd+ " && clear\" ENTER") - print("done cwd") - os.system("tmux attach -t guix_env_"+name) - else: - os.system(activation_file) + os.system(os.path.join(main_dir, name, "bin", "launch_in_guix.sh") + " " + os.path.join(main_dir, name, "bin", "launch_shell.sh")) @guix_env.command() @click.argument('name',required = True, type=str) @@ -231,11 +242,16 @@ def run(ctx, name, cmd): Example of usage is guix-env run my_env "ls $HOME/" """ - run_file = os.path.join(main_dir, name, "bin", "run.sh") - os.system(run_file+" "+cmd) - + + _launch_cmd(name, cmd) + + +def _launch_cmd(name, cmd): + os.system(os.path.join(main_dir, name, "bin", "launch_in_guix.sh")+ " " + os.path.join(main_dir, name, "bin", "run_script.sh") + " " + cmd) + def _is_in_guix(pkg): + print("Checking that the package is indeed a guix package") output = subprocess.run(["guix", "search", pkg], capture_output=True).stdout.decode() output = output.split("name: ") names = [o.split("\n")[0] for o in output] @@ -244,3 +260,45 @@ def _is_in_guix(pkg): if pkg == name.strip(): res = True return res + +def _make_channel_file(channel_file=None): + if channel_file is None: + system_channels = subprocess.run(["guix", "describe", "-f", "channels"], capture_output=True).stdout.decode() + else: + system_channels = subprocess.run(["cat", channel_file], capture_output=True).stdout.decode() + + channels = environment.get_template("channels.scm").render(system_channels = system_channels) + return channels + + +def _make_python_env(main_dir, name, pyproject_file, poetry_lock_file, requirements_file): + guix_python_cmd = f"guix time-machine --channels=$HOME/.guix_env/{name}/channels.scm -- shell python -- python3 --version | cut -d ' ' -f 2" + python_version = subprocess.check_output(guix_python_cmd, shell=True).decode().strip() + + if pyproject_file is None: + author = subprocess.run(["whoami"], capture_output=True).stdout.decode() + pyproject = environment.get_template("pyproject.toml").render(name = name, python_version = python_version) + else: + with open(pyproject_file, "r") as myfile: + pyproject = myfile.read() + + with open(os.path.join(main_dir, name, "pyproject.toml"), "w") as myfile: + myfile.write(pyproject) + + if poetry_lock_file is not None: + os.system(f"cp {poetry_lock_file} {os.path.join(main_dir, name)}") + + if requirements_file is None: + reqfile = "" + else: + os.system("cp "+os.path.realpath(requirements_file)+ " /tmp/requirements_for_guix_env.txt") + reqfile = "/tmp/requirements_for_guix_env.txt" + + + create_env_file = environment.get_template("create_env.sh").render(name=name, + directory = os.path.join(main_dir, name), + requirements = reqfile) + with open(os.path.join("/tmp", "create_guix_env.sh"), "w") as myfile: + myfile.write(create_env_file) + os.system("chmod +x "+os.path.join("/tmp", "create_guix_env.sh")) + os.system(os.path.join(main_dir, name, "bin", "launch_in_guix.sh")+ " " + os.path.join("/tmp", "create_guix_env.sh")) diff --git a/guix_env/template_scripts/activate.sh b/guix_env/template_scripts/activate.sh deleted file mode 100644 index 3da109c..0000000 --- a/guix_env/template_scripts/activate.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env -S guix time-machine --channels=${HOME}/.guix_env/{{ name }}/channels.scm -- shell {{ guix_args }} --preserve='(^DISPLAY$|^XAUTHORITY$|^TERM$)' --share=${HOME} --expose=/dev/dri --expose=/sys -m ${HOME}/.guix_env/{{ name }}/manifest.scm -- bash - -# Link the lib file for FHS library handling -export LD_LIBRARY_PATH=/lib - -echo 'Entering guix environment' -rm ${HOME}/.guix_env/guix_env_venv/{{ name }}_venv/bin/python3 -ln -s $(which python3) ${HOME}/.guix_env/guix_env_venv/{{ name }}_venv/bin/python3 -source ${HOME}/.guix_env/guix_env_venv/{{ name }}_venv/bin/activate - -pip freeze > /tmp/guix_env_{{ name }}_requirements.txt -if cmp --silent -- "/tmp/guix_env_{{ name }}_requirements.txt" "${HOME}/.guix_env/{{ name }}/requirements.txt" -then - echo -else - read -p "Requirement and installed packages are different. Reinstall from requirements.txt ? (y/n)" -n 1 -r - echo # (optional) move to a new line - if [[ $REPLY =~ ^[Yy]$ ]] - then - pip install -r ${HOME}/.guix_env/{{ name }}/requirements.txt - fi -fi - - -ZDOTDIR=${HOME}/.guix_env/{{ name }}/bin zsh diff --git a/guix_env/template_scripts/channels.scm b/guix_env/template_scripts/channels.scm new file mode 100644 index 0000000..6171da9 --- /dev/null +++ b/guix_env/template_scripts/channels.scm @@ -0,0 +1,6 @@ +(cons* (channel + (name 't-guix) + (url "https://github.com/TimotheeMathieu/t-guix") + (branch "main") + ) + {{ system_channels }}) diff --git a/guix_env/template_scripts/create_env.sh b/guix_env/template_scripts/create_env.sh new file mode 100644 index 0000000..ff63f73 --- /dev/null +++ b/guix_env/template_scripts/create_env.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env -S bash +# Must be run through launch_in_guix + +export POETRY_CACHE_DIR=${HOME}/.guix_env/poetry_cache +export POETRY_VIRTUALENVS_IN_PROJECT=true +export GUIX_ENV_NAME={{ name }} +export SHELL=$(realpath $(which zsh)) +export LD_LIBRARY_PATH=/lib +export ZDOTDIR=${HOME}/.guix_env/{{ name }}/bin +export TERM=ansi + +echo "Creating the environment" + +export REQUIREMENTS_FILE={{ requirements }} + +poetry config virtualenvs.prompt ' ' --local --directory={{ directory }} +poetry install --no-root --directory={{ directory }} + +if [[ ! -z $REQUIREMENTS_FILE ]]; then + cat $REQUIREMENTS_FILE | xargs poetry add --directory={{ directory }} +fi + +poetry completions zsh > {{ directory }}/_poetry diff --git a/guix_env/template_scripts/launch_in_guix.sh b/guix_env/template_scripts/launch_in_guix.sh new file mode 100644 index 0000000..1b3533c --- /dev/null +++ b/guix_env/template_scripts/launch_in_guix.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +guix time-machine --channels=${HOME}/.guix_env/{{ name }}/channels.scm -- shell {{ guix_args }} --preserve='(^DISPLAY$|^XAUTHORITY$|^TERM$)' --share=${HOME}/.guix_env --share=${HOME}/.guix_env/{{ name }}/.local=${HOME}/.local --share=/tmp --expose=/dev/dri --expose=/sys -m ${HOME}/.guix_env/{{ name }}/manifest.scm -- "$@" diff --git a/guix_env/template_scripts/launch_shell.sh b/guix_env/template_scripts/launch_shell.sh new file mode 100644 index 0000000..4352ce4 --- /dev/null +++ b/guix_env/template_scripts/launch_shell.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env -S bash +# Must be run through launch_in_guix + +{% if with_python %} +export POETRY_CACHE_DIR=${HOME}/.guix_env/poetry_cache +export POETRY_VIRTUALENVS_IN_PROJECT=true +export REQUIREMENTS_FILE={{ requirements }} + +{% else %} + +{% endif %} + +export GUIX_ENV_NAME={{ name }} +export SHELL=$(realpath $(which zsh)) +export LD_LIBRARY_PATH=/lib +export ZDOTDIR=${HOME}/.guix_env/{{ name }} +export TERM=ansi + +zsh diff --git a/guix_env/template_scripts/pyproject.toml b/guix_env/template_scripts/pyproject.toml new file mode 100644 index 0000000..e54c48a --- /dev/null +++ b/guix_env/template_scripts/pyproject.toml @@ -0,0 +1,11 @@ +[tool.poetry] +name = "guix-env-{{ name }}" +package-mode = false +description = "Dependency file for guix-env environment {{ name }}" + +[tool.poetry.dependencies] +python = "{{ python_version }}" +numpy = "*" +scipy = "*" +matplotlib = "*" +PyQt6 = "*" diff --git a/guix_env/template_scripts/run_script.sh b/guix_env/template_scripts/run_script.sh index 5e67ec0..5fb5d45 100644 --- a/guix_env/template_scripts/run_script.sh +++ b/guix_env/template_scripts/run_script.sh @@ -1,10 +1,22 @@ -#!/usr/bin/env -S guix time-machine --channels=${HOME}/.guix_env/{{ name }}/channels.scm -- shell {{ guix_args }} --preserve='(^DISPLAY$|^XAUTHORITY$|^TERM$)' --share=${HOME} --expose=/dev/dri --expose=/sys -m ${HOME}/.guix_env/{{ name }}/manifest.scm -- bash +#!/usr/bin/env -S zsh +# Must be run through launch_in_guix -# Link the lib file for FHS library handling -export LD_LIBRARY_PATH=/lib -rm ${HOME}/.guix_env/guix_env_venv/{{ name }}_venv/bin/python3 -ln -s $(which python3) ${HOME}/.guix_env/guix_env_venv/{{ name }}_venv/bin/python3 -source ${HOME}/.guix_env/guix_env_venv/{{ name }}_venv/bin/activate +export GUIX_ENV_NAME={{ name }} +export SHELL=$(realpath $(which zsh)) +export LD_LIBRARY_PATH=/lib # Link the lib file for FHS library handling +export ZDOTDIR=${HOME}/.guix_env/{{ name }}/bin +export TERM=ansi +{% if with_python %} +export POETRY_CACHE_DIR=${HOME}/.guix_env/poetry_cache +export POETRY_VIRTUALENVS_IN_PROJECT=true +. $HOME/.guix_env/{{ name }}/.venv/bin/activate +. $HOME/.guix_env/{{ name }}/.zshrc + +$@ + +{% else %} $@ + +{% endif %} diff --git a/guix_env/template_scripts/zshrc b/guix_env/template_scripts/zshrc index e485182..c0a44a1 100644 --- a/guix_env/template_scripts/zshrc +++ b/guix_env/template_scripts/zshrc @@ -1,6 +1,8 @@ # -*- mode: shell-script -*- -autoload -U compinit promptinit +# completions +fpath+=~/.guix_env/{{ name }} +autoload -Uz compinit promptinit compinit promptinit @@ -27,6 +29,8 @@ bindkey "\e[1~" beginning-of-line # Home bindkey "\e[4~" end-of-line # End bindkey "\e[7~" beginning-of-line # Home bindkey "\e[8~" end-of-line # End +bindkey "\eOF" end-of-line +bindkey "\eOH" beginning-of-line bindkey "^E" end-of-line bindkey "^A" beginning-of-line @@ -34,6 +38,16 @@ autoload -U colors && colors alias ls='ls --color' +PROMPT="%{$fg[green]%}%n@{{ name }}-guix-env %T %~ +%{$reset_color%}%# >" +HISTFILE="$HOME/.guix_env/{{ name }}/.zhistory" + +{% if with_python %} + +. $HOME/.guix_env/{{ name }}/.venv/bin/activate + + + REQFILE={{ reqfile }} pip() @@ -45,5 +59,17 @@ pip() fi } -PROMPT="%{$fg[green]%}%n@{{ name }}-guix-env %T %~ -%{$reset_color%}%# >" +gep() # guix-env poetry inside guix-env, used to add packages to the guix-env virtual env. +{ + cd $HOME/.guix_env/{{ name }} + command poetry "$@" + cd - +} + +{% else %} + +{% endif %} + + + +