diff --git a/BUILD.bazel b/BUILD.bazel index 741fc39c95..834d5d404c 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -76,6 +76,7 @@ publish_all( "//hal:wpiHal-cpp_publish", "//ntcore:ntcore-cpp_publish", "//ntcore:ntcore-java_publish", + "//ntcoreffi:ntcoreffi-cpp_publish", "//outlineviewer:outlineviewer_publish", "//romiVendordep:romiVendordep-cpp_publish", "//romiVendordep:romiVendordep-java_publish", diff --git a/ntcoreffi/BUILD.bazel b/ntcoreffi/BUILD.bazel new file mode 100644 index 0000000000..05029aa0c0 --- /dev/null +++ b/ntcoreffi/BUILD.bazel @@ -0,0 +1,59 @@ +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_python//python:defs.bzl", "py_binary") +load("//shared/bazel/rules:cc_rules.bzl", "wpilib_cc_library", "wpilib_cc_shared_library") +load("//shared/bazel/rules:packaging.bzl", "package_shared_cc_project") + +py_binary( + name = "generate_exported_symbols", + srcs = [ + "generate_exported_symbols.py", + ], +) + +genrule( + name = "do_generate_exported_symbols", + srcs = ["src/main/native/symbols.txt"], + outs = ["src/generated/headers/ExportedSymbols.h"], + cmd = "$(location :generate_exported_symbols) --output $(OUTS) --symbols $(SRCS)", + tools = [":generate_exported_symbols"], +) + +cc_library( + name = "exported_symbols", + hdrs = [ + "src/generated/headers/ExportedSymbols.h", + ], + strip_include_prefix = "src/generated/headers/", +) + +wpilib_cc_library( + name = "ntcoreffi", + srcs = glob([ + "src/main/native/c/**", + "src/main/native/cpp/**", + ]), + hdrs = glob(["src/main/native/include/**"]), + include_license_files = True, + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + ":exported_symbols", + "//ntcore", + "//wpinet", + "//wpiutil", + ], +) + +wpilib_cc_shared_library( + name = "shared/ntcoreffi", + symbols = "src/main/native/symbols.txt", + use_debug_name = False, + visibility = ["//visibility:public"], + deps = [":ntcoreffi"], +) + +package_shared_cc_project( + name = "ntcoreffi", + maven_artifact_name = "ntcoreffi-cpp", + maven_group_id = "edu.wpi.first.ntcoreffi", +) diff --git a/ntcoreffi/generate_exported_symbols.py b/ntcoreffi/generate_exported_symbols.py new file mode 100644 index 0000000000..9328365d5f --- /dev/null +++ b/ntcoreffi/generate_exported_symbols.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +import argparse +from pathlib import Path + + +def generate_symbol_text(output_directory, symbols): + with open(output_directory, "w") as out: + with open(symbols, "r") as f: + for line in f: + out.write(f"AddFunctionToLink({line.strip()});\n") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--symbols", + help="Read the symbols from this file", + type=Path, + ) + parser.add_argument( + "--output", + help="Output the generated symbols to this file", + type=Path, + ) + args = parser.parse_args() + + generate_symbol_text(args.output, args.symbols) + + +if __name__ == "__main__": + main() diff --git a/shared/bazel/rules/cc_rules.bzl b/shared/bazel/rules/cc_rules.bzl index ed31af606e..408d31182a 100644 --- a/shared/bazel/rules/cc_rules.bzl +++ b/shared/bazel/rules/cc_rules.bzl @@ -6,6 +6,7 @@ load("@rules_cc//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_ATTRS", "find_cpp_tool load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_pkg//:mappings.bzl", "pkg_files") load("@rules_pkg//:pkg.bzl", "pkg_zip") +load("//shared/bazel/rules/gen:defs.bzl", "gen_versionscript") # Copied from bazel since it isn't exposed publicly that I can find. # https://github.com/bazelbuild/bazel/blob/cc4e3b25a89cd8294406d9489ece706cfcc019bd/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl#L272 @@ -328,6 +329,8 @@ def wpilib_cc_shared_library( use_debug_name = True, features = None, win_def_file = None, + symbols = None, + additional_linker_inputs = None, **kwargs): """Builds a cc_shared_library with some wpilib conventions applied. @@ -341,6 +344,7 @@ def wpilib_cc_shared_library( user_link_flags: User link flags to add to the linking process. Note: this gets augmented with extra flags to produce libfoo.so or libfood.so if in debug mode. + additional_linker_inputs: Additional inputs to provide to the linking process. use_debug_name: If true, (default): when in debug mode, produce libfood.so, otherwise produce libfoo.so. This matches the wpilib convention for debug library naming. JNI libraries @@ -349,6 +353,7 @@ def wpilib_cc_shared_library( win_def_file: The .def file used to specify symbols used in linking on Windows. This is selected automatically such that it is only used on Windows. + symbols: The symbols to export in the shared library. """ folder, lib = _folder_prefix(name) @@ -356,6 +361,33 @@ def wpilib_cc_shared_library( if not features: features = [] + if symbols: + if win_def_file: + fail("Can't specify symbols and win_def_file") + + exports_target = name + "_exports" + gen_versionscript( + name = exports_target, + src = symbols, + lib_name = lib, + format = select({ + "@platforms//os:linux": "linux", + "@platforms//os:osx": "osx", + "@platforms//os:windows": "windows", + }), + ) + + additional_linker_inputs = (additional_linker_inputs or []) + select({ + "@platforms//os:windows": [], + "//conditions:default": [exports_target], + }) + user_link_flags = (user_link_flags or []) + select({ + "@platforms//os:linux": ["-Wl,--version-script,$(location " + exports_target + ")"], + "@platforms//os:osx": ["-Wl,-exported_symbols_list,$(location " + exports_target + ")"], + "@platforms//os:windows": [], + }) + win_def_file = exports_target + if auto_export_windows_symbols: features.append("windows_export_all_symbols") @@ -379,6 +411,7 @@ def wpilib_cc_shared_library( user_link_flags = user_link_flags, features = features, visibility = visibility, + additional_linker_inputs = additional_linker_inputs, # Only include a .def file on windows. This makes it so we can mark # the .def file as only compatible with windows. win_def_file = select({ diff --git a/shared/bazel/rules/gen/BUILD.bazel b/shared/bazel/rules/gen/BUILD.bazel index 6fb915c7e2..056a079a8c 100644 --- a/shared/bazel/rules/gen/BUILD.bazel +++ b/shared/bazel/rules/gen/BUILD.bazel @@ -1,5 +1,14 @@ load("@rules_python//python:defs.bzl", "py_binary") +exports_files(["defs.bzl"]) + +py_binary( + name = "gen_versionscript", + srcs = ["gen_versionscript.py"], + main = "gen_versionscript.py", + visibility = ["//visibility:public"], +) + py_binary( name = "gen_resources", srcs = ["gen_resources.py"], diff --git a/shared/bazel/rules/gen/defs.bzl b/shared/bazel/rules/gen/defs.bzl new file mode 100644 index 0000000000..dae647773b --- /dev/null +++ b/shared/bazel/rules/gen/defs.bzl @@ -0,0 +1,56 @@ +"""Starlark rule for generating a version script from a symbols file.""" + +def _gen_versionscript_impl(ctx): + ext = ".txt" + if ctx.attr.format == "windows": + ext = ".def" + elif ctx.attr.format == "osx": + ext = ".list" + + output_file = ctx.actions.declare_file(ctx.label.name + ext) + + ctx.actions.run( + outputs = [output_file], + inputs = [ctx.file.src], + executable = ctx.executable._tool, + arguments = [ + "--input", + ctx.file.src.path, + "--output", + output_file.path, + "--lib_name", + ctx.attr.lib_name, + "--format", + ctx.attr.format, + ], + mnemonic = "GenVersionScript", + progress_message = "Generating version script for %{label}", + ) + + return [DefaultInfo(files = depset([output_file]), runfiles = ctx.runfiles(files = [output_file])), OutputGroupInfo(version_script_file = depset([output_file]))] + +gen_versionscript = rule( + implementation = _gen_versionscript_impl, + attrs = { + "format": attr.string( + mandatory = True, + values = ["linux", "windows", "osx"], + doc = "The output format.", + ), + "lib_name": attr.string( + mandatory = True, + doc = "The name of the library.", + ), + "src": attr.label( + mandatory = True, + allow_single_file = True, + doc = "The input symbols file.", + ), + "_tool": attr.label( + default = Label("//shared/bazel/rules/gen:gen_versionscript"), + executable = True, + cfg = "exec", + ), + }, + doc = "Generates a version script from a symbols file.", +) diff --git a/shared/bazel/rules/gen/gen_versionscript.py b/shared/bazel/rules/gen/gen_versionscript.py new file mode 100644 index 0000000000..71b17ab60e --- /dev/null +++ b/shared/bazel/rules/gen/gen_versionscript.py @@ -0,0 +1,49 @@ +import argparse + +# Quick script to generate a version script for each OS from a list of symbols. + + +def main(): + parser = argparse.ArgumentParser(description="Generate a version script.") + parser.add_argument("--input", required=True, help="Input symbols file") + parser.add_argument("--output", required=True, help="Output version script file") + parser.add_argument( + "--lib_name", required=True, help="Name of the library for the version script" + ) + parser.add_argument( + "--format", + required=True, + choices=["linux", "windows", "osx"], + help="Output format", + ) + args = parser.parse_args() + + with open(args.input, "r") as f: + symbols = f.read().splitlines() + + with open(args.output, "w") as f: + if args.format == "linux": + f.write(f"{args.lib_name} {{\n") + f.write(" global: ") + for symbol in symbols: + symbol = symbol.strip() + if symbol: + f.write(f"{symbol}; ") + f.write("\n local: *;\n") + f.write("};\n") + elif args.format == "windows": + f.write(f"LIBRARY {args.lib_name}\n") + f.write("EXPORTS\n") + for symbol in symbols: + symbol = symbol.strip() + if symbol: + f.write(f" {symbol}\n") + elif args.format == "osx": + for symbol in symbols: + symbol = symbol.strip() + if symbol: + f.write(f"_{symbol}\n") + + +if __name__ == "__main__": + main()