|
| 1 | +"""Module extension for generating third-party crates for use in bazel.""" |
| 2 | + |
| 3 | +load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") |
| 4 | +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") |
| 5 | +load("//crate_universe:defs.bzl", _crate_universe_crate = "crate") |
| 6 | +load("//crate_universe/private:crates_vendor.bzl", "CRATES_VENDOR_ATTRS", "generate_config_file", "generate_splicing_manifest") |
| 7 | +load("//crate_universe/private:generate_utils.bzl", "render_config") |
| 8 | +load("//crate_universe/private/module_extensions:cargo_bazel_bootstrap.bzl", "get_cargo_bazel_runner") |
| 9 | + |
| 10 | +def _generate_repo_impl(repo_ctx): |
| 11 | + for path, contents in repo_ctx.attr.contents.items(): |
| 12 | + repo_ctx.file(path, contents) |
| 13 | + |
| 14 | +_generate_repo = repository_rule( |
| 15 | + implementation = _generate_repo_impl, |
| 16 | + attrs = dict( |
| 17 | + contents = attr.string_dict(mandatory = True), |
| 18 | + ), |
| 19 | +) |
| 20 | + |
| 21 | +def _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg): |
| 22 | + cargo_lockfile = module_ctx.path(cfg.cargo_lockfile) |
| 23 | + tag_path = module_ctx.path(cfg.name) |
| 24 | + |
| 25 | + rendering_config = json.decode(render_config( |
| 26 | + regen_command = "Run 'cargo update [--workspace]'", |
| 27 | + )) |
| 28 | + config_file = tag_path.get_child("config.json") |
| 29 | + module_ctx.file( |
| 30 | + config_file, |
| 31 | + executable = False, |
| 32 | + content = generate_config_file( |
| 33 | + module_ctx, |
| 34 | + mode = "remote", |
| 35 | + annotations = {}, |
| 36 | + generate_build_scripts = cfg.generate_build_scripts, |
| 37 | + supported_platform_triples = cfg.supported_platform_triples, |
| 38 | + generate_target_compatible_with = True, |
| 39 | + repository_name = cfg.name, |
| 40 | + output_pkg = cfg.name, |
| 41 | + workspace_name = cfg.name, |
| 42 | + generate_binaries = cfg.generate_binaries, |
| 43 | + render_config = rendering_config, |
| 44 | + ), |
| 45 | + ) |
| 46 | + |
| 47 | + manifests = {module_ctx.path(m): m for m in cfg.manifests} |
| 48 | + splicing_manifest = tag_path.get_child("splicing_manifest.json") |
| 49 | + module_ctx.file( |
| 50 | + splicing_manifest, |
| 51 | + executable = False, |
| 52 | + content = generate_splicing_manifest( |
| 53 | + packages = {}, |
| 54 | + splicing_config = "", |
| 55 | + cargo_config = cfg.cargo_config, |
| 56 | + manifests = {str(k): str(v) for k, v in manifests.items()}, |
| 57 | + manifest_to_path = module_ctx.path, |
| 58 | + ), |
| 59 | + ) |
| 60 | + |
| 61 | + splicing_output_dir = tag_path.get_child("splicing-output") |
| 62 | + cargo_bazel([ |
| 63 | + "splice", |
| 64 | + "--output-dir", |
| 65 | + splicing_output_dir, |
| 66 | + "--config", |
| 67 | + config_file, |
| 68 | + "--splicing-manifest", |
| 69 | + splicing_manifest, |
| 70 | + "--cargo-lockfile", |
| 71 | + cargo_lockfile, |
| 72 | + ]) |
| 73 | + |
| 74 | + # Create a lockfile, since we need to parse it to generate spoke |
| 75 | + # repos. |
| 76 | + lockfile_path = tag_path.get_child("lockfile.json") |
| 77 | + module_ctx.file(lockfile_path, "") |
| 78 | + |
| 79 | + cargo_bazel([ |
| 80 | + "generate", |
| 81 | + "--cargo-lockfile", |
| 82 | + cargo_lockfile, |
| 83 | + "--config", |
| 84 | + config_file, |
| 85 | + "--splicing-manifest", |
| 86 | + splicing_manifest, |
| 87 | + "--repository-dir", |
| 88 | + tag_path, |
| 89 | + "--metadata", |
| 90 | + splicing_output_dir.get_child("metadata.json"), |
| 91 | + "--repin", |
| 92 | + "--lockfile", |
| 93 | + lockfile_path, |
| 94 | + ]) |
| 95 | + |
| 96 | + crates_dir = tag_path.get_child(cfg.name) |
| 97 | + _generate_repo( |
| 98 | + name = cfg.name, |
| 99 | + contents = { |
| 100 | + "BUILD.bazel": module_ctx.read(crates_dir.get_child("BUILD.bazel")), |
| 101 | + }, |
| 102 | + ) |
| 103 | + |
| 104 | + contents = json.decode(module_ctx.read(lockfile_path)) |
| 105 | + |
| 106 | + for crate in contents["crates"].values(): |
| 107 | + repo = crate["repository"] |
| 108 | + if repo == None: |
| 109 | + continue |
| 110 | + name = crate["name"] |
| 111 | + version = crate["version"] |
| 112 | + |
| 113 | + # "+" isn't valid in a repo name. |
| 114 | + crate_repo_name = "{repo_name}__{name}-{version}".format( |
| 115 | + repo_name = cfg.name, |
| 116 | + name = name, |
| 117 | + version = version.replace("+", "-"), |
| 118 | + ) |
| 119 | + |
| 120 | + build_file_content = module_ctx.read(crates_dir.get_child("BUILD.%s-%s.bazel" % (name, version))) |
| 121 | + if "Http" in repo: |
| 122 | + # Replicates functionality in repo_http.j2. |
| 123 | + repo = repo["Http"] |
| 124 | + http_archive( |
| 125 | + name = crate_repo_name, |
| 126 | + patch_args = repo.get("patch_args", None), |
| 127 | + patch_tool = repo.get("patch_tool", None), |
| 128 | + patches = repo.get("patches", None), |
| 129 | + remote_patch_strip = 1, |
| 130 | + sha256 = repo.get("sha256", None), |
| 131 | + type = "tar.gz", |
| 132 | + urls = [repo["url"]], |
| 133 | + strip_prefix = "%s-%s" % (crate["name"], crate["version"]), |
| 134 | + build_file_content = build_file_content, |
| 135 | + ) |
| 136 | + elif "Git" in repo: |
| 137 | + # Replicates functionality in repo_git.j2 |
| 138 | + repo = repo["Git"] |
| 139 | + kwargs = {} |
| 140 | + for k, v in repo["commitish"].items(): |
| 141 | + if k == "Rev": |
| 142 | + kwargs["commit"] = v |
| 143 | + else: |
| 144 | + kwargs[k.lower()] = v |
| 145 | + new_git_repository( |
| 146 | + name = crate_repo_name, |
| 147 | + init_submodules = True, |
| 148 | + patch_args = repo.get("patch_args", None), |
| 149 | + patch_tool = repo.get("patch_tool", None), |
| 150 | + patches = repo.get("patches", None), |
| 151 | + shallow_since = repo.get("shallow_since", None), |
| 152 | + remote = repo["remote"], |
| 153 | + build_file_content = build_file_content, |
| 154 | + strip_prefix = repo.get("strip_prefix", None), |
| 155 | + **kwargs |
| 156 | + ) |
| 157 | + else: |
| 158 | + fail("Invalid repo: expected Http or Git to exist for crate %s-%s, got %s" % (name, version, repo)) |
| 159 | + |
| 160 | +def _crate_impl(module_ctx): |
| 161 | + cargo_bazel = get_cargo_bazel_runner(module_ctx) |
| 162 | + all_repos = [] |
| 163 | + for mod in module_ctx.modules: |
| 164 | + local_repos = [] |
| 165 | + for cfg in mod.tags.from_cargo: |
| 166 | + if cfg.name in local_repos: |
| 167 | + fail("Defined two crate universes with the same name in the same MODULE.bazel file. Use the name tag to give them different names.") |
| 168 | + elif cfg.name in all_repos: |
| 169 | + fail("Defined two crate universes with the same name in different MODULE.bazel files. Either give one a different name, or use use_extension(isolate=True)") |
| 170 | + _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg) |
| 171 | + all_repos.append(cfg.name) |
| 172 | + local_repos.append(cfg.name) |
| 173 | + |
| 174 | +_from_cargo = tag_class( |
| 175 | + doc = "Generates a repo @crates from a Cargo.toml / Cargo.lock pair", |
| 176 | + attrs = dict( |
| 177 | + name = attr.string(doc = "The name of the repo to generate", default = "crates"), |
| 178 | + cargo_lockfile = CRATES_VENDOR_ATTRS["cargo_lockfile"], |
| 179 | + manifests = CRATES_VENDOR_ATTRS["manifests"], |
| 180 | + cargo_config = CRATES_VENDOR_ATTRS["cargo_config"], |
| 181 | + generate_binaries = CRATES_VENDOR_ATTRS["generate_binaries"], |
| 182 | + generate_build_scripts = CRATES_VENDOR_ATTRS["generate_build_scripts"], |
| 183 | + supported_platform_triples = CRATES_VENDOR_ATTRS["supported_platform_triples"], |
| 184 | + ), |
| 185 | +) |
| 186 | + |
| 187 | +crate = module_extension( |
| 188 | + implementation = _crate_impl, |
| 189 | + tag_classes = dict( |
| 190 | + from_cargo = _from_cargo, |
| 191 | + ), |
| 192 | +) |
0 commit comments