Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions python/private/py_executable_bazel.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,31 @@ def _create_zip_main(ctx, *, stage2_bootstrap, runtime_details, venv):
)
return output


# Return a relative path from one path to another, where both paths are each
# relative paths from a common root.
def relative_path(from_, to):
from_parts = from_.split("/")
to_parts = to.split("/")

# Strip common leading parts from both paths
# (no while loops in starlark :( )
n = min(len(from_parts), len(to_parts))
for _ in range(n):
if from_parts[0] == to_parts[0]:
from_parts.pop(0)
to_parts.pop(0)
else:
break

# Impossible to compute a relative path without knowing what ".." is
if from_parts and from_parts[0] == "..":
fail("cannot compute relative path from '%s' to '%s'", from_, to)

parts = ([".."] * len(from_parts)) + to_parts
return "/".join(parts)


# Create a venv the executable can use.
# For venv details and the venv startup process, see:
# * https://docs.python.org/3/library/venv.html
Expand All @@ -369,8 +394,8 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
# may choose to write what symlink() points to instead.
interpreter = ctx.actions.declare_symlink("{}/bin/{}".format(venv, py_exe_basename))
interpreter_actual_path = runtime.interpreter.short_path
parent = "/".join([".."] * (interpreter_actual_path.count("/") + 1))
rel_path = parent + "/" + interpreter_actual_path
venv_bin_dir = paths.dirname(interpreter.short_path)
rel_path = relative_path(from_=venv_bin_dir, to=interpreter_actual_path)
ctx.actions.symlink(output = interpreter, target_path = rel_path)
else:
py_exe_basename = paths.basename(runtime.interpreter_path)
Expand Down
4 changes: 4 additions & 0 deletions tests/bootstrap_impls/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,7 @@ sh_py_run_test(
sh_src = "sys_executable_inherits_sys_path_test.sh",
target_compatible_with = _SUPPORTS_BOOTSTRAP_SCRIPT,
)

load(":venv_relative_path_tests.bzl", "relative_path_test_suite")

relative_path_test_suite("relative_path_test")
121 changes: 121 additions & 0 deletions tests/bootstrap_impls/venv_relative_path_tests.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"Unit tests for yaml.bzl"

load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest")
load("//python/private:py_executable_bazel.bzl", "relative_path") # buildifier: disable=bzl-visibility

def _relative_path_test_impl(ctx):
env = unittest.begin(ctx)

# Basic test cases

asserts.equals(
env,
"../../c/d",
relative_path(
from_ = "a/b",
to = "c/d",
),
)

asserts.equals(
env,
"../../c/d",
relative_path(
from_ = "../a/b",
to = "../c/d",
),
)

asserts.equals(
env,
"../../../c/d",
relative_path(
from_ = "../a/b",
to = "../../c/d",
),
)

asserts.equals(
env,
"../../d",
relative_path(
from_ = "a/b/c",
to = "a/d",
),
)

asserts.equals(
env,
"d/e",
relative_path(
from_ = "a/b/c",
to = "a/b/c/d/e",
),
)

# Real examples

# external py_binary uses external python runtime
asserts.equals(
env,
"../../../../../rules_python~~python~python_3_9_x86_64-unknown-linux-gnu/bin/python3",
relative_path(
from_ = "../rules_python~/python/private/_py_console_script_gen_py.venv/bin",
to = "../rules_python~~python~python_3_9_x86_64-unknown-linux-gnu/bin/python3",
),
)

# internal py_binary uses external python runtime
asserts.equals(
env,
"../../../../rules_python~~python~python_3_9_x86_64-unknown-linux-gnu/bin/python3",
relative_path(
from_ = "test/version_default.venv/bin",
to = "../rules_python~~python~python_3_9_x86_64-unknown-linux-gnu/bin/python3",
),
)

# external py_binary uses internal python runtime
# asserts.equals(
# env,
# "???",
# relative_path(
# from_ = "../rules_python~/python/private/_py_console_script_gen_py.venv/bin",
# to = "python/python_3_9_x86_64-unknown-linux-gnu/bin/python3",
# ),
#)
# ^ TODO: Technically we can infer ".." to be the workspace name?

# internal py_binary uses internal python runtime
asserts.equals(
env,
"../../../python/python_3_9_x86_64-unknown-linux-gnu/bin/python3",
relative_path(
from_ = "scratch/main.venv/bin",
to = "python/python_3_9_x86_64-unknown-linux-gnu/bin/python3",
),
)

return unittest.end(env)

relative_path_test = unittest.make(
_relative_path_test_impl,
attrs = {},
)

def relative_path_test_suite(name):
unittest.suite(name, relative_path_test)