blob: c5c505783e310193aec75ea26343ed2c7ec3f24a [file] [log] [blame]
# 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.
RUST_FILETYPE = FileType([".rs"])
C_LIB_FILETYPE = FileType([".a"])
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
done = False
for src_part, dest_part in zip(src_parts, dest_parts):
if src_part != dest_part:
break
n += 1
relative_path = ""
for i in range(n, len(src_parts)):
relative_path += "../"
relative_path += "/".join(dest_parts[n:])
return relative_path
def _create_setup_cmd(lib, deps_dir):
"""
Helper function to construct a command for symlinking a library into the
deps directory.
"""
return (
"ln -sf " + _relative(deps_dir, lib.path) + " " +
deps_dir + "/" + lib.basename + "\n"
)
# TODO(dzc): rust_binary should not be able to depend on cc_library
def _setup_deps(deps, name, working_dir):
"""
Walks through dependencies and constructs the necessary commands for linking
to all the necessary dependencies.
"""
deps_dir = working_dir + "/" + name + ".deps"
setup_cmd = ["rm -rf " + deps_dir + "; mkdir " + deps_dir + "\n"]
has_rlib = False
has_native = False
libs = set()
transitive_libs = set()
symlinked_libs = set()
link_flags = []
for dep in deps:
if hasattr(dep, "rust_lib"):
libs += [dep.rust_lib]
transitive_libs += [dep.rust_lib]
symlinked_libs += [dep.rust_lib]
link_flags += [(
"--extern " + dep.label.name + "=" +
deps_dir + "/" + dep.rust_lib.basename
)]
has_rlib = True
if hasattr(dep, "transitive_libs"):
transitive_libs += dep.transitive_libs
symlinked_libs += dep.transitive_libs
# If this rule depends on a cc_library
if hasattr(dep, "cc"):
native_libs = C_LIB_FILETYPE.filter(dep.cc.libs)
libs += native_libs
transitive_libs += native_libs
symlinked_libs += native_libs
link_flags += ["-l static=" + dep.label.name]
has_native = True
for symlinked_lib in symlinked_libs:
setup_cmd += [_create_setup_cmd(symlinked_lib, deps_dir)]
search_flags = []
if has_rlib:
search_flags += ["-L dependency=" + deps_dir]
if has_native:
search_flags += ["-L native=" + deps_dir]
return struct(
libs = list(libs),
transitive_libs = list(transitive_libs),
setup_cmd = setup_cmd,
search_flags = search_flags,
link_flags = link_flags,
)
def _get_features_flags(features):
"""
Constructs a string containing the feature flags from the features specified
in the features attribute.
"""
features_flags = []
for feature in features:
features_flags += [" --cfg feature=\\\"" + feature + "\\\""]
return features_flags
def _build_rustc_command(ctx, crate_type, src, output_dir, depinfo,
extra_flags=[]):
"""
Builds the rustc command
"""
# Paths to the Rust compiler and standard libraries.
rustc_path = ctx.file._rustc.path
rustlib_path = ctx.files._rustlib[0].dirname
# Paths to cc (for linker) and ar
cpp_fragment = ctx.configuration.fragment(cpp)
cc = cpp_fragment.compiler_executable
ar = cpp_fragment.ar_executable
# Currently, the CROSSTOOL config for darwin sets ar to "libtool". Because
# rust uses ar-specific flags, use /usr/bin/ar in this case.
# TODO(dzc): This is not ideal. Remove this workaround once ar_executable
# always points to an ar binary.
ar_str = "%s" % ar
if ar_str.find("libtool", 0) != -1:
ar = "/usr/bin/ar"
# Construct features flags
features_flags = _get_features_flags(ctx.attr.features)
return " ".join([
"set -e;",
" ".join(depinfo.setup_cmd),
rustc_path + " " + src,
"--crate-name " + ctx.label.name,
"--crate-type " + crate_type,
"-g",
"--codegen ar=%s" % ar,
"--codegen linker=%s" % cc,
"-L all=" + rustlib_path,
" ".join(extra_flags),
" ".join(features_flags),
"--out-dir " + output_dir,
"--emit=dep-info,link",
" ".join(depinfo.search_flags),
" ".join(depinfo.link_flags),
" ".join(ctx.attr.rustc_flags),
])
def _rust_library_impl(ctx):
"""
Implementation for rust_library Skylark rule.
"""
# Find lib.rs
srcs = ctx.files.srcs
lib_rs = None
crate_name_rs = ctx.label.name + ".rs"
for src in srcs:
if src.basename == "lib.rs" or src.basename == crate_name_rs:
lib_rs = src.path
if not lib_rs:
fail("No lib.rs or source file matching crate name found.")
# Output library
rust_lib = ctx.outputs.rust_lib
output_dir = rust_lib.dirname
# Dependencies
depinfo = _setup_deps(ctx.attr.deps, ctx.label.name, output_dir)
# Build rustc command
cmd = _build_rustc_command(
ctx = ctx,
crate_type = "lib",
src = lib_rs,
output_dir = output_dir,
depinfo = depinfo)
# Compile action.
ctx.action(
inputs = srcs + ctx.files.data + depinfo.libs,
outputs = [rust_lib],
mnemonic = 'Rustc',
command = cmd,
use_default_shell_env = True,
progress_message = "Compiling Rust library " + ctx.label.name
)
return struct(
files = set([rust_lib]),
transitive_libs = depinfo.transitive_libs,
rust_lib = rust_lib,
)
def _rust_binary_impl_common(ctx, extra_flags = []):
"""
Implementation for rust_binary Skylark rule.
"""
# Find main.rs.
srcs = ctx.files.srcs
main_rs = None
crate_name_rs = ctx.label.name + ".rs"
for src in srcs:
if src.basename == "main.rs" or src.basename == crate_name_rs:
main_rs = src.path
if not main_rs:
fail("No main.rs or source file matching crate name found.")
# Output binary
rust_binary = ctx.outputs.executable
output_dir = rust_binary.dirname
# Dependencies
depinfo = _setup_deps(ctx.attr.deps, ctx.label.name, output_dir)
# Build rustc command.
cmd = _build_rustc_command(
ctx = ctx,
crate_type = "bin",
src = main_rs,
output_dir = output_dir,
depinfo = depinfo,
extra_flags = extra_flags)
# Compile action.
ctx.action(
inputs = srcs + ctx.files.data + depinfo.libs,
outputs = [rust_binary],
mnemonic = 'Rustc',
command = cmd,
use_default_shell_env = True,
progress_message = "Compiling Rust binary " + ctx.label.name
)
def _rust_binary_impl(ctx):
"""
Implementation for rust_binary Skylark rule.
"""
return _rust_binary_impl_common(ctx)
def _rust_test_impl(ctx):
"""
Implementation for rust_test Skylark rule.
"""
return _rust_binary_impl_common(ctx, ["--test"])
_rust_common_attrs = {
"srcs": attr.label_list(allow_files = RUST_FILETYPE),
"data": attr.label_list(allow_files = True, cfg = DATA_CFG),
"deps": attr.label_list(),
"features": attr.string_list(),
"rustc_flags": attr.string_list(),
"_rustc": attr.label(
default = Label("//tools/rust:rustc"),
executable = True,
single_file = True),
"_rustlib": attr.label(default = Label("//tools/rust:rustlib")),
}
rust_library = rule(
_rust_library_impl,
attrs = _rust_common_attrs,
outputs = {
"rust_lib": "lib%{name}.rlib",
},
)
rust_binary = rule(
_rust_binary_impl,
executable = True,
attrs = _rust_common_attrs,
)
rust_test = rule(
_rust_test_impl,
executable = True,
attrs = _rust_common_attrs,
test = True,
)