From e010c45ddb6295f3a130cf48a5733de2ec9e06d7 Mon Sep 17 00:00:00 2001 From: Hugues Morisset Date: Sat, 16 Mar 2024 18:36:57 +0100 Subject: [PATCH] Add 'buildlabel' function. This function facilitates the creation and modification of labels in bulk or for obtaining the full label. It also enables referencing other packages and subrepos relatively from the current one using '../' resolution. Examples: - `buildlabel(target=genrule(name="xxx", ...))` -> `["///subrepo//pkg:xxx"]` - `buildlabel([":a", ":b"], out="file")` -> `["///subrepo//pkg:a|file", "///subrepo//pkg:b|out"]` - `buildlabel(["subdir", "../uppkg"], target="build")` -> `["///subrepo//pkg/subdir:build", "///subrepo//uppkg:build"]` - `vis("*")` -> `["PUBLIC"]` - `vis("//anotherpkg/...")` -> `["/////anotherpkg/..."]` More examples can be found in the test file. --- rules/builtins.build_defs | 139 ++++++++++++++++++++++++++++++++++++++ test/buildlabel/BUILD | 107 +++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 test/buildlabel/BUILD diff --git a/rules/builtins.build_defs b/rules/builtins.build_defs index 87711fde18..0830b41a94 100644 --- a/rules/builtins.build_defs +++ b/rules/builtins.build_defs @@ -297,3 +297,142 @@ def is_semver(s:str) -> bool: def semver_check(version:str, constraint:str) -> bool: pass + + +# Returns build label +# https://github.com/thought-machine/please/blob/v17.3.1/src/core/build_label.go#L159 +# https://github.com/thought-machine/please/blob/6cc3d7689ad78c667618b5ca9d089630864cd140/src/core/build_input.go#L226 +def buildlabel( + label: str | list = "", + path: str = "", + target: str = "", + subtarget: str = "", + subrepo: str = "", + pkg: str = "", + package: str = "", + out: str = "", + name: str = "", + relative=False, + aslist=True, +): + """Compose a build label + + Args: + label (str|list): please label fallback into path if component cannot be guessed + path (str): classical UNIX path to package, last component can be '...' + target (str): target name, if empty will default to package name + subtarget (str): target named output + subrepo (str): subrepo name + pkg (str): alias path parameter + package (str): alias path parameter + out (str): alias subtarget parameter + name (str): alias target parameter + relative (bool): keep a relative path? + aslist (bool): return label in [] + Return: + (str): build label + """ + if isinstance(label, list): + res = map( + lambda l: buildlabel( + label=l, + path=path, + target=target, + subtarget=subtarget, + subrepo=subrepo, + pkg=pkg, + package=package, + out=out, + name=name, + relative=relative, + aslist=False, + ), + label, + ) + return res[0] if (len(res) == 1) and not aslist else res + + _res_alias = lambda als: reduce(lambda x, y: x if x else y, als) + _set_cnt = lambda l: reduce(lambda x, y: x + y, map(lambda x: 1 if x else 0, l), 0) + path = [path, pkg, package] + target = [target, name] + subtarget = [subtarget, out] + subrepo = [subrepo] + + if _set_cnt(path) > 1: + return fail("can only set one of [path, pkg, package]") + if _set_cnt(target) > 1: + return fail("can only set one of [target, name]") + if _set_cnt(subtarget) > 1: + return fail("can only set one of [subtarget, out]") + if _set_cnt(subrepo) > 1: + return fail("can only set one of [subrepo]") + + if label == "*": # special for vis + return ["PUBLIC"] if aslist else "PUBLIC" + + # split label + if label.startswith("///"): + label = "@" + label[3:] + if "|" in label: + label, lbl_subtarget = label.split("|") + subtarget += [lbl_subtarget] + if ":" in label: + label, lbl_target = label.split(":") + target += [lbl_target] + if "//" in label: + label, lbl_path = label.split("//") + path += [f"/{lbl_path}"] + if "@" in label: + label, lbl_subrepo = label.split("@") + subrepo += [f"/{lbl_subrepo}"] + path += [label] # reminder as path + + path = _res_alias(path) + target = _res_alias(target) + subtarget = _res_alias(subtarget) + subrepo = _res_alias(subrepo) + + _prefix = lambda pre, s: pre + s.removeprefix(pre) + _prefix_if = lambda pre, s: _prefix(pre, s) if s else "" + _globalize_rel_path = lambda p, glf: ( + p if p.startswith("/") else (glf() + _prefix("/", p)) + ) + _resolve_path_or_global = lambda p, glf: ( + "/".join(reduce(_resolve_path, _globalize_rel_path(p, glf).split("/"), [""])) + if p + else glf() + ) + + def _resolve_path(acc, p): + if p == ".": + return acc + elif p == "..": + return ( + acc[:-1] if len(acc) > 1 else acc + ) # len > 1 to avoid a please interpreter bug + else: + return acc + ([p] if p else []) + + path = path.replace("//", "/") + subrepo = subrepo.replace("///", "/") + + if not relative: + path = _resolve_path_or_global(path, get_base_path) + subrepo = _resolve_path_or_global(subrepo, subrepo_name) + + path = path.removeprefix("/") + subrepo = subrepo.removeprefix("/") + + subrepo = _prefix("///", subrepo) + path = _prefix("//", path) + target = _prefix_if(":", target) + subtarget = _prefix_if("|", subtarget) + + label = _prefix("///", subrepo + path + target + subtarget) + return [label] if aslist else label + + +# buildlabel aliases +bl = buildlabel +subdir = buildlabel +vis = buildlabel diff --git a/test/buildlabel/BUILD b/test/buildlabel/BUILD new file mode 100644 index 0000000000..4bbae354e9 --- /dev/null +++ b/test/buildlabel/BUILD @@ -0,0 +1,107 @@ +CONFIG.setdefault("TEST_ERRS", []) + + +def should(msg, a, b): + err = [f'should|{msg} ("{a}" !== "{b}")'] + if (isinstance(a, str) and not isinstance(b, str)) or (isinstance(a, list) and not isinstance(b, list)) or (isinstance(a, str) and a != b) or (isinstance(a, list) and not all([x == y for x, y in zip(a, b)])): + # not equal + CONFIG["TEST_ERRS"] += err + + +def assert_tests(): + if CONFIG["TEST_ERRS"]: + fail('\nvvvvvvv\n' + '\n'.join(CONFIG["TEST_ERRS"]) + '\n^^^^^^^\n\n') + + +bl_f = buildlabel +vis_f = buildlabel + +pkg_path = get_base_path() +def_target = basename(pkg_path) +sr_name = subrepo_name() + +## TESTS + +should("return components in correct position and form", ["///c//a:b|d"], bl_f(path='a', target='b', subrepo='c', subtarget='d', relative=True, aslist=True)) +should("return components in correct position and form", ["///c//a:b|d"], bl_f(path='//a', target=':b', subrepo='///c', subtarget='|d', relative=True, aslist=True)) + +should("return given target from root", "/////:all", bl_f(target='all', relative=True, aslist=False)) +should("return given target global", f"///{sr_name}//{pkg_path}:all", bl_f(target='all', relative=False, aslist=False)) +should("return current subrepo and package", f"///{sr_name}//{pkg_path}", bl_f(relative=False, aslist=False)) +should("return root target", f"/////:all", bl_f(subrepo="///", path="//", target='all', relative=False, aslist=False)) +should("return root target", f"/////:all", bl_f(subrepo="/", path="/", target='all', relative=False, aslist=False)) + +# path resolution + +path_append = lambda b, p: f'{b}/{p}' if b else p +sr_c = path_append(sr_name, 'c/c') +pkg_a = path_append(pkg_path, 'a/a') + +should("not resolve path when relative=True", f"///../b//../a", bl_f(subrepo='../b', path='../a', relative=True, aslist=False)) +should("not resolve path starting with /", "///c//a", bl_f(path='/a', subrepo='/c', relative=False, aslist=False)) +should("resolve path not starting with /", f"///{sr_c}//{pkg_a}", bl_f(path='a/a', subrepo='c/c', relative=False, aslist=False)) + +pkg_a = path_append(dirname(pkg_path), 'a/c') + +should("get parent directory with ../ and remove ./", f"/////{pkg_a}", bl_f(path='../a/b/./../c', subrepo="/", relative=False, aslist=False)) +should("get parent directory with ../ and remove ./", f"///a/c//", bl_f(subrepo='a/./b/../c', path="/", relative=False, aslist=False)) +should("get parent directory with ../ and remove ./", f"/////a/c", bl_f(path='/a/b/../c/.', relative=False, subrepo="/", aslist=False)) + +should("return path from root with lots of ../", f"/////a/c", bl_f(path='/../../../../../../../a/b/../c', subrepo="/", relative=False, aslist=False)) +should("return path from root with lots of ../", f"/////a/c", bl_f(path='../../../../../../../a/b/../c', subrepo="/", relative=False, aslist=False)) +should("return path from root with lots of ../", f"///a/c//", bl_f(subrepo='/../../../../../../../a/b/../c', path="/", relative=False, aslist=False)) +should("return path from root with lots of ../", f"///a/c//", bl_f(subrepo='../../../../../../../a/b/../c', path="/", relative=False, aslist=False)) + +pkg_c = path_append(pkg_path, 'a/c') + +should("return path from root with ./", f"/////{pkg_path}", bl_f(path='.', subrepo="/", relative=False, aslist=False)) +should("return path from root with ./", f"/////{pkg_c}", bl_f(path='./a/c', subrepo="/", relative=False, aslist=False)) +should("return path from root with ./", f"///{sr_c}//", bl_f(subrepo='./c/c', path="/", relative=False, aslist=False)) + +# defaults + +should("default aslist=True", ["///c//a:b|d"], bl_f(path='a', target='b', subrepo='c', subtarget='d', relative=True)) +should("default relative=False", "///c//a:b|d", bl_f(path='/a', target='b', subrepo='/c', subtarget='d', aslist=False)) +should("default arguments positions label,path,target,subtarget,subrepo", "///d//a:b|c", bl_f('', 'a', 'b', 'c', 'd', relative=True, aslist=False)) +should("default label", '///a//c:d|e', bl_f('///a//c:d|e', relative=True, aslist=False)) +should("default label", ['///a//c:d|e'], bl_f('///a//c:d|e', relative=True, aslist=True)) + +# aliases + +should("alias path with pkg", "///c//a:b|d", bl_f(pkg='a', target='b', subrepo='c', subtarget='d', relative=True, aslist=False)) +should("alias path with package", "///c//a:b|d", bl_f(package='a', target='b', subrepo='c', subtarget='d', relative=True, aslist=False)) +should("alias subtarget with out", "///c//a:b|d", bl_f(path='a', target='b', subrepo='c', out='d', relative=True, aslist=False)) +should("alias target with name", "///c//a:b|d", bl_f(path='a', name='b', subrepo='c', out='d', relative=True, aslist=False)) + +# should("fail when two alias are set", "///c//a:b|d", bl_f(path='a', pkg='n', target='b', subrepo='c', out='d', relative=True, aslist=False)) +# should("fail when two alias are set", "///c//a:b|d", bl_f(path='a', target='b', subrepo='c', subtarget='e', out='d', relative=True, aslist=False)) + +# label + +should("compose with first argument", "///a//b:c|d", bl_f('///a//b:c', out='d', relative=True, aslist=False)) +should("compose with first argument", "///a//b|d", bl_f('///a', path='b', out='d', relative=True, aslist=False)) +should("compose with first argument", f"///a//:x|d", bl_f('///a', target='x', out='d', relative=True, aslist=False)) +should("compose with first argument", f"///a//{pkg_path}:x|d", bl_f('///a', target='x', out='d', relative=False, aslist=False)) +should("compose with first argument", f"/////:x|d", bl_f(':r', target='x', out='d', relative=True, aslist=False)) +should("compose with first argument", f"/////r:x|d", bl_f('//r', target='x', out='d', relative=True, aslist=False)) + +# list + +should("accept list on first argument [1] aslist=False", "///a//b:c|d", bl_f(['///a//b:c'], out='d', relative=True, aslist=False)) +should("accept list on first argument [1] aslist=True", ["///a//b:c|d"], bl_f(['///a//b:c'], out='d', relative=True, aslist=True)) +should("accept list on first argument [2] aslist=False", ["///a//b:c|d", "///a//b:n|d"], bl_f(['///a//b:c', '///a//b:n'], out='d', relative=True, aslist=False)) +should("accept list on first argument [2] aslist=True", ["///a//b:c|d", "///a//b:n|d"], bl_f(['///a//b:c', '///a//b:n'], out='d', relative=True, aslist=True)) +should("accept list on first argument [0] aslist=False", [], bl_f([], out='d', relative=True, aslist=False)) + +# vis + +should("return PUBLIC for *", ["PUBLIC"], vis_f('*', aslist=True)) +should("return PUBLIC for *", "PUBLIC", vis_f('*', aslist=False)) +should("keep ... for vis", "/////a/...", vis_f('a/...', relative=True, aslist=False)) +should("keep ... for vis", f"/////{pkg_path}/a/...", vis_f('a/...', relative=False, aslist=False)) +should("keep ... for vis", f"/////{pkg_path}/a/c/...", vis_f('a/b/../c/...', relative=False, aslist=False)) +should("keep ... for vis", "/////a/c/...", vis_f('/a/b/../c/...', relative=False, aslist=False)) + +assert_tests() + +# fail("TEST OK")