[bazel] Build and test wpical with bazel (#8155)

This pulls down the prebuilt ceres libraries and uses them with Bazel to
build and test wpical.

Do note that bazel looks up artifacts used for testing differently than
the other build systems.  It wants you to use its runfiles API to find
the dependencies reliably.  Add a function to look up the paths for
files, and use runfiles only when building with Bazel to maintain
compatibility with other languages.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
This commit is contained in:
Austin Schuh
2025-08-06 21:56:42 -07:00
committed by GitHub
parent c01f0c3d46
commit 6eba91bc04
12 changed files with 427 additions and 25 deletions

View File

@@ -95,6 +95,7 @@ publish_all(
"//sysid:sysid_publish.publish",
"//thirdparty/googletest:googletest-cpp_publish.publish",
"//thirdparty/imgui_suite:imguiSuite-cpp_publish.publish",
"//wpical:wpical_publish.publish",
"//wpigui:wpigui-cpp_publish.publish",
"//wpilibNewCommands:wpilibNewCommands-cpp_publish.publish",
"//wpilibNewCommands:wpilibNewCommands-java_publish.publish",

View File

@@ -1,4 +1,7 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
load("//thirdparty/ceres:repositories.bzl", "ceres_repositories")
ceres_repositories()
http_archive(
name = "bazel_features",

View File

@@ -189,6 +189,7 @@ def third_party_cc_lib_helper(
include_root,
src_root = None,
src_excludes = [],
defines = [],
visibility = None):
"""
Helper for src / headers pairs that aren't directly compiled, but rather pulled into a bigger library.
@@ -212,6 +213,7 @@ def third_party_cc_lib_helper(
include_root + "/**",
]),
includes = [include_root],
defines = defines,
strip_include_prefix = include_root,
visibility = visibility,
)
@@ -292,6 +294,7 @@ def wpilib_cc_library(
srcs = srcs + [lib + "-srcs" for lib in third_party_libraries],
deps = deps + [lib + "-headers" for lib in third_party_libraries + third_party_header_only_libraries],
strip_include_prefix = strip_include_prefix,
linkopts = linkopts,
**kwargs
)

View File

@@ -353,6 +353,7 @@ def package_binary_cc_project(
name,
maven_group_id,
maven_artifact_name,
extra_files = [],
architectures = None,
renames = None):
"""Packages the C++ binary targets for a project.
@@ -374,7 +375,7 @@ def package_binary_cc_project(
srcs = [
":{}-files".format(name),
"//:license_pkg_files",
],
] + extra_files,
architectures = architectures,
)

45
thirdparty/ceres/BUILD.bazel vendored Normal file
View File

@@ -0,0 +1,45 @@
"""
Ceres Solver files
"""
load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library")
package(default_visibility = ["//visibility:public"])
cc_import(
name = "ceres-impl",
static_library = select({
"@rules_bzlmodrio_toolchains//conditions:linux_x86_64": "@ceres_linuxx86-64static//:lib",
"@rules_bzlmodrio_toolchains//conditions:linux_x86_64_debug": "@ceres_linuxx86-64staticdebug//:lib",
"@rules_bzlmodrio_toolchains//conditions:osx": "@ceres_osxuniversalstatic//:lib",
"@rules_bzlmodrio_toolchains//conditions:osx_debug": "@ceres_osxuniversalstaticdebug//:lib",
"@rules_bzlmodrio_toolchains//conditions:windows_arm64": "@ceres_windowsarm64static//:lib",
"@rules_bzlmodrio_toolchains//conditions:windows_arm64_debug": "@ceres_windowsarm64staticdebug//:lib",
"@rules_bzlmodrio_toolchains//conditions:windows_x86_64": "@ceres_windowsx86-64static//:lib",
"@rules_bzlmodrio_toolchains//conditions:windows_x86_64_debug": "@ceres_windowsx86-64staticdebug//:lib",
"@rules_bzlmodrio_toolchains//constraints/is_bookworm64:bookworm64": "@ceres_linuxarm64static//:lib",
"@rules_bzlmodrio_toolchains//constraints/is_bookworm64:bookworm64_debug": "@ceres_linuxarm64staticdebug//:lib",
"@rules_bzlmodrio_toolchains//constraints/is_raspibookworm32:raspibookworm32": "@ceres_linuxarm32static//:lib",
"@rules_bzlmodrio_toolchains//constraints/is_raspibookworm32:raspibookworm32_debug": "@ceres_linuxarm32staticdebug//:lib",
"//conditions:default": None,
}),
)
cc_library(
name = "ceres",
defines = [
"GLOG_NO_GFLAGS",
"GLOG_USE_GLOG_EXPORT",
] + select({
"@platforms//os:windows": ["GLOG_DEPRECATED=__declspec(deprecated)"],
"//conditions:default": ["GLOG_DEPRECATED=[[deprecated]]"],
}),
linkopts = select({
"@platforms//os:windows": ["dbghelp.lib"],
"//conditions:default": [],
}),
deps = [
":ceres-impl",
"@ceres_headers//:headers",
],
)

60
thirdparty/ceres/repositories.bzl vendored Normal file
View File

@@ -0,0 +1,60 @@
""" Starlark file for ceres repository definitions """
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
def ceres_repositories():
""" Fetches the ceres solver libraries """
year = "frc2026"
version = "2.2-1"
http_archive(
name = "ceres_headers",
build_file_content = """
load(\"@rules_cc//cc:defs.bzl\", \"cc_library\")
cc_library(
name = \"headers\",
hdrs = glob(["ceres/**", "glog/**", "suitesparse/**", "openblas/**"]),
includes = ["."],
deps = ["@//wpimath:eigen-headers"],
visibility = [\"//visibility:public\"],
)
""",
url = "https://frcmaven.wpi.edu/artifactory/development/edu/wpi/first/thirdparty/" + year + "/ceres/ceres-cpp/" + version + "/ceres-cpp-" + version + "-headers.zip",
integrity = "sha256-ITP1hirOrcna3tyOUQyniUGPw5JeeC8Ffm5scno865w=",
)
_LIB_ARTIFACTS = {
"linuxarm32static": ("linux", "**/*.a", "sha256-ptlO1AuEWz84nAZn0fdXDf6fRsfj1EQu+/FR7PQO8Pk="),
"linuxarm32staticdebug": ("linux", "**/*.a", "sha256-1n5wFPsML0HYuOodbiDTT4taxo5k/IC7to7YBPQtM1Q="),
"linuxarm64static": ("linux", "**/*.a", "sha256-sBoNq7nTyllKJnOvyNm+IAxnKi8wP3YymaMwK2m8qCw="),
"linuxarm64staticdebug": ("linux", "**/*.a", "sha256-44RmHzCSx8rptaaP3DC2IbTy4p61oAHzfzvomCALk6I="),
"linuxx86-64static": ("linux", "**/*.a", "sha256-ntuu0fS01f/vkL4rMaYEuUaDvuYqQwwqLKcQy6yJwd8="),
"linuxx86-64staticdebug": ("linux", "**/*.a", "sha256-2CmV1Z+gM3AKq/+rC9WSn8Gkx/OXLnVMjHlGT1kmB/c="),
"osxuniversalstatic": ("osx", "**/*.a", "sha256-ExnU2z+kGU0iaYRmOpcPCLWdwIDKhVpQnN2g6iJ/z/U="),
"osxuniversalstaticdebug": ("osx", "**/*.a", "sha256-dnqxm5qgVBKD43ORCIkYH+mXi7oQQKbCTWDY1GdTNfQ="),
"windowsarm64static": ("windows", "**/*.lib", "sha256-obWGoORklu5g//x7CmykDX4S4g7ixy9RECdui5zy28g="),
"windowsarm64staticdebug": ("windows", "**/*.lib", "sha256-iL/rdZ2i+9+48IxQEK3Ty8/zQkp/1h9halDG9YEEXz0="),
"windowsx86-64static": ("windows", "**/*.lib", "sha256-wPhWBdyEC3AK6KgUgbvJsxWODGPSUMbAZmdEE7zSJ9Y="),
"windowsx86-64staticdebug": ("windows", "**/*.lib", "sha256-ibozSdLVUEYAuECwKtTKrqZB5pNFdajktwF2TEw+aDg="),
}
for artifact, (prefix, glob_pattern, integrity) in _LIB_ARTIFACTS.items():
repo_name = "ceres_" + artifact
build_file_content = """
filegroup(
name = \"lib\",
srcs = glob([\"%s\"]),
visibility = [\"//visibility:public\"],
)
""" % glob_pattern
url_fname = "ceres-cpp-" + version + "-" + artifact + ".zip"
http_archive(
name = repo_name,
build_file_content = build_file_content,
strip_prefix = prefix,
url = "https://frcmaven.wpi.edu/artifactory/development/edu/wpi/first/thirdparty/" + year + "/ceres/ceres-cpp/" + version + "/" + url_fname,
integrity = integrity,
)

247
wpical/BUILD.bazel Normal file
View File

@@ -0,0 +1,247 @@
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("@rules_pkg//:mappings.bzl", "pkg_files")
load("//shared/bazel/rules:cc_rules.bzl", "wpilib_cc_library")
load("//shared/bazel/rules:packaging.bzl", "package_binary_cc_project")
load("//shared/bazel/rules/gen:gen-resources.bzl", "generate_resources")
load("//shared/bazel/rules/gen:gen-version-file.bzl", "generate_version_file")
pkg_files(
name = "licenses",
srcs = [
"WPICalThirdPartyNotices.txt",
],
)
generate_resources(
name = "generate-resources",
namespace = "wpical",
prefix = "WPI",
resource_files = glob(["src/main/native/resources/*"]),
)
generate_version_file(
name = "generate-version",
output_file = "WPILibVersion.cpp",
template = "src/main/generate/WPILibVersion.cpp.in",
)
cc_library(
name = "headers",
hdrs = glob([
"src/main/native/include/**/*",
]),
strip_include_prefix = "src/main/native/include",
deps = [
":headers-libdogleg",
":headers-mrcal",
":headers-mrcal-generated",
":headers-mrcal-java",
],
)
cc_library(
name = "headers-libdogleg",
hdrs = glob([
"src/main/native/thirdparty/libdogleg/include/**/*",
]),
strip_include_prefix = "src/main/native/thirdparty/libdogleg/include",
)
cc_library(
name = "headers-mrcal-generated",
hdrs = glob([
"src/main/native/thirdparty/mrcal/generated/**/*",
]),
strip_include_prefix = "src/main/native/thirdparty/mrcal/generated/",
)
cc_library(
name = "headers-mrcal",
hdrs = glob([
"src/main/native/thirdparty/mrcal/include/**/*",
]),
strip_include_prefix = "src/main/native/thirdparty/mrcal/include/",
)
cc_library(
name = "headers-mrcal-java",
hdrs = glob([
"src/main/native/thirdparty/mrcal_java/include/**/*",
]),
strip_include_prefix = "src/main/native/thirdparty/mrcal_java/include/",
)
unix_copts = [
"-Wno-pedantic",
"-Wno-format-nonliteral",
"-Wno-unused-variable",
"-Wno-unused-function",
"-Wno-sign-compare",
]
osx_copts = unix_copts
copts = select({
"@platforms//os:linux": unix_copts + [
"-Wno-maybe-uninitialized",
],
"@platforms//os:osx": osx_copts,
"@platforms//os:windows": [
"/wd4047",
"/wd4098",
"/wd4267",
],
})
unix_cxxopts = [
"-Wno-missing-field-initializers",
"-Wno-pedantic",
"-fpermissive",
"-Wno-deprecated-declarations",
"-Wno-return-type",
"-Wno-missing-braces",
"-Wno-null-conversion",
"-Wno-unused-but-set-variable",
]
osx_cxxopts = unix_cxxopts + [
"-Wno-unused-variable",
"-Wno-unused-function",
"-Wno-sign-compare",
"-Wno-sometimes-uninitialized",
]
cxxopts = select({
"@platforms//os:linux": unix_cxxopts + [
"-Wno-deprecated-enum-enum-conversion",
],
"@platforms//os:osx": osx_cxxopts,
"@platforms//os:windows": [
"/wd4068",
"/wd4101",
"/wd4200",
"/wd4576",
"/wd4715",
],
})
mac_linkopts = [
"-framework",
"Metal",
"-framework",
"MetalKit",
"-framework",
"Cocoa",
"-framework",
"IOKit",
"-framework",
"CoreFoundation",
"-framework",
"CoreVideo",
"-framework",
"QuartzCore",
"-framework",
"Accelerate",
"-framework",
"AVFoundation",
"-framework",
"CoreMedia",
]
linkopts = select({
"@platforms//os:linux": [],
"@platforms//os:osx": mac_linkopts,
"@platforms//os:windows": [
"-DEFAULTLIB:Gdi32.lib",
"-DEFAULTLIB:Shell32.lib",
"-DEFAULTLIB:d3d11.lib",
"-DEFAULTLIB:d3dcompiler.lib",
"-DEFAULTLIB:Comdlg32.lib",
"-DEFAULTLIB:dbghelp.lib",
"-DEFAULTLIB:Advapi32.lib",
],
})
wpilib_cc_library(
name = "wpical_lib",
srcs = glob(
[
"src/main/native/cpp/**/*.cpp",
"src/main/native/thirdparty/libdogleg/src/**/*.cpp",
"src/main/native/thirdparty/mrcal/src/**/*.cpp",
"src/main/native/thirdparty/mrcal/src/**/*.c",
"src/main/native/thirdparty/mrcal_java/src/**/*.cpp",
],
exclude = ["src/main/native/cpp/WPIcal.cpp"],
) + [
":generate-resources",
],
copts = copts,
cxxopts = cxxopts,
defines = ["OPENCV_DISABLE_EIGEN_TENSOR_SUPPORT"],
include_license_files = True,
linkopts = linkopts,
linkstatic = True,
strip_include_prefix = "include",
visibility = ["//visibility:public"],
deps = [
":headers",
"//apriltag",
"//thirdparty/ceres",
"//wpigui",
"//wpiutil",
"@bzlmodrio-opencv//libraries/cpp/opencv",
],
)
cc_binary(
name = "wpical",
srcs = [
"src/main/native/cpp/WPIcal.cpp",
],
copts = copts,
cxxopts = cxxopts,
linkopts = select({
"@platforms//os:linux": [],
"@platforms//os:osx": [],
"@platforms//os:windows": [
"-SUBSYSTEM:WINDOWS",
],
}),
deps = [
":wpical_lib",
"//thirdparty/imgui_suite",
],
)
cc_test(
name = "wpical_test",
size = "medium",
srcs = glob(["src/test/native/**"]),
copts = copts,
cxxopts = cxxopts,
data = glob(["src/main/native/assets/**"]) + [
"src/main/native/assets",
"src/main/native/assets/altfieldvideo",
"src/main/native/assets/fieldvideo",
],
defines = [
'PROJECT_ROOT_PATH=\\"wpical/src/main/native/assets\\"',
"__BAZEL__=1",
],
deps = [
":wpical_lib",
"//thirdparty/ceres",
"//thirdparty/googletest",
"@bazel_tools//tools/cpp/runfiles",
],
)
package_binary_cc_project(
name = "wpical",
extra_files = [":licenses"],
maven_artifact_name = "wpical",
maven_group_id = "edu.wpi.first.tools",
)

View File

@@ -0,0 +1,20 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <memory>
#include <string>
#ifdef __BAZEL__
#include "tools/cpp/runfiles/runfiles.h"
using bazel::tools::cpp::runfiles::Runfiles;
std::string LookupPath(std::string path) {
std::string error;
std::unique_ptr<Runfiles> runfiles(Runfiles::CreateForTest(&error));
return runfiles->Rlocation("__main__/" + path);
}
#else
std::string LookupPath(std::string path) {
return path;
}
#endif

View File

@@ -0,0 +1,9 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string>
std::string LookupPath(std::string path);

View File

@@ -10,10 +10,18 @@
#include <gtest/gtest.h>
#include <wpi/json.h>
#include "path_lookup.h"
const std::string projectRootPath = PROJECT_ROOT_PATH;
const char* const tmpdir_c_str = std::getenv("TEST_TMPDIR");
const std::string calSavePath =
projectRootPath.substr(0, projectRootPath.find("/src/main/native/assets")) +
"/build";
tmpdir_c_str == nullptr
? (projectRootPath.substr(
0, projectRootPath.find("/src/main/native/assets")) +
"/build")
: std::string(tmpdir_c_str);
cameracalibration::CameraModel cameraModel = {
.intrinsic_matrix = Eigen::Matrix<double, 3, 3>::Identity(),
.distortion_coefficients = Eigen::Matrix<double, 8, 1>::Zero(),
@@ -28,10 +36,11 @@ const std::string videoLocation = "/altfieldvideo";
const std::string fileSuffix = ".mp4";
const std::string videoLocation = "/fieldvideo";
#endif
TEST(Camera_CalibrationTest, OpenCV_Typical) {
int ret = cameracalibration::calibrate(
projectRootPath + "/testcalibration" + fileSuffix, cameraModel, 0.709f,
0.551f, 12, 8, false);
LookupPath(projectRootPath + "/testcalibration" + fileSuffix),
cameraModel, 0.709f, 0.551f, 12, 8, false);
cameracalibration::dumpJson(cameraModel,
calSavePath + "/cameracalibration.json");
EXPECT_EQ(ret, 0);
@@ -39,44 +48,44 @@ TEST(Camera_CalibrationTest, OpenCV_Typical) {
TEST(Camera_CalibrationTest, OpenCV_Atypical) {
int ret = cameracalibration::calibrate(
projectRootPath + videoLocation + "/long" + fileSuffix, cameraModel,
0.709f, 0.551f, 12, 8, false);
LookupPath(projectRootPath + videoLocation + "/long" + fileSuffix),
cameraModel, 0.709f, 0.551f, 12, 8, false);
EXPECT_EQ(ret, 1);
}
TEST(Camera_CalibrationTest, MRcal_Typical) {
int ret = cameracalibration::calibrate(
projectRootPath + "/testcalibration" + fileSuffix, cameraModel, .709f, 12,
8, 1080, 1920, false);
LookupPath(projectRootPath + "/testcalibration" + fileSuffix),
cameraModel, .709f, 12, 8, 1080, 1920, false);
EXPECT_EQ(ret, 0);
}
TEST(Camera_CalibrationTest, MRcal_Atypical) {
int ret = cameracalibration::calibrate(
projectRootPath + videoLocation + "/short" + fileSuffix, cameraModel,
0.709f, 12, 8, 1080, 1920, false);
LookupPath(projectRootPath + videoLocation + "/short" + fileSuffix),
cameraModel, 0.709f, 12, 8, 1080, 1920, false);
EXPECT_EQ(ret, 1);
}
TEST(Field_CalibrationTest, Typical) {
int ret = fieldcalibration::calibrate(
projectRootPath + videoLocation, output_json,
LookupPath(projectRootPath + videoLocation), output_json,
calSavePath + "/cameracalibration.json",
projectRootPath + "/2024-crescendo.json", 3, false);
LookupPath(projectRootPath + "/2024-crescendo.json"), 3, false);
EXPECT_EQ(ret, 0);
}
TEST(Field_CalibrationTest, Atypical_Bad_Camera_Model_Directory) {
int ret = fieldcalibration::calibrate(
projectRootPath + videoLocation, output_json,
projectRootPath + videoLocation + "/long" + fileSuffix,
projectRootPath + "/2024-crescendo.json", 3, false);
LookupPath(projectRootPath + videoLocation), output_json,
LookupPath(projectRootPath + videoLocation + "/long" + fileSuffix),
LookupPath(projectRootPath + "/2024-crescendo.json"), 3, false);
EXPECT_EQ(ret, 1);
}
TEST(Field_CalibrationTest, Atypical_Bad_Ideal_JSON) {
int ret = fieldcalibration::calibrate(
projectRootPath + videoLocation, output_json,
LookupPath(projectRootPath + videoLocation), output_json,
calSavePath + "/cameracalibration.json",
calSavePath + "/cameracalibration.json", 3, false);
EXPECT_EQ(ret, 1);
@@ -84,24 +93,24 @@ TEST(Field_CalibrationTest, Atypical_Bad_Ideal_JSON) {
TEST(Field_CalibrationTest, Atypical_Bad_Input_Directory) {
int ret = fieldcalibration::calibrate(
projectRootPath + "", output_json,
LookupPath(projectRootPath + ""), output_json,
calSavePath + "/cameracalibration.json",
projectRootPath + "/2024-crescendo.json", 3, false);
LookupPath(projectRootPath + "/2024-crescendo.json"), 3, false);
EXPECT_EQ(ret, 1);
}
TEST(Field_CalibrationTest, Atypical_Bad_Pinned_Tag) {
int ret = fieldcalibration::calibrate(
projectRootPath + videoLocation, output_json,
LookupPath(projectRootPath + videoLocation), output_json,
calSavePath + "/cameracalibration.json",
projectRootPath + "/2024-crescendo.json", 42, false);
LookupPath(projectRootPath + "/2024-crescendo.json"), 42, false);
EXPECT_EQ(ret, 1);
}
TEST(Field_CalibrationTest, Atypical_Bad_Pinned_Tag_Negative) {
int ret = fieldcalibration::calibrate(
projectRootPath + videoLocation, output_json,
LookupPath(projectRootPath + videoLocation), output_json,
calSavePath + "/cameracalibration.json",
projectRootPath + "/2024-crescendo.json", -1, false);
LookupPath(projectRootPath + "/2024-crescendo.json"), -1, false);
EXPECT_EQ(ret, 1);
}

View File

@@ -13,6 +13,8 @@
#include <gtest/gtest.h>
#include <mrcal_wrapper.h>
#include "path_lookup.h"
using namespace cv;
struct cmpByFilename {
@@ -147,8 +149,9 @@ std::vector<double> calibrate(const std::string& fname, cv::Size boardSize,
const std::string projectRootPath = PROJECT_ROOT_PATH;
TEST(MrcalResultExactlyMatchesTest, lifecam_1280) {
auto calculated_intrinsics{calibrate(
projectRootPath + "/lifecam_1280p_10x10.vnl", {10, 10}, {1280, 720})};
auto calculated_intrinsics{
calibrate(LookupPath(projectRootPath + "/lifecam_1280p_10x10.vnl"),
{10, 10}, {1280, 720})};
// ## generated with mrgingham --jobs 4 --gridn 10
// /home/mmorley/photonvision/test-resources/calibrationSquaresImg/lifecam/2024-01-02_lifecam_1280/*.png

View File

@@ -102,6 +102,7 @@ filegroup(
third_party_cc_lib_helper(
name = "eigen",
include_root = "src/main/native/thirdparty/eigen/include",
visibility = ["//visibility:public"],
)
third_party_cc_lib_helper(