blob: 71f717ebff9ec06934b54e215cf2ae9265aeace2 [file] [log] [blame]
#!/bin/bash -eu
#
# Copyright 2016 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.
#
# Tests the behavior of C++ rules.
# Load the test setup defined in the parent directory
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${CURRENT_DIR}/../integration_test_setup.sh" \
|| { echo "integration_test_setup.sh not found!" >&2; exit 1; }
function test_extra_action_for_compile() {
mkdir -p ea
cat > ea/BUILD <<EOF
action_listener(
name = "al",
extra_actions = [":ea"],
mnemonics = ["CppCompile"],
visibility = ["//visibility:public"],
)
extra_action(
name = "ea",
cmd = "if ! [[ -r ea/cc.cc ]]; then echo 'source file not in inputs'; exit 1; fi",
)
cc_library(
name = "cc",
srcs = ["cc.cc"],
)
EOF
echo 'void cc() {}' > ea/cc.cc
bazel build --experimental_action_listener=//ea:al //ea:cc || fail "expected success"
}
function test_cc_library_include_prefix_external_repository() {
r="$TEST_TMPDIR/r"
mkdir -p "$TEST_TMPDIR/r/foo/v1"
create_workspace_with_default_repos "$TEST_TMPDIR/r/WORKSPACE"
echo "#define FOO 42" > "$TEST_TMPDIR/r/foo/v1/foo.h"
cat > "$TEST_TMPDIR/r/foo/BUILD" <<EOF
cc_library(
name = "foo",
hdrs = ["v1/foo.h"],
include_prefix = "foolib",
strip_include_prefix = "v1",
visibility = ["//visibility:public"],
)
EOF
cat >> $(create_workspace_with_default_repos WORKSPACE) <<EOF
local_repository(
name = "foo",
path = "$TEST_TMPDIR/r",
)
EOF
cat > BUILD <<EOF
cc_binary(
name = "ok",
srcs = ["ok.cc"],
deps = ["@foo//foo"],
)
cc_binary(
name = "bad",
srcs = ["bad.cc"],
deps = ["@foo//foo"],
)
EOF
cat > ok.cc <<EOF
#include <stdio.h>
#include "foolib/foo.h"
int main() {
printf("FOO is %d\n", FOO);
}
EOF
cat > bad.cc <<EOF
#include <stdio.h>
#include "foo/v1/foo.h"
int main() {
printf("FOO is %d\n", FOO);
}
EOF
bazel build :bad && fail "Should not have found include at repository-relative path"
bazel build :ok || fail "Should have found include at synthetic path"
}
function test_tree_artifact_headers_are_invalidated() {
mkdir -p "ta_headers"
cat > "ta_headers/BUILD" <<EOF
load(":mygen.bzl", "mygen")
sh_binary(
name = "mygen_sh",
srcs = ["mygen.sh"],
visibility = ["//visibility:public"],
)
mygen(
name="mylib_generated",
srcs=[":mydef.txt"],
)
cc_library(
name = "mylib",
srcs = [":mylib_generated"],
hdrs = [":mylib_generated"],
)
cc_binary(
name = "myexec",
srcs = [],
deps = [":mylib"],
)
EOF
cat > "ta_headers/mygen.sh" <<'EOF'
#!/bin/bash
set -euo pipefail
src_files=$1
hdr_files=$2
fc_name=$(cat ta_headers/mydef.txt)
mkdir -p ${src_files}
mkdir -p ${hdr_files}
cat > ${src_files}/main.c <<EOT
#include "ta_headers/files.h/another.h"
int main(void) {
return MYFC();
}
EOT
cat > ${src_files}/another.c <<EOT
#include "ta_headers/files.h/another.h"
int ${fc_name}(void) {
return 0;
}
EOT
cat > ${hdr_files}/another.h <<EOT
#define MYFC ${fc_name}
int ${fc_name}(void);
EOT
EOF
chmod +x ta_headers/mygen.sh
cat > "ta_headers/mygen.bzl" <<EOF
def _mygen_impl(ctx):
args = ctx.actions.args()
treeC = ctx.actions.declare_directory("files.c")
treeH = ctx.actions.declare_directory("files.h")
args.add(treeC.path)
args.add(treeH.path)
ctx.actions.run(
inputs = ctx.files.srcs,
outputs = [treeC, treeH],
arguments = [args],
executable = ctx.executable._mygen,
)
return [DefaultInfo(files=depset([treeC, treeH]))]
mygen = rule(
implementation=_mygen_impl,
attrs={
"srcs": attr.label_list(allow_files=True),
"_mygen": attr.label(
cfg="host",
executable=True,
allow_files=True,
default=":mygen_sh",
),
},
)
EOF
# So we have another.h defining a macro that is used by both main.c and
# another.c. :main depends on :another, and gets the header through the
# tree artifact. First build is fine.
echo "fc1" > "ta_headers/mydef.txt"
bazel build //ta_headers:myexec || fail \
"First build failed, something is wrong with the test."
# Now we change the content of another.h to define a different macro.
# This test verifies that not only another.c is recompiled, but also
# main.c. This is a regression test for
# https://github.com/bazelbuild/bazel/issues/5785.
echo "fc2" > "ta_headers/mydef.txt"
bazel build //ta_headers:myexec || fail \
"Second build failed, tree artifact was not invalidated."
}
# This test tests that Bazel can produce dynamic libraries that have undefined
# symbols on Mac and Linux. Not sure it is a sane default to allow undefined
# symbols, but it's the default we had historically. This test creates
# an executable (main) that defines bar(), and a shared library (plugin) that
# calls bar(). When linking the libplugin.so, symbol 'bar' is undefined.
# +-----------------------------+ +----------------------------------+
# | main | | libplugin.so |
# | | | |
# | main() { return foo(); } +---------> foo() { return bar() - 42; } |
# | | | + |
# | | | | |
# | bar() { return 42; } <------------------+ |
# | | | |
# +-----------------------------+ +----------------------------------+
function test_undefined_dynamic_lookup() {
if is_windows; then
# Windows doesn't allow undefined symbols in shared libraries.
return 0
fi
mkdir -p "dynamic_lookup"
cat > "dynamic_lookup/BUILD" <<EOF
cc_binary(
name = "libplugin.so",
srcs = ["plugin.cc"],
linkshared = 1,
)
cc_binary(
name = "main",
srcs = ["main.cc", "libplugin.so"],
)
EOF
cat > "dynamic_lookup/plugin.cc" <<EOF
int bar();
int foo() { return bar() - 42; }
EOF
cat > "dynamic_lookup/main.cc" <<EOF
int foo();
int bar() { return 42; }
int main() { return foo(); }
EOF
bazel build //dynamic_lookup:main || fail "Bazel couldn't build the binary."
bazel run //dynamic_lookup:main || fail "Run of the binary failed."
}
function test_save_feature_state() {
mkdir -p ea
cat > ea/BUILD <<EOF
cc_library(
name = "cc",
srcs = ["cc.cc", "cc1.cc"],
features = ["test_feature"],
)
EOF
echo 'void cc() {}' > ea/cc.cc
echo 'void cc1() {}' > ea/cc1.cc
bazel build --experimental_save_feature_state //ea:cc || fail "expected success"
ls bazel-bin/ea/feature_debug/cc/requested_features.txt || "requested_features.txt not created"
ls bazel-bin/ea/feature_debug/cc/enabled_features.txt || "enabled_features.txt not created"
# This assumes "grep" is supported in any environment bazel is used.
grep "test_feature" bazel-bin/ea/feature_debug/cc/requested_features.txt || "test_feature should have been found in requested_features."
}
# TODO: test include dirs and defines
function setup_cc_starlark_api_test() {
local pkg="$1"
create_workspace_with_default_repos "$pkg"/WORKSPACE
mkdir "$pkg"/include_dir
touch "$pkg"/include_dir/include.h
mkdir "$pkg"/system_include_dir
touch "$pkg"/system_include_dir/system_include.h
mkdir "$pkg"/quote_include_dir
touch "$pkg"/quote_include_dir/quote_include.h
cat > "$pkg"/BUILD << EOF
load("//${pkg}:cc_api_rules.bzl", "cc_lib", "cc_bin")
cc_lib(
name = "a",
srcs = [
"a1.cc",
"a2.cc",
],
private_hdrs = [
"a2.h",
"include_dir/include.h",
"system_include_dir/system_include.h",
"quote_include_dir/quote_include.h"
],
user_compile_flags = ["-DA_DEFINITION_LOCAL"],
public_hdrs = ["a.h"],
includes = ["$pkg/include_dir"],
system_includes = ["$pkg/system_include_dir"],
quote_includes = ["$pkg/quote_include_dir"],
defines = ["A_DEFINITION"],
deps = [
":b",
":d",
],
)
cc_lib(
name = "b",
srcs = [
"b.cc",
],
public_hdrs = ["b.h"],
deps = [":c"],
)
cc_lib(
name = "c",
srcs = [
"c.cc",
],
public_hdrs = ["c.h"],
)
cc_lib(
name = "d",
srcs = ["d.cc"],
public_hdrs = ["d.h"],
)
cc_bin(
name = "e",
srcs = ["e.cc"],
data = [":f"],
linkstatic = 1,
user_link_flags = [
"-ldl",
"-lm",
"-Wl,-rpath,bazel-bin/${pkg}",
],
deps = [
":a",
],
)
cc_bin(
name = "f",
srcs = ["f.cc"],
linkshared = 1,
deps = [
":a",
],
)
EOF
cat > $pkg/a1.cc << EOF
#include <system_include.h>
#include "include.h"
#include "quote_include.h"
#include "a.h"
#include "a2.h"
#ifdef A_DEFINITION_LOCAL
#include "b.h"
#include "d.h"
#endif
using namespace std;
string alongernamethanusual() { return "a1" + a2() + b() + d(); }
EOF
cat > $pkg/a2.cc << EOF
#include <string>
using namespace std;
string a2() { return "a2"; }
EOF
cat > $pkg/a.h << EOF
#ifndef HEADER_A
#define HEADER_A
#include <string>
using namespace std;
string alongernamethanusual();
#endif
EOF
cat > $pkg/a2.h << EOF
#ifndef HEADER_A2
#define HEADER_A2
#include <string>
using namespace std;
string a2();
#endif
EOF
cat > $pkg/b.cc << EOF
#include "b.h"
#include <string>
#include "c.h"
using namespace std;
string b() { return "b" + c(); }
EOF
cat > $pkg/b.h << EOF
#ifndef HEADER_B
#define HEADER_B
#include <string>
using namespace std;
string b();
#endif
EOF
cat > $pkg/c.cc << EOF
#include "c.h"
#include <algorithm>
#include <string>
using namespace std;
string c() { return "c"; }
EOF
cat > $pkg/c.h << EOF
#ifndef HEADER_C
#define HEADER_C
#include <string>
using namespace std;
string c();
#endif
EOF
cat > $pkg/d.cc << EOF
#include "d.h"
#include <string>
using namespace std;
string d() { return "d"; }
EOF
cat > $pkg/d.h << EOF
#ifndef HEADER_D
#define HEADER_D
#include <string>
using namespace std;
string d();
#endif
EOF
cat > $pkg/e.cc << EOF
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef A_DEFINITION
#include "a.h"
#endif
#ifdef A_DEFINITION_LOCAL
#include "thisdoesntexist.h"
#endif
using namespace std;
int main() {
void* handle = dlopen("libf.so", RTLD_LAZY);
typedef string (*f_t)();
f_t f = (f_t)dlsym(handle, "f");
cout << alongernamethanusual() + f() << endl;
return 0;
}
EOF
cat > $pkg/f.cc << EOF
#include <algorithm>
#include <string>
#include "a.h"
using namespace std;
extern "C" string f() {
string str = alongernamethanusual();
reverse(str.begin(), str.end());
return str;
}
EOF
cat > $pkg/script.lds << EOF
VERS_42.0 {
local:
*;
};
EOF
cp "$CURRENT_DIR"/cc_api_rules.bzl "$pkg"/cc_api_rules.bzl
}
function test_cc_starlark_api_default_values() {
local pkg="${FUNCNAME[0]}"
mkdir -p "$pkg"
setup_cc_starlark_api_test "${FUNCNAME[0]}"
bazel build --experimental_cc_skylark_api_enabled_packages=, --verbose_failures \
//"$pkg":e &>"$TEST_log" || fail "Build failed"
nm -u bazel-bin/"$pkg"/e | grep alongernamethanusual && \
fail "alongernamethanusual is not defined"
bazel-bin/"$pkg"/e | grep a1a2bcddcb2a1a || fail "output is incorrect"
}
function test_cc_starlark_api_link_static_false() {
local pkg="${FUNCNAME[0]}"
mkdir -p "$pkg"
setup_cc_starlark_api_test "${FUNCNAME[0]}"
cat >> "$pkg"/BUILD << EOF
cc_bin(
name = "g",
srcs = ["e.cc"],
data = [":f"],
linkstatic = 0,
user_link_flags = [
"-ldl",
"-lm",
"-Wl,-rpath,bazel-bin/${pkg}",
],
deps = [
":a",
],
)
EOF
bazel build --experimental_cc_skylark_api_enabled_packages=, --verbose_failures \
//"$pkg":g &>"$TEST_log" || fail "Build failed"
nm -u bazel-bin/"$pkg"/g | grep alongernamethanusual || fail "alongernamethanusual is defined"
bazel-bin/"$pkg"/g | grep a1a2bcddcb2a1a || fail "output is incorrect"
}
function test_cc_starlark_api_additional_inputs() {
# This uses --version-script which isn't available on Mac linker.
[ "$PLATFORM" != "darwin" ] || return 0
local pkg="${FUNCNAME[0]}"
mkdir -p "$pkg"
setup_cc_starlark_api_test "${FUNCNAME[0]}"
cat >> "$pkg"/BUILD << EOF
cc_bin(
name = "g",
srcs = ["e.cc"],
data = [":f"],
linkstatic = 1,
additional_linker_inputs = ["script.lds"],
user_link_flags = [
"-ldl",
"-lm",
"-Wl,-rpath,bazel-bin/${pkg}",
"-Wl,--version-script=\$(location script.lds)",
],
deps = [
":a",
],
)
EOF
bazel build --experimental_cc_skylark_api_enabled_packages=, --verbose_failures \
//"$pkg":g &>"$TEST_log" || fail "Build failed"
nm bazel-bin/"$pkg"/g | grep VERS_42.0 || fail "VERS_42.0 not in binary"
bazel-bin/"$pkg"/g | grep a1a2bcddcb2a1a || fail "output is incorrect"
}
run_suite "cc_integration_test"