linkedin / linkedin/shiv

Allow shebang with %ENVIRONMENT VARIABLES% in it

Open
#209 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.9k
Forks
114
PR merge metrics
No merged PRs in 30d

Description

I am using the shebang to specify the specific version of the intepreter to be used (3.8, 3.9, ...) knowing that the user's laptop that will run the pyz have the different versions installed as "%USERPROFILE%\AppData\Local\Programs\Python\PythonNN".
Hence I would need the shebang line to accept the environment variable USERPROFILE.

The current shebang line does not allow this and shiv will "expand_user" the python interpreter (for '~').

The workaround I found (on windows) is to use a shebang line looking like
cmd.exe /C call "%USERPROFILE%\AppData\Local\Programs\Python\Python38\python.exe"

However, specifying such an argument via the CLI is cumbersome:

  1. one need to remember this specific shebang construct
  2. the %USERPROFILE% will be interpreted directly by the shell and so will come to python already replaced.

What would be nice is to be able to specify a python interpreter like "$USERPROFILE$\AppData\Local\Programs\Python\Python38\python.exe" and have shiv detect the "$" and replace the intepreter by the ad-hoc shebang line with the $ replaced by %.

In the general case, when the interpreter path includes the %USERPROFILE% string, it could even be nice to automatically replace it by the shebang with the dynamic $USERPROFILE$ (this could be disabled through an option).

The two logics are implemented and illustrated here below:

import os
from pathlib import Path


def replace_userprofile(python):
    r"""convert a path string with "C:\Users\name-of-user\AppData\Local\Programs\Python\Python38\python.exe" to
    the path string $USERPROFILE$\AppData\Local\Programs\Python\Python38\python.exe
    """
    if (user_profile := os.environ.get("USERPROFILE")) :
        try:
            return str("$USERPROFILE$" / Path(python).relative_to(Path(user_profile)))
        except ValueError:
            return python


def build_shebang(python):
    # handle $ENVIRONMENT_VARIABLES$ in python path (use an trampoline shebang)
    if "$" in python:
        # if the python path contains a $, we replace it by % and use a cmd.exe shebang to trampoline to python
        return f'cmd.exe /C call "{python.replace("$", "%")}"'
    else:
        return python


if __name__ == "__main__":
    for python in [
        r"C:\Users\GFJ138\AppData\Local\Programs\Python\Python38\python.exe",
        r"$USERPROFILE$\AppData\Local\Programs\Python\Python38\python.exe",
        r"C:\Program Files\Python\Python38\python.exe",
    ]:
        print(
            f"Original interpreter: {python}\n"
            f"Dynamic interpreter:  {replace_userprofile(python)}\n"
            f"Shebang:              {build_shebang(replace_userprofile(python))}\n"
        )

which outputs (given USERPROFILE=C:\Users\JohnDoe):

Original interpreter: C:\Users\JohnDoe\AppData\Local\Programs\Python\Python38\python.exe
Dynamic interpreter:  $USERPROFILE$\AppData\Local\Programs\Python\Python38\python.exe
Shebang:              cmd.exe /C call "%USERPROFILE%\AppData\Local\Programs\Python\Python38\python.exe"

Original interpreter: $USERPROFILE$\AppData\Local\Programs\Python\Python38\python.exe
Dynamic interpreter:  $USERPROFILE$\AppData\Local\Programs\Python\Python38\python.exe
Shebang:              cmd.exe /C call "%USERPROFILE%\AppData\Local\Programs\Python\Python38\python.exe"

Original interpreter: C:\Program Files\Python\Python38\python.exe
Dynamic interpreter:  C:\Program Files\Python\Python38\python.exe
Shebang:              C:\Program Files\Python\Python38\python.exe

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the CLI path that accepts the Python interpreter and trace the existing shebang handling and expand_user behavior. Decide how explicit and automatically detected environment-variable paths should be represented on Windows, then verify that generated pyz launchers preserve the requested interpreter path without shell pre-expansion.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.