Skip to content

Commit b6dd781

Browse files
committed
Factor cargo workspace graph helpers
1 parent adb7877 commit b6dd781

4 files changed

Lines changed: 231 additions & 211 deletions

File tree

rs/extensions.bzl

Lines changed: 22 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ load("//rs/private:annotations.bzl", "annotation_for", "build_annotation_map", "
66
load("//rs/private:cargo_credentials.bzl", "load_cargo_credentials")
77
load(
88
"//rs/private:cargo_workspace_graph.bzl",
9+
"cargo_toml_fact",
910
"platform_label",
1011
"render_dep_data",
1112
"render_string_list",
1213
"resolve_cargo_workspace_members",
14+
"resolve_package_facts",
15+
"split_lockfile_packages",
1316
"workspace_dep_data",
14-
_add_to_dict = "add_to_dict",
1517
_fq_crate = "fq_crate",
16-
_new_feature_resolutions = "new_feature_resolutions",
1718
_normalize_path = "normalize_path",
18-
_prepare_possible_deps = "prepare_possible_deps",
1919
_select = "select_items",
2020
)
2121
load("//rs/private:crate_repository.bzl", "crate_repository", "local_crate_repository")
@@ -56,26 +56,6 @@ def _date(ctx, label):
5656
result = ctx.execute(["gdate", '+"%Y-%m-%d %H:%M:%S.%3N"'])
5757
print(label, result.stdout)
5858

59-
def _relative_to_workspace(path, workspace_root):
60-
normalized_root = _normalize_path(workspace_root)
61-
normalized_path = _normalize_path(path)
62-
63-
if not paths.is_absolute(normalized_path):
64-
normalized_path = _normalize_path(paths.normalize(paths.join(normalized_root, normalized_path)))
65-
66-
root_parts = [p for p in normalized_root.split("/") if p]
67-
path_parts = [p for p in normalized_path.split("/") if p]
68-
69-
common = 0
70-
max_common = min(len(root_parts), len(path_parts))
71-
for idx in range(max_common):
72-
if root_parts[idx] != path_parts[idx]:
73-
break
74-
common = idx + 1
75-
76-
rel_parts = [".."] * (len(root_parts) - common) + path_parts[common:]
77-
return "/".join(rel_parts) if rel_parts else "."
78-
7959
def _label_directory(label):
8060
idx = label.name.rfind("/")
8161
if idx == -1:
@@ -105,54 +85,6 @@ def _additive_build_file_content(mctx, annotation):
10585
content += annotation.additive_build_file_content
10686
return content
10787

108-
def _spec_to_dep_dict_inner(dep, spec, is_build = False):
109-
if type(spec) == "string":
110-
dep = {"name": dep}
111-
else:
112-
dep = {
113-
"name": dep,
114-
"optional": spec.get("optional", False),
115-
"default_features": spec.get("default_features", spec.get("default-features", True)),
116-
"features": spec.get("features", []),
117-
}
118-
if "package" in spec:
119-
dep["package"] = spec["package"]
120-
121-
if is_build:
122-
dep["kind"] = "build"
123-
124-
return dep
125-
126-
def _spec_to_dep_dict(dep, spec, annotation, workspace_cargo_toml_json, is_build = False):
127-
if type(spec) == "dict" and spec.get("workspace") == True:
128-
workspace = workspace_cargo_toml_json.get("workspace")
129-
if not workspace and annotation.workspace_cargo_toml != "Cargo.toml":
130-
fail("""
131-
132-
ERROR: `crate.annotation` for `{name}` has a `workspace_cargo_toml` pointing to a Cargo.toml without a `workspace` section. Please correct it in your MODULE.bazel!
133-
Make sure you point to the `Cargo.toml` of the workspace, not of `{name}`!”
134-
135-
""".format(name = annotation.crate))
136-
137-
inherited = _spec_to_dep_dict_inner(
138-
dep,
139-
workspace["dependencies"][dep],
140-
is_build,
141-
)
142-
143-
extra_features = spec.get("features")
144-
if extra_features:
145-
inherited["features"] = sorted(set(extra_features + inherited.get("features", [])))
146-
147-
if spec.get("optional"):
148-
inherited["optional"] = True
149-
150-
if spec.get("package"):
151-
inherited["package"] = spec["package"]
152-
153-
return inherited
154-
return _spec_to_dep_dict_inner(dep, spec, is_build)
155-
15688
def _generate_hub_and_spokes(
15789
mctx,
15890
hub_name,
@@ -203,79 +135,23 @@ def _generate_hub_and_spokes(
203135
existing_facts = getattr(mctx, "facts", {}) or {}
204136
facts = {}
205137

206-
workspace_root = _normalize_path(cargo_metadata["workspace_root"])
207-
workspace_root_prefix = workspace_root + "/"
208-
workspace_member_keys = {}
209-
for package in cargo_metadata["packages"]:
210-
workspace_member_keys[(package["name"], package["version"])] = True
211-
212-
dep_paths_by_name = {}
213-
for package in cargo_metadata["packages"]:
214-
for dep in package.get("dependencies", []):
215-
dep_path = dep.get("path")
216-
if dep_path:
217-
dep_paths_by_name[dep["name"]] = _relative_to_workspace(dep_path, workspace_root)
218-
219-
patch_paths_by_name = {}
220-
for registry_patches in workspace_cargo_toml_json.get("patch", {}).values():
221-
for name, spec in registry_patches.items():
222-
if type(spec) != "dict":
223-
continue
224-
225-
patch_path = spec.get("path")
226-
if not patch_path:
227-
continue
228-
229-
if patch_path.startswith("/"):
230-
normalized = _normalize_path(patch_path)
231-
if not normalized.startswith(workspace_root_prefix):
232-
fail("Patch path for %s points outside the workspace: %s" % (name, patch_path))
233-
rel_patch_path = normalized.removeprefix(workspace_root_prefix)
234-
else:
235-
rel_patch_path = _normalize_path(paths.normalize(patch_path))
236-
237-
patch_paths_by_name[name] = rel_patch_path
238-
239-
workspace_members = []
240-
packages = []
241-
242-
for package in all_packages:
243-
pkg = dict(package)
244-
245-
if pkg.get("source"):
246-
packages.append(pkg)
247-
continue
248-
249-
key = (pkg["name"], pkg["version"])
250-
if key in workspace_member_keys:
251-
workspace_members.append(pkg)
252-
continue
253-
254-
rel_path = patch_paths_by_name.get(pkg["name"]) or dep_paths_by_name.get(pkg["name"])
255-
local_path = rel_path
256-
if rel_path and not rel_path.startswith("/"):
257-
local_path = paths.join(workspace_root, rel_path)
258-
259-
if not local_path:
260-
fail("Found a path dependency on %s %s but could not determine its path from Cargo.toml. Please declare it in [patch] or as a path dependency." % (pkg["name"], pkg["version"]))
261-
262-
pkg["source"] = "path+" + hub_name + "/" + rel_path
263-
pkg["local_path"] = local_path
264-
packages.append(pkg)
138+
split_packages = split_lockfile_packages(
139+
hub_name,
140+
cargo_metadata,
141+
workspace_cargo_toml_json,
142+
all_packages,
143+
)
144+
packages = split_packages.packages
145+
workspace_members = split_packages.workspace_members
265146

266147
mctx.report_progress("Computing dependencies and features")
267148

268-
feature_resolutions_by_fq_crate = dict()
269-
270-
versions_by_name = dict()
271-
for package_index in range(len(packages)):
272-
package = packages[package_index]
149+
facts_by_fq_crate = {}
150+
for package in packages:
273151
name = package["name"]
274152
version = package["version"]
275153
source = package["source"]
276154

277-
_add_to_dict(versions_by_name, name, version)
278-
279155
if source.startswith("sparse+"):
280156
key = name + "_" + version
281157
fact = existing_facts.get(key)
@@ -332,26 +208,7 @@ def _generate_hub_and_spokes(
332208
mctx.watch(mctx.path(cargo_toml_path))
333209
annotation = annotation_for(annotations, name, package["version"])
334210
cargo_toml_json = run_toml2json(mctx, cargo_toml_path)
335-
336-
dependencies = [
337-
_spec_to_dep_dict(dep, spec, annotation, {})
338-
for dep, spec in cargo_toml_json.get("dependencies", {}).items()
339-
] + [
340-
_spec_to_dep_dict(dep, spec, annotation, {}, is_build = True)
341-
for dep, spec in cargo_toml_json.get("build-dependencies", {}).items()
342-
]
343-
344-
for target, value in cargo_toml_json.get("target", {}).items():
345-
for dep, spec in value.get("dependencies", {}).items():
346-
converted = _spec_to_dep_dict(dep, spec, annotation, {})
347-
converted["target"] = target
348-
dependencies.append(converted)
349-
350-
fact = dict(
351-
features = cargo_toml_json.get("features", {}),
352-
dependencies = dependencies,
353-
strip_prefix = "",
354-
)
211+
fact = cargo_toml_fact(cargo_toml_json, {})
355212

356213
facts[key] = json.encode(fact)
357214
package["strip_prefix"] = fact.get("strip_prefix", "")
@@ -375,28 +232,10 @@ def _generate_hub_and_spokes(
375232
package_workspace_cargo_toml_json = package.get("workspace_cargo_toml_json")
376233
strip_prefix = package.get("strip_prefix", "")
377234

378-
dependencies = [
379-
_spec_to_dep_dict(dep, spec, annotation, package_workspace_cargo_toml_json)
380-
for dep, spec in cargo_toml_json.get("dependencies", {}).items()
381-
] + [
382-
_spec_to_dep_dict(dep, spec, annotation, package_workspace_cargo_toml_json, is_build = True)
383-
for dep, spec in cargo_toml_json.get("build-dependencies", {}).items()
384-
]
385-
386-
for target, value in cargo_toml_json.get("target", {}).items():
387-
for dep, spec in value.get("dependencies", {}).items():
388-
converted = _spec_to_dep_dict(dep, spec, annotation, package_workspace_cargo_toml_json)
389-
converted["target"] = target
390-
dependencies.append(converted)
391-
392-
if not dependencies and debug:
393-
print(name, version, package["source"])
235+
fact = cargo_toml_fact(cargo_toml_json, package_workspace_cargo_toml_json, strip_prefix = strip_prefix)
394236

395-
fact = dict(
396-
features = cargo_toml_json.get("features", {}),
397-
dependencies = dependencies,
398-
strip_prefix = strip_prefix,
399-
)
237+
if not fact["dependencies"] and debug:
238+
print(name, version, package["source"])
400239

401240
# Nest a serialized JSON since max path depth is 5.
402241
facts[key] = json.encode(fact)
@@ -405,11 +244,11 @@ def _generate_hub_and_spokes(
405244
else:
406245
fail("Unknown source %s for crate %s" % (source, name))
407246

408-
possible_features = fact["features"]
409-
possible_deps = _prepare_possible_deps(fact["dependencies"])
410-
feature_resolutions = _new_feature_resolutions(package_index, possible_deps, possible_features, platform_triples)
411-
package["feature_resolutions"] = feature_resolutions
412-
feature_resolutions_by_fq_crate[_fq_crate(name, version)] = feature_resolutions
247+
facts_by_fq_crate[_fq_crate(name, version)] = fact
248+
249+
resolved_facts = resolve_package_facts(packages, facts_by_fq_crate, platform_triples)
250+
feature_resolutions_by_fq_crate = resolved_facts.feature_resolutions_by_fq_crate
251+
versions_by_name = resolved_facts.versions_by_name
413252

414253
# Only files in the current Bazel workspace can/should be watched, so check where our manifests are located.
415254
watch_manifests = cargo_lock_path.repo_name == ""

0 commit comments

Comments
 (0)