-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathpkg_config.bzl
194 lines (165 loc) · 6.79 KB
/
pkg_config.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
def _success(value):
return struct(error = None, value = value)
def _error(message):
return struct(error = message, value = None)
def _split(result, delimeter = " "):
if result.error != None:
return result
return _success([arg for arg in result.value.strip().split(delimeter) if arg])
def _find_binary(ctx, binary_name):
binary = ctx.which(binary_name)
if binary == None:
return _error("Unable to find binary: {}".format(binary_name))
return _success(binary)
def _execute(ctx, binary, args):
result = ctx.execute([binary] + args)
if result.return_code != 0:
return _error("Failed execute {} {}".format(binary, args))
return _success(result.stdout)
def _pkg_config(ctx, pkg_config, pkg_name, args):
return _execute(ctx, pkg_config, [pkg_name] + args)
def _check(ctx, pkg_config, pkg_name):
exist = _pkg_config(ctx, pkg_config, pkg_name, ["--exists"])
if exist.error != None:
return _error("Package {} does not exist".format(pkg_name))
if ctx.attr.version != "":
version = _pkg_config(ctx, pkg_config, pkg_name, ["--exact-version", ctx.attr.version])
if version.error != None:
return _error("Require {} version = {}".format(pkg_name, ctx.attr.version))
if ctx.attr.min_version != "":
version = _pkg_config(ctx, pkg_config, pkg_name, ["--atleast-version", ctx.attr.min_version])
if version.error != None:
return _error("Require {} version >= {}".format(pkg_name, ctx.attr.min_version))
if ctx.attr.max_version != "":
version = _pkg_config(ctx, pkg_config, pkg_name, ["--max-version", ctx.attr.max_version])
if version.error != None:
return _error("Require {} version <= {}".format(pkg_name, ctx.attr.max_version))
return _success(None)
def _extract_prefix(flags, prefix, strip = True):
stripped, remain = [], []
for arg in flags:
if arg.startswith(prefix):
if strip:
stripped += [arg[len(prefix):]]
else:
stripped += [arg]
else:
remain += [arg]
return stripped, remain
def _includes(ctx, pkg_config, pkg_name):
includes = _split(_pkg_config(ctx, pkg_config, pkg_name, ["--cflags-only-I"]))
if includes.error != None:
return includes
includes, unused = _extract_prefix(includes.value, "-I", strip = True)
return _success(includes)
def _copts(ctx, pkg_config, pkg_name):
return _split(_pkg_config(ctx, pkg_config, pkg_name, [
"--cflags-only-other",
"--libs-only-L",
"--static",
]))
def _linkopts(ctx, pkg_config, pkg_name):
return _split(_pkg_config(ctx, pkg_config, pkg_name, [
"--libs-only-other",
"--libs-only-l",
"--static",
]))
def _ignore_opts(opts, ignore_opts):
remain = []
for opt in opts:
if opt not in ignore_opts:
remain += [opt]
return remain
def _symlinks(ctx, basename, srcpaths):
result = []
root = ctx.path("")
base = root.get_child(basename)
rootlen = len(str(base)) - len(basename)
for src in [ctx.path(p) for p in srcpaths]:
dest = base.get_child(src.basename)
ctx.symlink(src, dest)
result += [str(dest)[rootlen:]]
return result
def _deps(ctx, pkg_config, pkg_name):
deps = _split(_pkg_config(ctx, pkg_config, pkg_name, [
"--libs-only-L",
"--static",
]))
if deps.error != None:
return deps
deps, unused = _extract_prefix(deps.value, "-L", strip = True)
result = []
for dep in {dep: True for dep in deps}.keys():
base = "deps_" + dep.replace("/", "_").replace(".", "_")
result += _symlinks(ctx, base, [dep])
return _success(result)
def _fmt_array(array):
return ",".join(['"{}"'.format(a) for a in array])
def _fmt_glob(array):
return _fmt_array(["{}/**/*.h".format(a) for a in array])
def _pkg_config_impl(ctx):
pkg_name = ctx.attr.pkg_name
if pkg_name == "":
pkg_name = ctx.attr.name
pkg_config = _find_binary(ctx, "pkg-config")
if pkg_config.error != None:
return pkg_config
pkg_config = pkg_config.value
check = _check(ctx, pkg_config, pkg_name)
if check.error != None:
return check
includes = _includes(ctx, pkg_config, pkg_name)
if includes.error != None:
return includes
includes = includes.value
includes = _symlinks(ctx, "includes", includes)
strip_include = "includes"
if len(includes) == 1:
strip_include = includes[0]
if ctx.attr.strip_include != "":
strip_include += "/" + ctx.attr.strip_include
ignore_opts = ctx.attr.ignore_opts
copts = _copts(ctx, pkg_config, pkg_name)
if copts.error != None:
return copts
copts = _ignore_opts(copts.value, ignore_opts)
linkopts = _linkopts(ctx, pkg_config, pkg_name)
if linkopts.error != None:
return linkopts
linkopts = _ignore_opts(linkopts.value, ignore_opts)
deps = _deps(ctx, pkg_config, pkg_name)
if deps.error != None:
return deps
deps = deps.value
include_prefix = ctx.attr.name
if ctx.attr.include_prefix != "":
include_prefix = ctx.attr.include_prefix + "/" + ctx.attr.name
build = ctx.template("BUILD", Label("//:BUILD.tmpl"), substitutions = {
"%{name}": ctx.attr.name,
"%{hdrs}": _fmt_glob(includes),
"%{includes}": _fmt_array(includes),
"%{copts}": _fmt_array(copts),
"%{extra_copts}": _fmt_array(ctx.attr.copts),
"%{deps}": _fmt_array(deps),
"%{extra_deps}": _fmt_array(ctx.attr.deps),
"%{linkopts}": _fmt_array(linkopts),
"%{extra_linkopts}": _fmt_array(ctx.attr.linkopts),
"%{strip_include}": strip_include,
"%{include_prefix}": include_prefix,
}, executable = False)
pkg_config = repository_rule(
attrs = {
"pkg_name": attr.string(doc = "Package name for pkg-config query, default to name."),
"include_prefix": attr.string(doc = "Additional prefix when including file, e.g. third_party. Compatible with strip_include option to produce desired include paths."),
"strip_include": attr.string(doc = "Strip prefix when including file, e.g. libs, files not included will be invisible. Compatible with include_prefix option to produce desired include paths."),
"version": attr.string(doc = "Exact package version."),
"min_version": attr.string(doc = "Minimum package version."),
"max_version": attr.string(doc = "Maximum package version."),
"deps": attr.string_list(doc = "Dependency targets."),
"linkopts": attr.string_list(doc = "Extra linkopts value."),
"copts": attr.string_list(doc = "Extra copts value."),
"ignore_opts": attr.string_list(doc = "Ignore listed opts in copts or linkopts."),
},
local = True,
implementation = _pkg_config_impl,
)