indygreg / python-build-standalone

Produce redistributable builds of Python
BSD 3-Clause "New" or "Revised" License
1.71k stars 107 forks source link

Scripts in install/bin break if symlinked #187

Open sabiroid opened 9 months ago

sabiroid commented 9 months ago

Latest build (cpython-3.8.17+20230826-x86_64-apple-darwin-pgo+lto-full) Scripts in install/bin directory (e.g. pip) find interpreter by looking in the same directory as $0. Meanwhile, if such a script gets symlinked and a symlink is executed, $0 points to the symlink instead of its destination. (This is true for the chain of symlinks as well, each symlink in a chan will point at itself with $0.)

Because of that, symlinks of the scripts try to run the Python binary colocated with the symlink itself (which may not exist or may be a wrong Python distribution) rather than with the script behind a symlink.

cipriancraciun commented 9 months ago

I've stumbled upon this issue myself, that is why I've written a small rust binary that when started resolves the executable (itself), canonicalizes that path (i.e. it can be any number of symlinks to it), and then executes the proper ./bin/python tool. (In my case it supports both Linux and Darwin installs side-by-side, but you can adjust for your needs.)

// rustc --crate-type bin -O -C target-feature=+crt-static -C relocation-model=static -o ./launcher--linux -- ./launcher.rs
// rustc --crate-type bin -O -o ./launcher--darwin -- ./launcher.rs

use std::{env, process, os::unix::process::CommandExt as _};

fn main () -> ! {

    let _executable = env::current_exe () .expect ("[10d63289]");
    let _executable = _executable.canonicalize () .expect ("[a0240181]");

    let _dirname = _executable.parent () .expect ("[4287ad94]");
    let _basename = _executable.file_name () .expect ("[ef7a04be]") .to_str () .expect ("[c31acf97]");
    let _target = match _basename {
        "launcher--linux" => "linux",
        "launcher--darwin" => "darwin",
        _ => panic! ("[d03e07b5] {}", _executable.display ()),
    };

    let _python = _dirname.join (_target) .join ("bin") .join ("python3.9");
    let _python = _python.canonicalize () .expect ("[ee9ea82d]");

    let _command = process::Command::new (_python) .args (env::args_os () .skip (1)) .exec ();
    unreachable! ("[54118a1e]");
}