| # Copyright 2015 Google Inc. 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.""" |
| |
| 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_d_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_d_libs = set() |
| d_srcs = set() |
| symlinked_libs = set() |
| versions = set() |
| imports = set() |
| link_flags = set() |
| for dep in deps: |
| if hasattr(dep, "d_lib"): |
| # The dependency is a d_library. |
| libs += [dep.d_lib] |
| d_srcs += dep.d_srcs |
| transitive_d_libs += [dep.d_lib] + dep.transitive_d_libs |
| symlinked_libs += [dep.d_lib] + dep.transitive_d_libs |
| versions += dep.versions |
| 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_libs += dep.transitive_d_libs |
| symlinked_libs += dep.transitive_d_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_d_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_d_libs = list(transitive_d_libs), |
| d_srcs = list(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.""" |
| 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.libs + |
| depinfo.transitive_d_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 + depinfo.d_srcs, |
| transitive_d_libs = depinfo.transitive_d_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 + 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_d_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) |
| |
| def _d_binary_impl(ctx): |
| """Implementation of the d_binary rule.""" |
| return _d_binary_impl_common(ctx) |
| |
| def _d_test_impl(ctx): |
| """Implementation of the d_test rule.""" |
| return _d_binary_impl_common(ctx, extra_flags=["-unittest"]) |
| |
| def _d_source_library_impl(ctx): |
| """Implementation of the d_source_library rule.""" |
| transitive_d_srcs = set(order="compile") |
| transitive_d_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_d_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 + list(transitive_d_srcs), |
| transitive_d_libs = transitive_d_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. |
| """ |
| d_docs_zip = ctx.outputs.d_docs |
| docs_dir = d_docs_zip.dirname + "/_d_docs" |
| objs_dir = d_docs_zip.dirname + "/_d_objs" |
| |
| # 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 ctx.attr.imports] + |
| toolchain.import_flags + |
| [src.path for src in ctx.files.srcs]) |
| |
| # HTML documentation generated by the D compiler. |
| d_docs = [ |
| ctx.new_file(ctx.configuration.bin_dir, |
| "_d_docs/%s" % src.basename.replace(".d", ".html")) |
| for src in ctx.files.srcs] |
| |
| # Object files created by the D compiler during documentation generation. |
| d_objs = [ |
| ctx.new_file(ctx.configuration.bin_dir, |
| "_d_objs/%s" % src.basename.replace(".d", ".o")) |
| for src in ctx.files.srcs] |
| |
| toolchain_files = ( |
| [ctx.file._d_compiler] + |
| ctx.files._d_stdlib + |
| ctx.files._d_stdlib_src + |
| ctx.files._d_runtime_import_src) |
| ddoc_inputs = ctx.files.srcs + toolchain_files |
| ctx.action(inputs = ddoc_inputs, |
| outputs = d_docs + d_objs, |
| mnemonic = "Ddoc", |
| command = " ".join(doc_cmd), |
| use_default_shell_env = True, |
| progress_message = "Generating D docs for " + ctx.label.name) |
| |
| # Build zip command. |
| zip_cmd = [ |
| ZIP_PATH, |
| "-qj", |
| d_docs_zip.path, |
| ] + [doc.path for doc in d_docs] |
| |
| ctx.action(inputs = d_docs, |
| outputs = [d_docs_zip], |
| mnemonic = "Ddoczip", |
| command = " ".join(zip_cmd), |
| use_default_shell_env = True, |
| progress_message = "Creating D doc archive 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("//tools/build_defs/d:dmd"), |
| executable = True, |
| single_file = True), |
| "_d_stdlib": attr.label( |
| default = Label("//tools/build_defs/d:libphobos2")), |
| "_d_stdlib_src": attr.label( |
| default = Label("//tools/build_defs/d:phobos-src")), |
| "_d_runtime_import_src": attr.label( |
| default = Label("//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, |
| executable = True, |
| attrs = _d_common_attrs + _d_compile_attrs, |
| ) |
| |
| d_test = rule( |
| _d_test_impl, |
| executable = True, |
| attrs = _d_common_attrs + _d_compile_attrs, |
| test = True, |
| ) |
| |
| _d_docs_attrs = { |
| "srcs": attr.label_list(allow_files = D_FILETYPE), |
| "imports": attr.string_list(), |
| } |
| |
| d_docs = rule( |
| _d_docs_impl, |
| attrs = _d_docs_attrs + _d_compile_attrs, |
| outputs = { |
| "d_docs": "%{name}-docs.zip", |
| }, |
| ) |