| """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(file): | |
| """ | |
| The relative path from bazel-out/[arch]/bin to the given File object | |
| Args: | |
| file: a `File` object | |
| Returns: | |
| The output relative path for the `File` | |
| """ | |
| if file.is_source: | |
| execroot = "../../../" | |
| else: | |
| execroot = "" | |
| if file.short_path.startswith("../"): | |
| path = "external/" + file.short_path[3:] | |
| else: | |
| path = file.short_path | |
| return execroot + path | |
| def _to_rlocation_path(ctx, file): | |
| """The rlocation path for a `File` | |
| This produces the same value as the `rlocationpath` predefined source/output path variable. | |
| From https://bazel.build/reference/be/make-variables#predefined_genrule_variables: | |
| > `rlocationpath`: The path a built binary can pass to the `Rlocation` function of a runfiles | |
| > library to find a dependency at runtime, either in the runfiles directory (if available) | |
| > or using the runfiles manifest. | |
| > This is similar to root path (a.k.a. [short_path](https://bazel.build/rules/lib/File#short_path)) | |
| > in that it does not contain configuration prefixes, but differs in that it always starts with the | |
| > name of the repository. | |
| > The rlocation path of a `File` in an external repository repo will start with `repo/`, followed by the | |
| > repository-relative path. | |
| > Passing this path to a binary and resolving it to a file system path using the runfiles libraries | |
| > is the preferred approach to find dependencies at runtime. Compared to root path, it has the | |
| > advantage that it works on all platforms and even if the runfiles directory is not available. | |
| Args: | |
| ctx: starlark rule execution context | |
| file: a `File` object | |
| Returns: | |
| The rlocationpath for the `File` | |
| """ | |
| if file.short_path.startswith("../"): | |
| return file.short_path[3:] | |
| else: | |
| return ctx.workspace_name + "/" + file.short_path | |
| def _to_repository_relative_path(file): | |
| """The repository relative path for a `File` | |
| This is the full runfiles path of a `File` excluding its workspace name. | |
| This differs from root path (a.k.a. [short_path](https://bazel.build/rules/lib/File#short_path)) and | |
| rlocation path as it does not include the repository name if the `File` is from an external repository. | |
| Args: | |
| file: a `File` object | |
| Returns: | |
| The repository relative path for the `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_output_relative_path = _to_output_relative_path, | |
| to_repository_relative_path = _to_repository_relative_path, | |
| to_rlocation_path = _to_rlocation_path, | |
| ) |