6
6
import dataclasses
7
7
import json
8
8
import logging
9
+ import os
9
10
import shlex
10
11
from dataclasses import dataclass
11
12
from pathlib import PurePath
@@ -523,11 +524,18 @@ def _create_venv_script(
523
524
f"{ name } ={ shlex .quote (value )} "
524
525
for name , value in pex_environment .environment_dict (python_configured = True ).items ()
525
526
)
527
+ # Ensure that the pex is executed from a path relative to the shim script, so
528
+ # that running from within a working directory works properly.
529
+ # NB: No method for determining the path of the current script is entirely foolproof,
530
+ # but BASH_SOURCE works fine in our specific case (and has been available since bash-3.0,
531
+ # released in 2004, so it seems safe to assume it's available).
532
+ local_pex_bin = '"${BASH_SOURCE%/*}/"' + shlex .quote (self .pex .name )
526
533
target_venv_executable = shlex .quote (str (venv_executable ))
527
534
venv_dir = shlex .quote (str (self .venv_dir ))
528
535
execute_pex_args = " " .join (
529
- shlex .quote (arg )
530
- for arg in pex_environment .create_argv (self .pex .name , python = self .pex .python )
536
+ # Don't quote the BASH_SOURCE dereference in local_pex_bin.
537
+ arg if arg == local_pex_bin else shlex .quote (arg )
538
+ for arg in pex_environment .create_argv (local_pex_bin , python = self .pex .python )[1 :]
531
539
)
532
540
533
541
script = dedent (
@@ -693,6 +701,7 @@ class PexProcess:
693
701
description : str = dataclasses .field (compare = False )
694
702
level : LogLevel
695
703
input_digest : Digest | None
704
+ working_directory : str | None
696
705
extra_env : FrozenDict [str , str ] | None
697
706
output_files : tuple [str , ...] | None
698
707
output_directories : tuple [str , ...] | None
@@ -708,6 +717,7 @@ def __init__(
708
717
argv : Iterable [str ] = (),
709
718
level : LogLevel = LogLevel .INFO ,
710
719
input_digest : Digest | None = None ,
720
+ working_directory : str | None = None ,
711
721
extra_env : Mapping [str , str ] | None = None ,
712
722
output_files : Iterable [str ] | None = None ,
713
723
output_directories : Iterable [str ] | None = None ,
@@ -720,6 +730,7 @@ def __init__(
720
730
self .description = description
721
731
self .level = level
722
732
self .input_digest = input_digest
733
+ self .working_directory = working_directory
723
734
self .extra_env = FrozenDict (extra_env ) if extra_env else None
724
735
self .output_files = tuple (output_files ) if output_files else None
725
736
self .output_directories = tuple (output_directories ) if output_directories else None
@@ -731,7 +742,12 @@ def __init__(
731
742
@rule
732
743
async def setup_pex_process (request : PexProcess , pex_environment : SandboxPexEnvironment ) -> Process :
733
744
pex = request .pex
734
- argv = pex_environment .create_argv (pex .name , * request .argv , python = pex .python )
745
+ pex_bin = (
746
+ os .path .relpath (pex .name , request .working_directory )
747
+ if request .working_directory
748
+ else pex .name
749
+ )
750
+ argv = pex_environment .create_argv (pex_bin , * request .argv , python = pex .python )
735
751
env = {
736
752
** pex_environment .environment_dict (python_configured = pex .python is not None ),
737
753
** (request .extra_env or {}),
@@ -746,6 +762,7 @@ async def setup_pex_process(request: PexProcess, pex_environment: SandboxPexEnvi
746
762
description = request .description ,
747
763
level = request .level ,
748
764
input_digest = input_digest ,
765
+ working_directory = request .working_directory ,
749
766
env = env ,
750
767
output_files = request .output_files ,
751
768
output_directories = request .output_directories ,
@@ -764,6 +781,7 @@ class VenvPexProcess:
764
781
description : str = dataclasses .field (compare = False )
765
782
level : LogLevel
766
783
input_digest : Digest | None
784
+ working_directory : str | None
767
785
extra_env : FrozenDict [str , str ] | None
768
786
output_files : tuple [str , ...] | None
769
787
output_directories : tuple [str , ...] | None
@@ -779,6 +797,7 @@ def __init__(
779
797
argv : Iterable [str ] = (),
780
798
level : LogLevel = LogLevel .INFO ,
781
799
input_digest : Digest | None = None ,
800
+ working_directory : str | None = None ,
782
801
extra_env : Mapping [str , str ] | None = None ,
783
802
output_files : Iterable [str ] | None = None ,
784
803
output_directories : Iterable [str ] | None = None ,
@@ -791,6 +810,7 @@ def __init__(
791
810
self .description = description
792
811
self .level = level
793
812
self .input_digest = input_digest
813
+ self .working_directory = working_directory
794
814
self .extra_env = FrozenDict (extra_env ) if extra_env else None
795
815
self .output_files = tuple (output_files ) if output_files else None
796
816
self .output_directories = tuple (output_directories ) if output_directories else None
@@ -804,7 +824,12 @@ async def setup_venv_pex_process(
804
824
request : VenvPexProcess , pex_environment : SandboxPexEnvironment
805
825
) -> Process :
806
826
venv_pex = request .venv_pex
807
- argv = (venv_pex .pex .argv0 , * request .argv )
827
+ pex_bin = (
828
+ os .path .relpath (venv_pex .pex .argv0 , request .working_directory )
829
+ if request .working_directory
830
+ else venv_pex .pex .argv0
831
+ )
832
+ argv = (pex_bin , * request .argv )
808
833
input_digest = (
809
834
await Get (Digest , MergeDigests ((venv_pex .digest , request .input_digest )))
810
835
if request .input_digest
@@ -815,6 +840,7 @@ async def setup_venv_pex_process(
815
840
description = request .description ,
816
841
level = request .level ,
817
842
input_digest = input_digest ,
843
+ working_directory = request .working_directory ,
818
844
env = request .extra_env ,
819
845
output_files = request .output_files ,
820
846
output_directories = request .output_directories ,
0 commit comments