This repository has been archived by the owner on Jun 18, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
hazel.bzl
280 lines (253 loc) · 9.83 KB
/
hazel.bzl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
load("//hazel_base_repository:hazel_base_repository.bzl",
"hazel_base_repository",
"symlink_and_invoke_hazel")
load("@bazel_tools//tools/build_defs/repo:git.bzl",
"new_git_repository",
)
load("@bazel_tools//tools/build_defs/repo:http.bzl",
"http_archive",
)
load("//tools:ghc.bzl", "get_ghc_workspace", "default_ghc_workspaces")
load("//tools:mangling.bzl", "hazel_binary", "hazel_library", "hazel_workspace")
def _cabal_haskell_repository_impl(ctx):
ghc_workspace = get_ghc_workspace(ctx.attr.ghc_workspaces, ctx)
pkg = "{}-{}".format(ctx.attr.package_name, ctx.attr.package_version)
url = "https://hackage.haskell.org/package/{}.tar.gz".format(pkg)
# If the SHA is wrong, the error message is very unhelpful:
# https://github.com/bazelbuild/bazel/issues/3709
# As a workaround, we compute it manually if it's not set (and then fail
# this rule).
if not ctx.attr.sha256:
ctx.download(url=url, output="tar")
res = ctx.execute(["openssl", "sha", "-sha256", "tar"])
fail("Missing expected attribute \"sha256\" for {}; computed {}".format(pkg, res.stdout + res.stderr))
ctx.download_and_extract(
url=url,
stripPrefix=ctx.attr.package_name + "-" + ctx.attr.package_version,
sha256=ctx.attr.sha256,
output="")
symlink_and_invoke_hazel(
ctx,
ctx.attr.hazel_base_repo_name,
ghc_workspace,
ctx.attr.package_flags,
ctx.attr.package_name + ".cabal",
"package.bzl"
)
_cabal_haskell_repository = repository_rule(
implementation=_cabal_haskell_repository_impl,
attrs={
"package_name": attr.string(mandatory=True),
"package_version": attr.string(mandatory=True),
"package_flags": attr.string_dict(mandatory=True),
"hazel_base_repo_name": attr.string(mandatory=True),
"sha256": attr.string(mandatory=True),
"ghc_workspaces": attr.string_dict(mandatory=True),
})
def _core_library_repository_impl(ctx):
ctx.file(
"BUILD",
executable=False,
content="""
load("@io_tweag_rules_haskell//haskell:haskell.bzl", "haskell_import")
haskell_import(
name = "{pkg}",
package = "{pkg}",
visibility = ["//visibility:public"],
)
# Cabal packages can depend on other Cabal package's cbits, for example for
# CPP includes. To enable uniform handling we define a `-cbits` target for
# every Hazel Haskell target. In case of core_libraries this is just a dummy.
cc_import(
name = "{pkg}-cbits",
visibility = ["//visibility:public"],
)
""".format(pkg=ctx.attr.package))
_core_library_repository = repository_rule(
implementation=_core_library_repository_impl,
attrs={
"package": attr.string(mandatory=True),
})
def _all_hazel_packages_impl(ctx):
all_packages_filegroup = """
filegroup(
name = "all-package-files",
srcs = [{}],
)
""".format(",".join(["\"@{}//:bzl\"".format(hazel_workspace(p)) for p in ctx.attr.packages]))
one_package_template = """
filegroup(
name = "haskell_{package_name}",
srcs = ["@{workspace_name}//:bzl"],
)
"""
package_filegroups = [
one_package_template.format(
package_name = p,
workspace_name = hazel_workspace(p),
)
for p in ctx.attr.packages
]
ctx.file("BUILD", "\n".join([all_packages_filegroup]+package_filegroups), executable=False)
_all_hazel_packages = repository_rule(
implementation=_all_hazel_packages_impl,
attrs={
"packages": attr.string_list(mandatory=True),
})
def hazel_repositories(
core_packages,
packages,
extra_flags={},
extra_libs={},
extra_libs_hdrs={},
extra_libs_strip_include_prefix={},
exclude_packages=[],
ghc_workspaces=default_ghc_workspaces):
"""Generates external dependencies for a set of Haskell packages.
This macro should be invoked in the WORKSPACE. It generates a set of
external dependencies corresponding to the given packages:
- @hazel_base_repository: The compiled "hazel" Haskell binary, along with
support files.
- @haskell_{package}_{hash}: A build of the given Cabal package, one per entry
of the "packages" argument. (Note that Bazel only builds these
on-demand when needed by other rules.) This repository automatically
downloads the package's Cabal distribution from Hackage and parses the
.cabal file to generate BUILD rules.
- @all_hazel_packages: A repository depending on each package. Useful for
measuring our coverage of the full package set.
Args:
core_packages: A dict mapping Haskell package names to version
numbers. These packages are assumed to be provided by GHC.
packages: A dict mapping strings to structs, where each struct has two fields:
- version: A version string
- sha256: A hex-encoded SHA of the Cabal distribution (*.tar.gz).
extra_flags: A dict mapping package names to cabal flags.
exclude_packages: names of packages to exclude.
extra_libs: A dictionary that maps from name of extra libraries to Bazel
targets that provide the shared library.
extra_libs_hdrs: Similar to extra_libs, but provides header files.
extra_libs_strip_include_prefix: Similar to extra_libs, but allows to
get include prefix to strip.
ghc_workspaces: Dictionary mapping OS names to GHC workspaces.
Default: Linux/MacOS: "@ghc", Windows: "@ghc_windows".
Dictionary keys correspond to CPU values as returned by
`get_cpu_value` from `@bazel_tools//tools/cpp:lib_cc_configure.bzl`.
"""
hazel_base_repo_name = "hazel_base_repository"
pkgs = {n: packages[n] for n in packages if n not in exclude_packages}
hazel_base_repository(
name = hazel_base_repo_name,
ghc_workspaces = ghc_workspaces,
extra_libs = extra_libs,
extra_libs_hdrs = extra_libs_hdrs,
extra_libs_strip_include_prefix = extra_libs_strip_include_prefix,
)
for p in pkgs:
flags = {}
if hasattr(pkgs[p], "flags"):
items = pkgs[p].flags
# NOTE We have to convert booleans to strings in order to pass them as
# attributes of the _cabal_haskell_repository rule because there is no
# attribute type for dictionary of booleans at the moment.
flags = {flag: str(items[flag]) for flag in items}
if p in extra_flags:
items = extra_flags[p]
flags.update({flag: str(items[flag]) for flag in items})
_cabal_haskell_repository(
name = hazel_workspace(p),
package_name = p,
package_version = pkgs[p].version,
package_flags = flags,
sha256 = pkgs[p].sha256 if hasattr(pkgs[p], "sha256") else None,
hazel_base_repo_name = hazel_base_repo_name,
ghc_workspaces = ghc_workspaces,
)
for p in core_packages:
_core_library_repository(
name = hazel_workspace(p),
package = p,
)
_all_hazel_packages(
name = "all_hazel_packages",
packages = [p for p in pkgs])
def hazel_custom_package_hackage(
package_name,
version,
sha256=None,
build_file=None,
build_file_content=None):
"""Generate a repo for a Haskell package fetched from Hackage.
Args:
package_name: string, package name.
version: string, package version.
sha256: string, SHA256 hash of archive.
build_file: string,
the file to use as the BUILD file for this package.
Defaults to //third_party/haskel:BUILD.<package_name> if
neither build_file nor build_file_content are specified.
This attribute is a label relative to the main workspace.
build_file and build_file_content are mutually exclusive.
build_file_content: string,
the content for the BUILD file for this repository.
Will fall back to build_file if not specified.
build_file and build_file_content are mutually exclusive.
"""
package_id = package_name + "-" + version
url = "https://hackage.haskell.org/package/{0}/{1}.tar.gz".format(
package_id,
package_id,
)
if not build_file and not build_file_content:
build_file = "//third_party/haskell:BUILD.{0}".format(package_name)
http_archive(
name = hazel_workspace(package_name),
build_file = build_file,
build_file_content = build_file_content,
sha256 = sha256,
strip_prefix = package_id,
urls = [url],
)
def hazel_custom_package_github(
package_name,
github_user,
github_repo,
repo_sha,
strip_prefix=None,
archive_sha256=None,
clone_via_ssh=False,
build_file=None,
build_file_content=None):
"""Generate a repo for a Haskell package coming from a GitHub repo.
Args:
package_name: string, package name.
github_user: string, GitHub user.
github_repo: string, repo name under `github_user` account.
repo_sha: SHA1 of commit in the repo.
strip_prefix: strip this path prefix from directory repo, useful when a
repo contains several packages.
archive_sha256: hash of the actual archive to download.
clone_via_ssh: whether to clone the repo using SSH (useful for private
repos).
build_file: string,
the file to use as the BUILD file for this package.
Defaults to //third_party/haskel:BUILD.<package_name> if
neither build_file nor build_file_content are specified.
This attribute is a label relative to the main workspace.
build_file and build_file_content are mutually exclusive.
build_file_content: string,
the content for the BUILD file for this repository.
Will fall back to build_file if not specified.
build_file and build_file_content are mutually exclusive.
"""
if not build_file and not build_file_content:
build_file = "//third_party/haskell:BUILD.{0}".format(package_name)
url = "https://github.com/{0}/{1}".format(github_user, github_repo)
ssh_url = "[email protected]:{0}/{1}".format(github_user, github_repo)
new_git_repository(
name = hazel_workspace(package_name),
remote = ssh_url if clone_via_ssh else url,
build_file = build_file,
build_file_content = build_file_content,
commit = repo_sha,
strip_prefix = strip_prefix,
)