| 
16 | 16 | 
 
  | 
17 | 17 | load("@bazel_skylib//lib:paths.bzl", "paths")  | 
18 | 18 | load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")  | 
 | 19 | +load(":common.bzl", "runfiles_root_path")  | 
19 | 20 | load(":py_exec_tools_info.bzl", "PyExecToolsInfo")  | 
20 | 21 | load(":sentinel.bzl", "SentinelInfo")  | 
21 | 22 | load(":toolchain_types.bzl", "TARGET_TOOLCHAIN_TYPE")  | 
@@ -82,24 +83,86 @@ See {obj}`PyExecToolsInfo.exec_interpreter` for further docs.  | 
82 | 83 |     },  | 
83 | 84 | )  | 
84 | 85 | 
 
  | 
 | 86 | +def relative_path(from_, to):  | 
 | 87 | +    """Compute a relative path from one path to another.  | 
 | 88 | +
  | 
 | 89 | +    Args:  | 
 | 90 | +        from_: {type}`str` the starting directory. Note that it should be  | 
 | 91 | +            a directory because relative-symlinks are relative to the  | 
 | 92 | +            directory the symlink resides in.  | 
 | 93 | +        to: {type}`str` the path that `from_` wants to point to  | 
 | 94 | +
  | 
 | 95 | +    Returns:  | 
 | 96 | +        {type}`str` a relative path  | 
 | 97 | +    """  | 
 | 98 | +    from_parts = from_.split("/")  | 
 | 99 | +    to_parts = to.split("/")  | 
 | 100 | + | 
 | 101 | +    # Strip common leading parts from both paths  | 
 | 102 | +    n = min(len(from_parts), len(to_parts))  | 
 | 103 | +    for _ in range(n):  | 
 | 104 | +        if from_parts[0] == to_parts[0]:  | 
 | 105 | +            from_parts.pop(0)  | 
 | 106 | +            to_parts.pop(0)  | 
 | 107 | +        else:  | 
 | 108 | +            break  | 
 | 109 | + | 
 | 110 | +    # Impossible to compute a relative path without knowing what ".." is  | 
 | 111 | +    if from_parts and from_parts[0] == "..":  | 
 | 112 | +        fail("cannot compute relative path from '%s' to '%s'", from_, to)  | 
 | 113 | + | 
 | 114 | +    parts = ([".."] * len(from_parts)) + to_parts  | 
 | 115 | +    return paths.join(*parts)  | 
 | 116 | + | 
85 | 117 | def _current_interpreter_executable_impl(ctx):  | 
86 | 118 |     toolchain = ctx.toolchains[TARGET_TOOLCHAIN_TYPE]  | 
87 | 119 |     runtime = toolchain.py3_runtime  | 
 | 120 | +    direct = []  | 
88 | 121 | 
 
  | 
89 | 122 |     # NOTE: We name the output filename after the underlying file name  | 
90 | 123 |     # because of things like pyenv: they use $0 to determine what to  | 
91 | 124 |     # re-exec. If it's not a recognized name, then they fail.  | 
92 | 125 |     if runtime.interpreter:  | 
93 |  | -        executable = ctx.actions.declare_file(runtime.interpreter.basename)  | 
94 |  | -        ctx.actions.symlink(output = executable, target_file = runtime.interpreter, is_executable = True)  | 
 | 126 | +        # Even though ctx.actions.symlink() could be used, we bump into the issue  | 
 | 127 | +        # with RBE where bazel is making a copy to the file instead of symlinking  | 
 | 128 | +        # to the hermetic toolchain repository. This means that we need to employ  | 
 | 129 | +        # a similar strategy to how the `py_executable` venv is created where the  | 
 | 130 | +        # file in the `runfiles` is a dangling symlink into the hermetic toolchain  | 
 | 131 | +        # repository. This smells like a bug in RBE, but I would not be surprised  | 
 | 132 | +        # if it is not one.  | 
 | 133 | + | 
 | 134 | +        # Create a dangling symlink in `bin/python3` to the real interpreter  | 
 | 135 | +        # in the hermetic toolchain.  | 
 | 136 | +        interpreter_basename = runtime.interpreter.basename  | 
 | 137 | +        executable = ctx.actions.declare_symlink("bin/" + interpreter_basename)  | 
 | 138 | +        direct.append(executable)  | 
 | 139 | +        interpreter_actual_path = runfiles_root_path(ctx, runtime.interpreter.short_path)  | 
 | 140 | +        target_path = relative_path(  | 
 | 141 | +            # dirname is necessary because a relative symlink is relative to  | 
 | 142 | +            # the directory the symlink resides within.  | 
 | 143 | +            from_ = paths.dirname(runfiles_root_path(ctx, executable.short_path)),  | 
 | 144 | +            to = interpreter_actual_path,  | 
 | 145 | +        )  | 
 | 146 | +        ctx.actions.symlink(output = executable, target_path = target_path)  | 
 | 147 | + | 
 | 148 | +        # Create a dangling symlink into the runfiles and use that as the  | 
 | 149 | +        # entry point.  | 
 | 150 | +        interpreter_actual_path = runfiles_root_path(ctx, executable.short_path)  | 
 | 151 | +        executable = ctx.actions.declare_symlink(interpreter_basename)  | 
 | 152 | +        target_path = interpreter_basename + ".runfiles/" + interpreter_actual_path  | 
 | 153 | +        ctx.actions.symlink(output = executable, target_path = target_path)  | 
95 | 154 |     else:  | 
96 |  | -        executable = ctx.actions.declare_symlink(paths.basename(runtime.interpreter_path))  | 
97 |  | -        ctx.actions.symlink(output = executable, target_path = runtime.interpreter_path)  | 
 | 155 | +        interpreter_basename = paths.basename(runtime.interpreter.interpreter_path)  | 
 | 156 | +        executable = ctx.actions.declare_symlink(interpreter_basename)  | 
 | 157 | +        direct.append(executable)  | 
 | 158 | +        target_path = runtime.interpreter_path  | 
 | 159 | +        ctx.actions.symlink(output = executable, target_path = target_path)  | 
 | 160 | + | 
98 | 161 |     return [  | 
99 | 162 |         toolchain,  | 
100 | 163 |         DefaultInfo(  | 
101 | 164 |             executable = executable,  | 
102 |  | -            runfiles = ctx.runfiles([executable], transitive_files = runtime.files),  | 
 | 165 | +            runfiles = ctx.runfiles(direct, transitive_files = runtime.files),  | 
103 | 166 |         ),  | 
104 | 167 |     ]  | 
105 | 168 | 
 
  | 
 | 
0 commit comments