Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions perl/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Perl rules for Bazel"""

load(
"//perl/private:perl.bzl",
_perl_binary = "perl_binary",
_perl_library = "perl_library",
_perl_test = "perl_test",
)
load(
"//perl/private:perl_xs.bzl",
_perl_xs = "perl_xs",
)
load(
"//perl/private:providers.bzl",
_PerlInfo = "PerlInfo",
)

PerlInfo = _PerlInfo
perl_binary = _perl_binary
perl_library = _perl_library
perl_test = _perl_test
perl_xs = _perl_xs

# Keep this name around for legacy support.
# buildifier: disable=name-conventions
PerlLibrary = PerlInfo
364 changes: 19 additions & 345 deletions perl/perl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,353 +14,27 @@

"""Perl rules for Bazel"""

load("@bazel_skylib//lib:new_sets.bzl", "sets")
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@rules_cc//cc:defs.bzl", "CcInfo", "cc_common")
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")

# buildifier: disable=name-conventions
PerlLibrary = provider(
doc = "A provider containing components of a `perl_library`",
fields = [
"transitive_perl_sources",
"includes",
],
load(
"//perl/private:perl.bzl",
_perl_binary = "perl_binary",
_perl_library = "perl_library",
_perl_test = "perl_test",
)

PERL_XS_COPTS = [
"-fwrapv",
"-fPIC",
"-fno-strict-aliasing",
"-D_LARGEFILE_SOURCE",
"-D_FILE_OFFSET_BITS=64",
]

_perl_file_types = [".pl", ".pm", ".t", ".so", ".ix", ".al", ""]
_perl_srcs_attr = attr.label_list(allow_files = _perl_file_types)

_perl_deps_attr = attr.label_list(
allow_files = False,
providers = [PerlLibrary],
)

_perl_data_attr = attr.label_list(
allow_files = True,
load(
"//perl/private:perl_xs.bzl",
_perl_xs = "perl_xs",
)

_perl_main_attr = attr.label(
allow_single_file = _perl_file_types,
load(
"//perl/private:providers.bzl",
_PerlInfo = "PerlInfo",
)

_perl_env_attr = attr.string_dict()

def _get_main_from_sources(ctx):
sources = ctx.files.srcs
if len(sources) != 1:
fail("Cannot infer main from multiple 'srcs'. Please specify 'main' attribute.", "main")
return sources[0]

def _transitive_srcs(deps):
return struct(
srcs = [
d[PerlLibrary].transitive_perl_sources
for d in deps
if PerlLibrary in d
],
files = [
d[DefaultInfo].default_runfiles.files
for d in deps
],
)

def transitive_deps(ctx, extra_files = [], extra_deps = []):
"""Calculates transitive sets of args.

Calculates the transitive sets for perl sources, data runfiles,
include flags and runtime flags from the srcs, data and deps attributes
in the context.

Also adds extra_deps to the roots of the traversal.

Args:
ctx: a ctx object for a perl_library or a perl_binary rule.
extra_files: a list of File objects to be added to the default_files
extra_deps: a list of Target objects.
"""
deps = _transitive_srcs(ctx.attr.deps + extra_deps)
files = ctx.runfiles(
files = extra_files + ctx.files.srcs + ctx.files.data,
transitive_files = depset(transitive = deps.files),
collect_default = True,
)
return struct(
srcs = depset(
direct = ctx.files.srcs,
transitive = deps.srcs,
),
files = files,
)

def _include_paths(ctx):
"""Calculate the PERL5LIB paths for a perl_library rule's includes."""
workspace_name = ctx.label.workspace_name
if workspace_name:
workspace_root = "../" + workspace_name
else:
workspace_root = ""
package_root = (workspace_root + "/" + ctx.label.package).strip("/") or "."
include_paths = [package_root] if "." in ctx.attr.includes else []
include_paths.extend([package_root + "/" + include for include in ctx.attr.includes if include != "."])
for dep in ctx.attr.deps:
include_paths.extend(dep[PerlLibrary].includes)
include_paths = depset(direct = include_paths).to_list()
return include_paths

def _perl_library_implementation(ctx):
transitive_sources = transitive_deps(ctx)
return [
DefaultInfo(
runfiles = transitive_sources.files,
),
PerlLibrary(
transitive_perl_sources = transitive_sources.srcs,
includes = _include_paths(ctx),
),
]

def sum(items, initial):
result = initial
for item in items:
result += item
return result

def _perl_binary_implementation(ctx):
toolchain = ctx.toolchains["@rules_perl//perl:toolchain_type"].perl_runtime
interpreter = toolchain.interpreter

transitive_sources = transitive_deps(ctx, extra_files = toolchain.runtime + [ctx.outputs.executable])

main = ctx.file.main
if main == None:
main = _get_main_from_sources(ctx)

include_paths = sum([dep[PerlLibrary].includes for dep in ctx.attr.deps], [])
perl5lib = ":" + ":".join(include_paths) if include_paths else ""

ctx.actions.expand_template(
template = ctx.file._wrapper_template,
output = ctx.outputs.executable,
substitutions = {
"{PERL5LIB}": perl5lib,
"{env_vars}": _env_vars(ctx),
"{interpreter}": interpreter.short_path,
"{main}": main.short_path,
"{workspace_name}": ctx.label.workspace_name or ctx.workspace_name,
},
is_executable = True,
)

return DefaultInfo(
executable = ctx.outputs.executable,
runfiles = transitive_sources.files,
)

def _env_vars(ctx):
environment = ""
for name, value in ctx.attr.env.items():
if not _is_identifier(name):
fail("%s is not a valid environment variable name." % str(name))
value = ctx.expand_location(value, targets = ctx.attr.data)
environment += ("{key}='{value}' ").format(
key = name,
value = value.replace("'", "\\'"),
)
return environment

def _is_identifier(name):
# Must be non-empty.
if name == None or len(name) == 0:
return False

# Must start with alpha or '_'
if not (name[0].isalpha() or name[0] == "_"):
return False

# Must consist of alnum characters or '_'s.
for c in name.elems():
if not (c.isalnum() or c == "_"):
return False
return True

def _perl_test_implementation(ctx):
return _perl_binary_implementation(ctx)
PerlInfo = _PerlInfo
perl_binary = _perl_binary
perl_library = _perl_library
perl_test = _perl_test
perl_xs = _perl_xs

def _perl_xs_cc_lib(ctx, toolchain, srcs):
cc_toolchain = find_cc_toolchain(ctx)
xs_headers = toolchain.xs_headers

includes = [f.dirname for f in xs_headers.to_list()]

textual_hdrs = []
for hdrs in ctx.attr.textual_hdrs:
for hdr in hdrs.files.to_list():
textual_hdrs.append(hdr)
includes.append(hdr.dirname)

includes = sets.make(includes)
includes = sets.to_list(includes)

feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
requested_features = ctx.features,
unsupported_features = ctx.disabled_features,
)

(compilation_context, compilation_outputs) = cc_common.compile(
actions = ctx.actions,
name = ctx.label.name,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
srcs = srcs + ctx.files.cc_srcs,
defines = ctx.attr.defines,
additional_inputs = textual_hdrs,
private_hdrs = xs_headers.to_list(),
includes = includes,
user_compile_flags = ctx.attr.copts + PERL_XS_COPTS,
compilation_contexts = [],
)

(linking_context, _linking_outputs) = cc_common.create_linking_context_from_compilation_outputs(
actions = ctx.actions,
name = ctx.label.name,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
compilation_outputs = compilation_outputs,
user_link_flags = ctx.attr.linkopts,
linking_contexts = [],
)

return CcInfo(
compilation_context = compilation_context,
linking_context = linking_context,
)

def _perl_xs_implementation(ctx):
toolchain = ctx.toolchains["@rules_perl//perl:toolchain_type"].perl_runtime
xsubpp = toolchain.xsubpp

toolchain_files = depset(toolchain.runtime)

gen = []
cc_infos = []
args_typemaps = []

for typemap in ctx.files.typemaps:
args_typemaps += ["-typemap", typemap.short_path]

for src in ctx.files.srcs:
c_execpath = paths.replace_extension(src.path, ".c")
o_packagepath = paths.join("_objs/execroot/", c_execpath)
out = ctx.actions.declare_file(o_packagepath)

ctx.actions.run(
outputs = [out],
inputs = [src] + ctx.files.typemaps,
arguments = args_typemaps + ["-output", out.path, src.path],
progress_message = "Translitterating %s to %s" % (src.short_path, out.short_path),
executable = xsubpp,
tools = toolchain_files,
)

gen.append(out)

cc_info = _perl_xs_cc_lib(ctx, toolchain, gen)
cc_infos = [cc_info] + [dep[CcInfo] for dep in ctx.attr.deps]
cc_info = cc_common.merge_cc_infos(cc_infos = cc_infos)
lib = cc_info.linking_context.linker_inputs.to_list()[0].libraries[0]
dyn_lib = lib.dynamic_library

if len(ctx.attr.output_loc):
output = ctx.actions.declare_file(ctx.attr.output_loc)
else:
output = ctx.actions.declare_file(ctx.label.name + ".so")

ctx.actions.run_shell(
outputs = [output],
inputs = [dyn_lib],
arguments = [dyn_lib.path, output.path],
command = "cp $1 $2",
)

return [
cc_info,
DefaultInfo(files = depset([output])),
]

perl_library = rule(
attrs = {
"data": _perl_data_attr,
"deps": _perl_deps_attr,
"includes": attr.string_list(default = [".", "lib"]),
"srcs": _perl_srcs_attr,
},
implementation = _perl_library_implementation,
toolchains = ["@rules_perl//perl:toolchain_type"],
)

perl_binary = rule(
attrs = {
"data": _perl_data_attr,
"deps": _perl_deps_attr,
"env": _perl_env_attr,
"main": _perl_main_attr,
"srcs": _perl_srcs_attr,
"_wrapper_template": attr.label(
allow_single_file = True,
default = "binary_wrapper.tpl",
),
},
executable = True,
implementation = _perl_binary_implementation,
toolchains = ["@rules_perl//perl:toolchain_type"],
)

perl_test = rule(
attrs = {
"data": _perl_data_attr,
"deps": _perl_deps_attr,
"env": _perl_env_attr,
"main": _perl_main_attr,
"srcs": _perl_srcs_attr,
"_wrapper_template": attr.label(
allow_single_file = True,
default = "binary_wrapper.tpl",
),
},
executable = True,
test = True,
implementation = _perl_test_implementation,
toolchains = ["@rules_perl//perl:toolchain_type"],
)

perl_xs = rule(
attrs = {
"cc_srcs": attr.label_list(allow_files = [".c", ".cc"]),
"copts": attr.string_list(),
"defines": attr.string_list(),
"deps": attr.label_list(providers = [CcInfo]),
"linkopts": attr.string_list(),
"output_loc": attr.string(),
"srcs": attr.label_list(allow_files = [".xs"]),
"textual_hdrs": attr.label_list(allow_files = True),
"typemaps": attr.label_list(allow_files = True),
"_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
},
implementation = _perl_xs_implementation,
fragments = ["cpp"],
toolchains = [
"@rules_perl//perl:toolchain_type",
"@bazel_tools//tools/cpp:toolchain_type",
],
)
# Keep this name around for legacy support.
# buildifier: disable=name-conventions
PerlLibrary = PerlInfo
Loading