blob: 9e8164a0fe801e5ed19f88fb8ffc4e24a45083ab [file] [log] [blame]
# Copyright 2015 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""D rules for Bazel."""
load("//tools/build_rules:deprecation.bzl", "deprecated")
def warning(rule):
print(deprecated(
"d",
rule,
"@bazel_tools//tools/build_defs/d:d.bzl",
"@io_bazel_rules_d//d:d.bzl"))
A_FILETYPE = FileType([".a"])
D_FILETYPE = FileType([
".d",
".di",
])
ZIP_PATH = "/usr/bin/zip"
def _relative(src_path, dest_path):
"""Returns the relative path from src_path to dest_path."""
src_parts = src_path.split("/")
dest_parts = dest_path.split("/")
n = 0
for src_part, dest_part in zip(src_parts, dest_parts):
if src_part != dest_part:
break
n += 1
relative_path = ""
for _ in range(n, len(src_parts)):
relative_path += "../"
relative_path += "/".join(dest_parts[n:])
return relative_path
def _create_setup_cmd(lib, deps_dir):
"""Constructs a command for symlinking a library into the deps directory."""
return (
"ln -sf " + _relative(deps_dir, lib.path) + " " +
deps_dir + "/" + lib.basename + "\n"
)
def _d_toolchain(ctx):
"""Returns a struct containing info about the D toolchain.
Args:
ctx: The ctx object.
Return:
Struct containing the following fields:
d_compiler_path: The path to the D compiler.
link_flags: Linker (-L) flags for adding the standard library to the
library search paths.
import_flags: import (-L) flags for adding the standard library sources
to the import paths.
"""
d_compiler_path = ctx.file._d_compiler.path
return struct(
d_compiler_path = d_compiler_path,
link_flags = ["-L-L" + ctx.files._d_stdlib[0].dirname],
import_flags = [
"-I" + ctx.files._d_stdlib_src[0].dirname,
"-I" + ctx.files._d_runtime_import_src[0].dirname])
def _format_version(name):
"""Formats the string name to be used in a --version flag."""
return name.replace("-", "_")
def _build_compile_command(ctx, srcs, out, depinfo, extra_flags=[]):
"""Returns a string containing the D compile command."""
toolchain = _d_toolchain(ctx)
cmd = (
["set -e;"] +
depinfo.setup_cmd +
[toolchain.d_compiler_path] +
extra_flags + [
"-of" + out.path,
"-I.",
"-debug",
"-w",
"-g",
] +
["-I%s/%s" % (ctx.label.package, im) for im in ctx.attr.imports] +
["-I%s" % im for im in depinfo.imports] +
toolchain.import_flags +
["-version=Have_%s" % _format_version(ctx.label.name)] +
["-version=%s" % v for v in ctx.attr.versions] +
["-version=%s" % v for v in depinfo.versions] +
srcs)
return " ".join(cmd)
def _build_link_command(ctx, objs, out, depinfo):
"""Returns a string containing the D link command."""
toolchain = _d_toolchain(ctx)
cmd = (
["set -e;"] +
depinfo.setup_cmd +
[toolchain.d_compiler_path] +
["-of" + out.path] +
toolchain.link_flags +
depinfo.lib_flags +
depinfo.link_flags +
objs)
return " ".join(cmd)
def _setup_deps(deps, name, working_dir):
"""Sets up dependencies.
Walks through dependencies and constructs the commands and flags needed
for linking the necessary dependencies.
Args:
deps: List of deps labels from ctx.attr.deps.
name: Name of the current target.
working_dir: The output directory of the current target's output.
Returns:
Returns a struct containing the following fields:
libs: List of Files containing the target's direct library dependencies.
transitive_libs: List of Files containing all of the target's
transitive libraries.
d_srcs: List of Files representing D source files of dependencies that
will be used as inputs for this target.
versions: List of D versions to be used for compiling the target.
setup_cmd: String containing the symlink commands to be used to set
up the dependencies.
imports: List of Strings containing input paths that will be passed
to the D compiler via -I flags.
link_flags: List of linker flags.
lib_flags: List of library search flags.
"""
deps_dir = working_dir + "/" + name + ".deps"
setup_cmd = ["rm -rf " + deps_dir + ";" + "mkdir -p " + deps_dir + ";"]
libs = set()
transitive_libs = set()
d_srcs = set()
transitive_d_srcs = set()
versions = set()
imports = set()
link_flags = set()
symlinked_libs = set()
for dep in deps:
if hasattr(dep, "d_lib"):
# The dependency is a d_library.
libs += [dep.d_lib]
transitive_libs += dep.transitive_libs
symlinked_libs += [dep.d_lib] + dep.transitive_libs
d_srcs += dep.d_srcs
transitive_d_srcs += dep.transitive_d_srcs
versions += dep.versions + ["Have_%s" % _format_version(dep.label.name)]
link_flags += ["-L-l%s" % dep.label.name] + dep.link_flags
imports += ["%s/%s" % (dep.label.package, im) for im in dep.imports]
elif hasattr(dep, "d_srcs"):
# The dependency is a d_source_library.
d_srcs += dep.d_srcs
transitive_d_srcs += dep.transitive_d_srcs
transitive_libs += dep.transitive_libs
symlinked_libs += dep.transitive_libs
link_flags += ["-L%s" % linkopt for linkopt in dep.linkopts]
imports += ["%s/%s" % (dep.label.package, im) for im in dep.imports]
versions += dep.versions
elif hasattr(dep, "cc"):
# The dependency is a cc_library
native_libs = A_FILETYPE.filter(dep.cc.libs)
libs += native_libs
transitive_libs += native_libs
symlinked_libs += native_libs
link_flags += ["-L-l%s" % dep.label.name]
else:
fail("D targets can only depend on d_library, d_source_library, or " +
"cc_library targets.", "deps")
for symlinked_libs in symlinked_libs:
setup_cmd += [_create_setup_cmd(symlinked_libs, deps_dir)]
return struct(
libs = list(libs),
transitive_libs = list(transitive_libs),
d_srcs = list(d_srcs),
transitive_d_srcs = list(transitive_d_srcs),
versions = versions,
setup_cmd = setup_cmd,
imports = list(imports),
link_flags = list(link_flags),
lib_flags = ["-L-L%s" % deps_dir])
def _d_library_impl(ctx):
"""Implementation of the d_library rule."""
warning("d_library")
d_lib = ctx.outputs.d_lib
# Dependencies
depinfo = _setup_deps(ctx.attr.deps, ctx.label.name, d_lib.dirname)
# Build compile command.
cmd = _build_compile_command(
ctx = ctx,
srcs = [src.path for src in ctx.files.srcs],
out = d_lib,
depinfo = depinfo,
extra_flags = ["-lib"])
compile_inputs = (
ctx.files.srcs +
depinfo.d_srcs +
depinfo.transitive_d_srcs +
depinfo.libs +
depinfo.transitive_libs +
[ctx.file._d_compiler] +
ctx.files._d_stdlib +
ctx.files._d_stdlib_src +
ctx.files._d_runtime_import_src)
ctx.action(inputs = compile_inputs,
outputs = [d_lib],
mnemonic = "Dcompile",
command = cmd,
use_default_shell_env = True,
progress_message = "Compiling D library " + ctx.label.name)
return struct(files = set([d_lib]),
d_srcs = ctx.files.srcs,
transitive_d_srcs = depinfo.d_srcs,
transitive_libs = depinfo.transitive_libs,
link_flags = depinfo.link_flags,
versions = ctx.attr.versions,
imports = ctx.attr.imports,
d_lib = d_lib)
def _d_binary_impl_common(ctx, extra_flags=[]):
"""Common implementation for rules that build a D binary."""
d_bin = ctx.outputs.executable
d_obj = ctx.new_file(ctx.configuration.bin_dir,
d_bin.basename + ".o")
depinfo = _setup_deps(ctx.attr.deps, ctx.label.name, d_bin.dirname)
# Build compile command
compile_cmd = _build_compile_command(
ctx = ctx,
srcs = [src.path for src in ctx.files.srcs],
depinfo = depinfo,
out = d_obj,
extra_flags = ["-c"] + extra_flags)
toolchain_files = (
[ctx.file._d_compiler] +
ctx.files._d_stdlib +
ctx.files._d_stdlib_src +
ctx.files._d_runtime_import_src)
compile_inputs = (ctx.files.srcs +
depinfo.d_srcs +
depinfo.transitive_d_srcs +
toolchain_files)
ctx.action(inputs = compile_inputs,
outputs = [d_obj],
mnemonic = "Dcompile",
command = compile_cmd,
use_default_shell_env = True,
progress_message = "Compiling D binary " + ctx.label.name)
# Build link command
link_cmd = _build_link_command(
ctx = ctx,
objs = [d_obj.path],
depinfo = depinfo,
out = d_bin)
link_inputs = (
[d_obj] +
depinfo.libs +
depinfo.transitive_libs +
toolchain_files)
ctx.action(inputs = link_inputs,
outputs = [d_bin],
mnemonic = "Dlink",
command = link_cmd,
use_default_shell_env = True,
progress_message = "Linking D binary " + ctx.label.name)
return struct(d_srcs = ctx.files.srcs,
transitive_d_srcs = depinfo.d_srcs,
imports = ctx.attr.imports)
def _d_binary_impl(ctx):
"""Implementation of the d_binary rule."""
warning("d_binary")
return _d_binary_impl_common(ctx)
def _d_test_impl(ctx):
"""Implementation of the d_test rule."""
warning("d_test")
return _d_binary_impl_common(ctx, extra_flags=["-unittest"])
def _d_source_library_impl(ctx):
"""Implementation of the d_source_library rule."""
warning("d_library")
transitive_d_srcs = set(order="compile")
transitive_libs = set()
transitive_imports = set()
transitive_linkopts = set()
transitive_versions = set()
for dep in ctx.attr.deps:
if hasattr(dep, "d_srcs"):
# Dependency is another d_source_library target.
transitive_d_srcs += dep.d_srcs
transitive_imports += dep.imports
transitive_linkopts += dep.linkopts
transitive_versions += dep.versions
elif hasattr(dep, "cc"):
# Dependency is a cc_library target.
native_libs = A_FILETYPE.filter(dep.cc.libs)
transitive_libs += native_libs
transitive_linkopts += ["-l%s" % dep.label.name]
else:
fail("d_source_library can only depend on other " +
"d_source_library or cc_library targets.", "deps")
return struct(
d_srcs = ctx.files.srcs,
transitive_d_srcs = list(transitive_d_srcs),
transitive_libs = transitive_libs,
imports = ctx.attr.imports + list(transitive_imports),
linkopts = ctx.attr.linkopts + list(transitive_linkopts),
versions = ctx.attr.versions + list(transitive_versions))
# TODO(dzc): Use ddox for generating HTML documentation.
def _d_docs_impl(ctx):
"""Implementation for the d_docs rule
This rule runs the following steps to generate an archive containing
HTML documentation generated from doc comments in D source code:
1. Run the D compiler with the -D flags to generate HTML code
documentation.
2. Create a ZIP archive containing the HTML documentation.
"""
warning("d_docs")
d_docs_zip = ctx.outputs.d_docs
docs_dir = d_docs_zip.dirname + "/_d_docs"
objs_dir = d_docs_zip.dirname + "/_d_objs"
target = struct(name = ctx.attr.dep.label.name,
srcs = ctx.attr.dep.d_srcs,
transitive_srcs = ctx.attr.dep.transitive_d_srcs,
imports = ctx.attr.dep.imports)
# Build D docs command
toolchain = _d_toolchain(ctx)
doc_cmd = (
[
"set -e;",
"rm -rf %s; mkdir %s;" % (docs_dir, docs_dir),
"rm -rf %s; mkdir %s;" % (objs_dir, objs_dir),
toolchain.d_compiler_path,
"-c",
"-D",
"-Dd%s" % docs_dir,
"-od%s" % objs_dir,
"-I.",
] +
["-I%s/%s" % (ctx.label.package, im) for im in target.imports] +
toolchain.import_flags +
[src.path for src in target.srcs] +
[
"&&",
"(cd %s &&" % docs_dir,
ZIP_PATH,
"-qR",
d_docs_zip.basename,
"$(find . -type f) ) &&",
"mv %s/%s %s" % (docs_dir, d_docs_zip.basename, d_docs_zip.path)
])
toolchain_files = (
[ctx.file._d_compiler] +
ctx.files._d_stdlib +
ctx.files._d_stdlib_src +
ctx.files._d_runtime_import_src)
ddoc_inputs = target.srcs + target.transitive_srcs + toolchain_files
ctx.action(inputs = ddoc_inputs,
outputs = [d_docs_zip],
mnemonic = "Ddoc",
command = " ".join(doc_cmd),
use_default_shell_env = True,
progress_message = "Generating D docs for " + ctx.label.name)
_d_common_attrs = {
"srcs": attr.label_list(allow_files = D_FILETYPE),
"deps": attr.label_list(),
"imports": attr.string_list(),
"linkopts": attr.string_list(),
"versions": attr.string_list(),
}
_d_compile_attrs = {
"_d_compiler": attr.label(
default = Label("@bazel_tools//tools/build_defs/d:dmd"),
executable = True,
single_file = True,
),
"_d_stdlib": attr.label(
default = Label("@bazel_tools//tools/build_defs/d:libphobos2"),
),
"_d_stdlib_src": attr.label(
default = Label("@bazel_tools//tools/build_defs/d:phobos-src"),
),
"_d_runtime_import_src": attr.label(
default = Label("@bazel_tools//tools/build_defs/d:druntime-import-src"),
),
}
d_library = rule(
_d_library_impl,
attrs = _d_common_attrs + _d_compile_attrs,
outputs = {
"d_lib": "lib%{name}.a",
},
)
d_source_library = rule(
_d_source_library_impl,
attrs = _d_common_attrs,
)
d_binary = rule(
_d_binary_impl,
attrs = _d_common_attrs + _d_compile_attrs,
executable = True,
)
d_test = rule(
_d_test_impl,
attrs = _d_common_attrs + _d_compile_attrs,
executable = True,
test = True,
)
_d_docs_attrs = {
"dep": attr.label(mandatory = True),
}
d_docs = rule(
_d_docs_impl,
attrs = _d_docs_attrs + _d_compile_attrs,
outputs = {
"d_docs": "%{name}-docs.zip",
},
)
DMD_BUILD_FILE = """
package(default_visibility = ["//visibility:public"])
config_setting(
name = "darwin",
values = {"host_cpu": "darwin"},
)
config_setting(
name = "k8",
values = {"host_cpu": "k8"},
)
filegroup(
name = "dmd",
srcs = select({
":darwin": ["dmd2/osx/bin/dmd"],
":k8": ["dmd2/linux/bin64/dmd"],
}),
)
filegroup(
name = "libphobos2",
srcs = select({
":darwin": ["dmd2/osx/lib/libphobos2.a"],
":k8": [
"dmd2/linux/lib64/libphobos2.a",
"dmd2/linux/lib64/libphobos2.so",
],
}),
)
filegroup(
name = "phobos-src",
srcs = glob(["dmd2/src/phobos/**/*.*"]),
)
filegroup(
name = "druntime-import-src",
srcs = glob([
"dmd2/src/druntime/import/*.*",
"dmd2/src/druntime/import/**/*.*",
]),
)
"""
def d_repositories():
warning("d_repositories")
native.new_http_archive(
name = "dmd_linux_x86_64",
url = "http://downloads.dlang.org/releases/2.x/2.070.0/dmd.2.070.0.linux.tar.xz",
sha256 = "42f48db8716f523076e881151f631e741342012881ec9b57353544ed46c4f774",
build_file_content = DMD_BUILD_FILE,
)
native.new_http_archive(
name = "dmd_darwin_x86_64",
url = "http://downloads.dlang.org/releases/2.x/2.070.0/dmd.2.070.0.osx.tar.xz",
sha256 = "c1dd14ded8e099dcb2f136379013959b07790249f440010d556e67ff59fe44a0",
build_file_content = DMD_BUILD_FILE,
)