blob: 9186c2534df89d9642b09ba2239477afb725c5c3 [file] [log] [blame]
Tim Windelschmidtf5c45102025-02-08 22:30:58 +00001load(
2 "@bazel_tools//tools/build_defs/repo:utils.bzl",
3 "patch",
4 "update_attrs",
5 "workspace_and_buildfile",
6)
7
8def _build_archive_url(owner, repo, ref):
9 return "https://github.com/{owner}/{repo}/archive/{ref}.tar.gz".format(
10 owner = owner,
11 repo = repo,
12 ref = ref,
13 ), "{repo}-{ref}".format(repo = repo, ref = ref)
14
15def build_submodule_info_url(owner, repo, submodule, ref):
16 return "https://api.github.com/repos/{owner}/{repo}/contents/{submodule}?ref={ref}".format(
17 owner = owner,
18 repo = repo,
19 submodule = submodule,
20 ref = ref,
21 )
22
23def parse_github_url(url):
24 url = url.removeprefix("https://github.com/")
25 url = url.removesuffix(".git")
26 (owner, repo) = url.split("/")
27 return owner, repo
28
29def _github_repository(ctx):
30 base_repo_archive_url, base_repo_archive_prefix = _build_archive_url(
31 owner = ctx.attr.owner,
32 repo = ctx.attr.repo,
33 ref = ctx.attr.ref,
34 )
35
36 base_repo_download_info = ctx.download_and_extract(
37 url = base_repo_archive_url,
38 stripPrefix = base_repo_archive_prefix,
39 integrity = ctx.attr.integrity,
40 type = "tar.gz",
41 )
42
43 for submodule, integrity in ctx.attr.submodules.items():
Tim Windelschmidtdb085222025-04-28 20:58:18 +020044 if submodule not in ctx.attr.submodule_info:
45 url = build_submodule_info_url(
46 owner = ctx.attr.owner,
47 repo = ctx.attr.repo,
48 ref = ctx.attr.ref,
49 submodule = submodule,
50 )
Tim Windelschmidtf5c45102025-02-08 22:30:58 +000051
Tim Windelschmidtdb085222025-04-28 20:58:18 +020052 submodule_info_path = submodule + ".submodule_info"
53 ctx.download(
54 url = url,
55 headers = {
56 "Accept": "application/vnd.github+json",
57 "X-GitHub-Api-Version": "2022-11-28",
58 },
59 output = submodule_info_path,
60 )
Tim Windelschmidtf5c45102025-02-08 22:30:58 +000061
Tim Windelschmidtdb085222025-04-28 20:58:18 +020062 submodule_info = json.decode(ctx.read(submodule_info_path))
63
64 # buildifier: disable=print
65 print("Missing submodule_info for submodule %s. Consider adding it: \n%s" % (submodule, submodule_info))
66 else:
67 submodule_info = json.decode(ctx.attr.submodule_info[submodule])
68
Tim Windelschmidtf5c45102025-02-08 22:30:58 +000069 if submodule_info["type"] != "submodule":
70 fail("provided submodule path is not a submodule")
71
72 submodule_owner, submodule_repo = parse_github_url(
73 url = submodule_info["submodule_git_url"],
74 )
75
76 submodule_url, submodule_strip_prefix = _build_archive_url(
77 owner = submodule_owner,
78 repo = submodule_repo,
79 ref = submodule_info["sha"],
80 )
81
82 download_info = ctx.download_and_extract(
83 url = submodule_url,
84 stripPrefix = submodule_strip_prefix,
85 integrity = integrity,
86 type = "tar.gz",
87 output = submodule_info["path"],
88 )
89 if integrity == "":
90 # buildifier: disable=print
Tim Windelschmidtdb085222025-04-28 20:58:18 +020091 print("Missing integrity for submodule {submodule}. Consider adding it:\n\"{submodule}\": \"{integrity}\".".format(
Tim Windelschmidtf5c45102025-02-08 22:30:58 +000092 submodule = submodule,
93 integrity = download_info.integrity,
94 ))
95
96 workspace_and_buildfile(ctx)
97
98 patch(ctx)
99
100 return update_attrs(ctx.attr, _github_repository_attrs.keys(), {"integrity": base_repo_download_info.integrity})
101
102_github_repository_attrs = {
103 "owner": attr.string(
104 mandatory = True,
105 doc = "The Owner of the Github repository",
106 ),
107 "repo": attr.string(
108 mandatory = True,
109 doc = "The Name of Github repository",
110 ),
111 "submodules": attr.string_dict(
112 mandatory = False,
113 default = {},
114 doc = "The list of submodules with their integrity as value",
115 ),
Tim Windelschmidtdb085222025-04-28 20:58:18 +0200116 "submodule_info": attr.string_dict(
117 mandatory = False,
118 default = {},
119 doc = """The list of submodules with their GitHub API response as value.
120 This is a workaround until https://github.com/bazelbuild/bazel/issues/24777
121 is implemented to prevent hitting GitHub API limits.""",
122 ),
Tim Windelschmidtf5c45102025-02-08 22:30:58 +0000123 "ref": attr.string(
124 default = "",
125 doc =
126 "The specific ref to be checked out.",
127 ),
128 "integrity": attr.string(
129 doc = """Expected checksum in Subresource Integrity format of the file downloaded.
130
131 This must match the checksum of the file downloaded. _It is a security risk
132 to omit the checksum as remote files can change._ At best omitting this
133 field will make your build non-hermetic. It is optional to make development
134 easier but either this attribute or `sha256` should be set before shipping.""",
135 ),
136 "patches": attr.label_list(
137 default = [],
138 doc =
139 "A list of files that are to be applied as patches after " +
140 "extracting the archive. By default, it uses the Bazel-native patch implementation " +
141 "which doesn't support fuzz match and binary patch, but Bazel will fall back to use " +
142 "patch command line tool if `patch_tool` attribute is specified or there are " +
143 "arguments other than `-p` in `patch_args` attribute.",
144 ),
145 "patch_tool": attr.string(
146 default = "",
147 doc = "The patch(1) utility to use. If this is specified, Bazel will use the specified " +
148 "patch tool instead of the Bazel-native patch implementation.",
149 ),
150 "patch_args": attr.string_list(
151 default = ["-p0"],
152 doc =
153 "The arguments given to the patch tool. Defaults to -p0, " +
154 "however -p1 will usually be needed for patches generated by " +
155 "git. If multiple -p arguments are specified, the last one will take effect." +
156 "If arguments other than -p are specified, Bazel will fall back to use patch " +
157 "command line tool instead of the Bazel-native patch implementation. When falling " +
158 "back to patch command line tool and patch_tool attribute is not specified, " +
159 "`patch` will be used. This only affects patch files in the `patches` attribute.",
160 ),
161 "patch_cmds": attr.string_list(
162 default = [],
163 doc = "Sequence of Bash commands to be applied on Linux/Macos after patches are applied.",
164 ),
165 "build_file": attr.label(
166 allow_single_file = True,
167 doc =
168 "The file to use as the BUILD file for this repository." +
169 "This attribute is an absolute label (use '@//' for the main " +
170 "repo). The file does not need to be named BUILD, but can " +
171 "be (something like BUILD.new-repo-name may work well for " +
172 "distinguishing it from the repository's actual BUILD files. " +
173 "Either build_file or build_file_content can be specified, but " +
174 "not both.",
175 ),
176 "build_file_content": attr.string(
177 doc =
178 "The content for the BUILD file for this repository. " +
179 "Either build_file or build_file_content can be specified, but " +
180 "not both.",
181 ),
182 "workspace_file": attr.label(
183 doc =
184 "The file to use as the `WORKSPACE` file for this repository. " +
185 "Either `workspace_file` or `workspace_file_content` can be " +
186 "specified, or neither, but not both.",
187 ),
188 "workspace_file_content": attr.string(
189 doc =
190 "The content for the WORKSPACE file for this repository. " +
191 "Either `workspace_file` or `workspace_file_content` can be " +
192 "specified, or neither, but not both.",
193 ),
194}
195
196github_repository = repository_rule(
197 implementation = _github_repository,
198 attrs = _github_repository_attrs,
199)