195 lines
6.8 KiB
Python
195 lines
6.8 KiB
Python
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",
|
|
]))
|
|
|
|
def _linkopts(ctx, pkg_config, pkg_name):
|
|
return _split(_pkg_config(ctx, pkg_config, pkg_name, [
|
|
"--libs-only-other",
|
|
"--libs-only-l",
|
|
]))
|
|
|
|
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)
|
|
if not dest.exists:
|
|
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,
|
|
)
|
|
|