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")