mirror of
https://github.com/bazel-contrib/bazel-lib
synced 2024-11-27 17:43:27 +00:00
63ae772087
* fix: relative_file fix when to path is itself relative starting with ../ * feat: Allow users to use ../ in paths and add tests * fix: Clarify documentation Co-authored-by: Greg Magolan <gmagolan@gmail.com>
121 lines
4 KiB
Python
121 lines
4 KiB
Python
"""Path utils built on top of Skylib's path utils"""
|
|
|
|
load("@bazel_skylib//lib:paths.bzl", _spaths = "paths")
|
|
|
|
def _relative_file(to_file, frm_file):
|
|
"""Resolves a relative path between two files, "to_file" and "frm_file".
|
|
|
|
If neither of the paths begin with ../ it is assumed that they share the same root. When finding the relative path,
|
|
the incoming files are treated as actual files (not folders) so the resulting relative path may differ when compared
|
|
to passing the same arguments to python's "os.path.relpath()" or NodeJs's "path.relative()".
|
|
|
|
For example, 'relative_file("../foo/foo.txt", "bar/bar.txt")' will return '../../foo/foo.txt'
|
|
|
|
Args:
|
|
to_file: the path with file name to resolve to, from frm
|
|
frm_file: the path with file name to resolve from
|
|
|
|
Returns:
|
|
The relative path from frm_file to to_file, including the file name
|
|
"""
|
|
|
|
to_parent_count = to_file.count("../")
|
|
frm_parent_count = frm_file.count("../")
|
|
|
|
parent_count = to_parent_count
|
|
|
|
if to_parent_count > 0 and frm_parent_count > 0:
|
|
if frm_parent_count > to_parent_count:
|
|
fail("traversing more parent directories (with '../') for 'frm_file' than 'to_file' requires file layout knowledge")
|
|
|
|
parent_count = to_parent_count - frm_parent_count
|
|
|
|
to_segments = _spaths.normalize(_spaths.join("/", to_file)).split("/")[:-1]
|
|
frm_segments = _spaths.normalize(_spaths.join("/", frm_file)).split("/")[:-1]
|
|
|
|
if len(to_segments) == 0 and len(frm_segments) == 0:
|
|
return to_file
|
|
|
|
# since we prefix a "/" and normalize, the first segment is always "". So split point will be at least 1.
|
|
split_point = 1
|
|
|
|
# If either of the paths starts with ../ then assume that any shared paths are a coincidence.
|
|
if to_segments[0] != ".." and frm_segments != "..":
|
|
longest_common = []
|
|
for to_seg, frm_seg in zip(to_segments, frm_segments):
|
|
if to_seg == frm_seg:
|
|
longest_common.append(to_seg)
|
|
else:
|
|
break
|
|
|
|
split_point = len(longest_common)
|
|
|
|
return _spaths.join(
|
|
*(
|
|
[".."] * (len(frm_segments) - split_point + parent_count) +
|
|
to_segments[split_point:] +
|
|
[_spaths.basename(to_file)]
|
|
)
|
|
)
|
|
|
|
def _to_output_relative_path(f):
|
|
"The relative path from bazel-out/[arch]/bin to the given File object"
|
|
if f.is_source:
|
|
execroot = "../../../"
|
|
else:
|
|
execroot = ""
|
|
if f.short_path.startswith("../"):
|
|
path = "external/" + f.short_path[3:]
|
|
else:
|
|
path = f.short_path
|
|
return execroot + path
|
|
|
|
def _to_manifest_path(ctx, file):
|
|
"""The runfiles manifest entry path for a file
|
|
|
|
This is the full runfiles path of a file including its workspace name as
|
|
the first segment. We refert to it as the manifest path as it is the path
|
|
flavor that is used for in the runfiles MANIFEST file.
|
|
|
|
We must avoid using non-normalized paths (workspace/../other_workspace/path)
|
|
in order to locate entries by their key.
|
|
|
|
Args:
|
|
ctx: starlark rule execution context
|
|
file: a File object
|
|
|
|
Returns:
|
|
The runfiles manifest entry path for a file
|
|
"""
|
|
|
|
if file.short_path.startswith("../"):
|
|
return file.short_path[3:]
|
|
else:
|
|
return ctx.workspace_name + "/" + file.short_path
|
|
|
|
def _to_workspace_path(file):
|
|
"""The workspace relative path for a file
|
|
|
|
This is the full runfiles path of a file excluding its workspace name.
|
|
This differs from root path and manifest path as it does not include the
|
|
repository name if the file is from an external repository.
|
|
|
|
Args:
|
|
file: a File object
|
|
|
|
Returns:
|
|
The workspace relative path for a file
|
|
"""
|
|
|
|
if file.short_path.startswith("../"):
|
|
return "/".join(file.short_path.split("/")[2:])
|
|
else:
|
|
return file.short_path
|
|
|
|
paths = struct(
|
|
relative_file = _relative_file,
|
|
to_manifest_path = _to_manifest_path,
|
|
to_output_relative_path = _to_output_relative_path,
|
|
to_workspace_path = _to_workspace_path,
|
|
)
|