Vendor dependencies

Let's see how I like this workflow.
This commit is contained in:
John Doty 2022-12-19 08:27:18 -08:00
parent 34d1830413
commit 9c435dc440
7500 changed files with 1665121 additions and 99 deletions

View file

@ -0,0 +1,92 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//utils:utils.bzl", "expect")
_ROOT_SYMBOL = "//"
_TARGET_SYMBOL = ":"
_RECURSIVE_SYMBOL = "..."
_PATH_SYMBOL = "/"
_BuildTargetPatternKind = enum(
"single",
"package",
"recursive",
)
BuildTargetPattern = record(
kind = field(_BuildTargetPatternKind.type),
cell = field([str.type, None], None),
path = field(str.type),
name = field([str.type, None], None),
)
def parse_build_target_pattern(pattern: str.type) -> BuildTargetPattern.type:
expect(len(pattern) >= len(_ROOT_SYMBOL) + 1, "Invalid build target pattern, pattern too short: {}".format(pattern))
root_position = pattern.find(_ROOT_SYMBOL)
expect(root_position >= 0, "Invalid build target pattern, pattern should started with `{}` or a cell name followed by `{}`: ".format(_ROOT_SYMBOL, _ROOT_SYMBOL, pattern))
cell = None
if root_position > 0:
cell = pattern[0:root_position]
name = None
end_of_path_position = -1
if pattern.endswith(_TARGET_SYMBOL):
kind = _BuildTargetPatternKind("package")
end_of_path_position = len(pattern) - 1
elif pattern.endswith(_RECURSIVE_SYMBOL):
kind = _BuildTargetPatternKind("recursive")
end_of_path_position = len(pattern) - len(_RECURSIVE_SYMBOL) - 1
expect(pattern[end_of_path_position] == _PATH_SYMBOL, "Invalid build target pattern, `{}` should be preceded by a `{}`: {}".format(_RECURSIVE_SYMBOL, _PATH_SYMBOL, pattern))
else:
kind = _BuildTargetPatternKind("single")
end_of_path_position = pattern.rfind(_TARGET_SYMBOL)
if (end_of_path_position < 0):
# Pattern does not have a target delimiter and thus a target name
# Assume target name to be the same as the last component of the package
end_of_path_position = len(pattern)
start_of_package = pattern.rfind(_PATH_SYMBOL)
name = pattern[start_of_package + len(_PATH_SYMBOL):]
elif end_of_path_position < root_position:
fail("Invalid build target pattern, cell name should not contain `{}`: {}".format(_PATH_SYMBOL, pattern))
else:
name = pattern[end_of_path_position + len(_TARGET_SYMBOL):]
start_of_path_position = root_position + len(_ROOT_SYMBOL)
expect(pattern[start_of_path_position] != _PATH_SYMBOL, "Invalid build target pattern, path cannot start with `{}`: {}".format(_PATH_SYMBOL, pattern))
path = pattern[start_of_path_position:end_of_path_position]
expect(path.find(_ROOT_SYMBOL) < 0, "Invalid build target pattern, `{}` can only appear once: {}".format(_ROOT_SYMBOL, pattern))
expect(path.find(_RECURSIVE_SYMBOL) < 0, "Invalid build target pattern, `{}` can only appear once: {}".format(_RECURSIVE_SYMBOL, pattern))
expect(path.find(_TARGET_SYMBOL) < 0, "Invalid build target pattern, `{}` can only appear once: {}".format(_TARGET_SYMBOL, pattern))
expect(len(path) == 0 or path[-1:] != _PATH_SYMBOL, "Invalid build target pattern, path cannot end with `{}`: {}".format(_PATH_SYMBOL, pattern))
return BuildTargetPattern(kind = kind, cell = cell, path = path, name = name)
def label_matches_build_target_pattern(label: ["label", "target_label"], pattern: BuildTargetPattern.type) -> bool.type:
if pattern.cell and pattern.cell != label.cell:
return False
if pattern.kind == _BuildTargetPatternKind("single"):
return pattern.path == label.package and pattern.name == label.name
elif pattern.kind == _BuildTargetPatternKind("package"):
return pattern.path == label.package
elif pattern.kind == _BuildTargetPatternKind("recursive"):
path_pattern_length = len(pattern.path)
if path_pattern_length == 0:
# This is a recursive pattern of the cell: cell//...
return True
elif len(label.package) > path_pattern_length:
# pattern cell//package/... matches label cell//package/subpackage:target
return label.package.startswith(pattern.path + _PATH_SYMBOL)
else:
return pattern.path == label.package
else:
fail("Unknown build target pattern kind.")

View file

@ -0,0 +1,27 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(
"@prelude//utils:utils.bzl",
"expect",
)
_DEFAULT_FMT = "found different values for key \"{0}\": {} != {}"
def update_x(dst: {"_a": "_b"}, k: "_a", v: "_b", fmt = _DEFAULT_FMT):
p = dst.setdefault(k, v)
expect(p == v, fmt, k, p, v)
def merge_x(dst: {"_a": "_b"}, src: {"_a": "_b"}, fmt = _DEFAULT_FMT):
for k, v in src.items():
update_x(dst, k, v, fmt = fmt)
def flatten_x(ds: [{"_a": "_b"}], fmt = _DEFAULT_FMT) -> {"_a": "_b"}:
out = {}
for d in ds:
merge_x(out, d, fmt = fmt)
return out

View file

@ -0,0 +1,92 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//utils:utils.bzl", "expect")
def topo_sort(graph: {"_a": ["_a"]}) -> ["_a"]:
"""
Topo-sort the given graph.
"""
in_degrees = {node: 0 for node in graph}
rdeps = {node: [] for node in graph}
for node, deps in graph.items():
for dep in dedupe(deps):
in_degrees[node] += 1
rdeps[dep].append(node)
queue = []
for node, in_degree in in_degrees.items():
if in_degree == 0:
queue.append(node)
ordered = []
for _ in range(len(in_degrees)):
expect(len(queue) != 0, "cycle in graph detected in `topo_sort`")
node = queue.pop()
ordered.append(node)
for rdep in rdeps[node]:
in_degrees[rdep] -= 1
if in_degrees[rdep] == 0:
queue.append(rdep)
expect(not queue, "finished before processing nodes: {}".format(queue))
expect(len(ordered) == len(graph), "missing or duplicate nodes in sort")
return ordered
def breadth_first_traversal(
graph_nodes: {"_a": ["_a"]},
roots: ["_a"]) -> ["_a"]:
"""
Like `breadth_first_traversal_by` but the nodes are stored in the graph.
"""
def lookup(x):
return graph_nodes[x]
return breadth_first_traversal_by(graph_nodes, roots, lookup)
def breadth_first_traversal_by(
graph_nodes: {"_a": ""},
roots: ["_a"],
get_nodes_to_traverse_func) -> ["_a"]:
"""
Performs a breadth first traversal of `graph_nodes`, beginning
with the `roots` and queuing the nodes returned by`get_nodes_to_traverse_func`.
Returns a list of all visisted nodes.
get_nodes_to_traverse_func(node: '_a') -> ['_a']:
Starlark does not offer while loops, so this implementation
must make use of a for loop. We pop from the end of the queue
as a matter of performance.
"""
# Dictify for O(1) lookup
visited = {k: None for k in roots}
queue = visited.keys()
for _ in range(len(graph_nodes)):
if not queue:
break
node = queue.pop()
expect(node in graph_nodes, "Expected node {} in graph nodes", node)
nodes_to_visit = get_nodes_to_traverse_func(node)
for node in nodes_to_visit:
if node not in visited:
visited[node] = None
queue.append(node)
expect(not queue, "Expected to be done with graph traversal queue.")
return visited.keys()

View file

@ -0,0 +1,24 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
def by_platform(
platform_flavors: [str.type],
xs: [(str.type, "_a")]) -> ["_a"]:
"""
Resolve platform-flavor-specific parameters, given the list of platform
flavors to match against. Meant to mirror the usage of
`PatternMatchedCollection`s in v1 for `platform_*` parameters.
"""
res = []
for (dtype, deps) in xs:
for platform in platform_flavors:
if regex_match(dtype, platform):
res.append(deps)
return res

View file

@ -0,0 +1,86 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# A set is useful when the `dedupe` builtin is not applicable. Dedupe looks at
# identity of the value (some kind of pointer) rather than equality, so for
# example doesn't eliminate duplicates of the same string value obtained from
# different places:
#
# things = ["huh", "huh"]
# expect(len(dedupe(things)) == 2)
#
# huh = "huh"
# things = [huh, huh]
# expect(len(dedupe(things)) == 1)
#
# In contrast a set compares its entries for equality, not identity, and will
# never contain one entry equal to another entry.
#
# Example usage:
#
# things = set()
# for x in somewhere:
# things.add(x)
# return things.list()
# Name the record `set_record` to enable users to use `set` to intialize a set.
set_record = record(
_entries = field(dict.type),
list = field("function"),
# Adds the value to the set, returning whether the value existed in the set
add = field("function"),
# Removes the value if the value is in the set, returning whether the value existed in the set
remove = field("function"),
# Adds the values to the set, returning the values that were added
update = field("function"),
# Returns whether the value is in the set
contains = field("function"),
size = field("function"),
)
# For typing a set, you may use `set_type` or `set_record.type`, the former is
# encouraged to avoid leaking the underlying implementation.
set_type = set_record.type
def set() -> set_type:
self = None
def set_list():
return self._entries.keys()
def set_add(v: "") -> bool.type:
if self.contains(v):
return True
self._entries[v] = None
return False
def set_contains(v: "") -> bool.type:
return v in self._entries
def set_remove(v: "") -> bool.type:
if self.contains(v):
self._entries.pop(v)
return True
return False
def set_update(values: [""]) -> [""]:
return filter(None, [v for v in values if not self.add(v)])
def set_size() -> int.type:
return len(self._entries)
self = set_record(
_entries = {},
list = set_list,
add = set_add,
remove = set_remove,
update = set_update,
contains = set_contains,
size = set_size,
)
return self

View file

@ -0,0 +1,24 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# Utilities for checking and ignoring types
# Ignores the type, always returning "" (the wildcard type).
# Used where the type is true, but performance concerns preclude the type in normal operation.
#
# FIXME: Probably have a way to unconditionally enable such types, to ensure they remain accurate.
def unchecked(_):
return ""
# Assert that a given value has a specific type, and return that value.
# Fails at runtime if the value does not have the right type.
def cast(value, type):
def inner(_: type):
pass
inner(value)
return value

View file

@ -0,0 +1,76 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# General utilities shared between multiple rules.
def value_or(x: [None, "_a"], default: "_a") -> "_a":
return default if x == None else x
# Flatten a list of lists into a list
def flatten(xss: [["_a"]]) -> ["_a"]:
return [x for xs in xss for x in xs]
# Flatten a list of dicts into a dict
def flatten_dict(xss: [{"_a": "_b"}]) -> {"_a": "_b"}:
return {k: v for xs in xss for k, v in xs.items()}
# Fail if given condition is not met.
def expect(x: bool.type, msg: str.type = "condition not expected", *fmt):
if not x:
fmt_msg = msg.format(*fmt)
fail(fmt_msg)
def expect_non_none(val, msg: str.type = "unexpected none", *fmt_args, **fmt_kwargs):
"""
Require the given value not be `None`.
"""
if val == None:
fail(msg.format(*fmt_args, **fmt_kwargs))
return val
def from_named_set(srcs: [{str.type: ["artifact", "dependency"]}, [["artifact", "dependency"]]]) -> {str.type: ["artifact", "dependency"]}:
"""
Normalize parameters of optionally named sources to a dictionary mapping
names to sources, deriving the name from the short path when it's not
explicitly provided.
"""
if type(srcs) == type([]):
srcs_dict = {}
for src in srcs:
if type(src) == "artifact":
name = src.short_path
else:
# If the src is a `dependency`, use the short path of the
# default output.
expect(
len(src[DefaultInfo].default_outputs) == 1,
"expected exactly one default output from {} ({})"
.format(src, src[DefaultInfo].default_outputs),
)
[artifact] = src[DefaultInfo].default_outputs
name = artifact.short_path
srcs_dict[name] = src
return srcs_dict
else:
return srcs
def map_idx(key: "_a", vals: ["_b"]) -> ["_c"]:
return [x[key] for x in vals]
def filter_idx(key: "_a", vals: ["_b"]) -> ["_b"]:
return [x for x in vals if key in x]
def filter_and_map_idx(key: "_a", vals: ["_b"]) -> ["_c"]:
return [x[key] for x in vals if key in x]
def idx(x: ["_a", None], key: "_b") -> ["_c", None]:
return x[key] if x != None else None
# TODO(T127134666) remove this once we have a native function that does this
def dedupe_by_value(vals: ["_a"]) -> ["_a"]:
return {val: None for val in vals}.keys()