From 2aaece31581342980a234b3e9c1194b2d6107eab Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Mon, 6 Mar 2023 13:52:14 -0600 Subject: [PATCH] Support for bazel in garden (#325) Signed-off-by: Michael Carroll --- BUILD.bazel | 128 +++++++++++---------------------- src/CMakeLists.txt | 35 ++++++--- src/Factory_TEST.cc | 15 ++-- test/BUILD.bazel | 59 --------------- tools/BUILD.bazel | 7 ++ tools/gz_msgs_generate.bzl | 104 +++++++++++++++++++++++++++ tools/gz_msgs_generate.py | 144 ++++++++++++++++++++----------------- 7 files changed, 265 insertions(+), 227 deletions(-) delete mode 100644 test/BUILD.bazel create mode 100644 tools/BUILD.bazel create mode 100644 tools/gz_msgs_generate.bzl diff --git a/BUILD.bazel b/BUILD.bazel index 38051404..9e8e3c84 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,46 +1,32 @@ load( - "//gz_bazel:build_defs.bzl", + "@gz//bazel/skylark:build_defs.bzl", + "GZ_FEATURES", "GZ_ROOT", "GZ_VISIBILITY", - "cmake_configure_file", - "generate_include_header", - "generate_yaml", - "gz_config_header", + "gz_configure_header", "gz_export_header", + "gz_include_header", ) load( - ":gz_msg_gen.bzl", + "@gz//msgs/tools:gz_msgs_generate.bzl", "get_proto_headers", - "gz_msg_gen", + "gz_msgs_generate", ) package( default_visibility = GZ_VISIBILITY, - features = [ - "-parse_headers", - "-layering_check", - ], + features = GZ_FEATURES, ) -licenses(["notice"]) +licenses(["notice"]) # Apache-2.0 exports_files(["LICENSE"]) -PROJECT_NAME = "gz-msgs" - -PROJECT_MAJOR = 8 - -PROJECT_MINOR = 0 - -PROJECT_PATCH = 0 - -# Generates config.hh based on the version numbers in CMake code. -gz_config_header( - name = "config", +gz_configure_header( + name = "msgs_config_hh", src = "include/gz/msgs/config.hh.in", cmakelists = ["CMakeLists.txt"], - project_name = PROJECT_NAME, - project_version = (PROJECT_MAJOR, PROJECT_MINOR, PROJECT_PATCH), + package = "msgs", ) gz_export_header( @@ -57,15 +43,15 @@ public_headers_no_gen = glob([ protos = glob(["proto/gz/msgs/*.proto"]) -generate_include_header( - name = "messagetypeshh_genrule", +gz_include_header( + name = "messagetypes_hh_genrule", out = "include/gz/msgs/MessageTypes.hh", hdrs = get_proto_headers(protos), strip_prefix = ["gz_msgs"], ) -generate_include_header( - name = "msghh_genrule", +gz_include_header( + name = "msgs_hh_genrule", out = "include/gz/msgs.hh", hdrs = public_headers_no_gen + [ "include/gz/msgs/config.hh", @@ -100,89 +86,55 @@ proto_library( name = "gzmsgs_proto", srcs = protos, strip_import_prefix = "proto", + deps = [ + "@com_google_protobuf//:any_proto", + ], ) -# Create a library of our protobuf message files -proto_library( - name = "gzmsgs_proto_public", - srcs = protos, - strip_import_prefix = "proto", -) - -# Generate our custom CC files from the protos -gz_msg_gen( - name = "gzmsgs_proto_cc", - deps = [":gzmsgs_proto"], +gz_msgs_generate( + name = "gzmsgs_cc_proto", + deps = [ + ":gzmsgs_proto", + "@com_google_protobuf//:any_proto", + ], ) cc_library( - name = "gz_msgs", + name = "msgs", srcs = [ "src/Factory.cc", "src/Filesystem.cc", "src/Utility.cc", - ":gzmsgs_proto_cc", + ":gzmsgs_cc_proto", ], hdrs = public_headers, includes = ["include"], deps = [ - ":gzmsgs_proto_cc", - GZ_ROOT + "gz_math", + ":gzmsgs_cc_proto", + GZ_ROOT + "math", "@com_google_protobuf//:protobuf", "@tinyxml2", ], ) -# use shared library only when absolutely needd -cc_binary( - name = "libgz-msgs.so", - srcs = [ - "src/gz.cc", - "src/gz.hh", - ], - includes = ["include"], - linkshared = True, - linkstatic = True, - deps = [ - ":gz_msgs", - ], +test_sources = glob( + include = ["src/*_TEST.cc"], + exclude = [], ) [cc_test( name = src.replace("/", "_").replace(".cc", "").replace("src_", ""), srcs = [src], - data = [GZ_ROOT + "gz_msgs/test:desc/stringmsg.desc"], + data = [ + "test/desc", + ], + defines = [ + 'GZ_MSGS_TEST_PATH=\\"msgs/test\\"', + ], deps = [ - ":gz_msgs", - GZ_ROOT + "gz_math", - GZ_ROOT + "gz_msgs/test:test_utils", + ":msgs", + GZ_ROOT + "common/testing", "@gtest", "@gtest//:gtest_main", ], -) for src in glob( - [ - "src/*_TEST.cc", - ], -)] - -cmake_configure_file( - name = "msgs.rb", - src = "src/cmd/cmdmsgs.rb.in", - out = "cmdmsgs.rb", - cmakelists = ["CMakeLists.txt"], - defines = [ - "library_location=libgz-msgs.so", - "PROJECT_VERSION_FULL=%d.%d.%d" % (PROJECT_MAJOR, PROJECT_MINOR, PROJECT_PATCH), # noqa - "GZ_LIBRARY_NAME=%s" % [PROJECT_NAME], - ], -) - -CMDS = " - msg : Print information about messages." - -generate_yaml( - name = "msgs", - commands = CMDS, - library_name = PROJECT_NAME, - library_version = "%d.%d.%d" % (PROJECT_MAJOR, PROJECT_MINOR, PROJECT_PATCH), - ruby_target = "msgs.rb", -) +) for src in test_sources] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 56499bc8..236f12f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -108,19 +108,31 @@ function(gz_msgs_protoc) set(${gz_msgs_protoc_OUTPUT_RUBY_VAR} ${${gz_msgs_protoc_OUTPUT_RUBY_VAR}} PARENT_SCOPE) endif() - add_custom_command( - OUTPUT ${output_files} - COMMAND Python3::Interpreter - ARGS tools/gz_msgs_generate.py + + set(GENERATE_ARGS --protoc-exec "$" --gz-generator-bin "${GZ_MSGS_GEN_EXECUTABLE}" - --generate-cpp "${gz_msgs_protoc_GENERATE_CPP}" - --generate-ruby "${gz_msgs_protoc_GENERATE_RUBY}" - --generate-ignition "TRUE" - --output-cpp-path "${gz_msgs_protoc_OUTPUT_CPP_DIR}" - --output-ruby-path "${gz_msgs_protoc_OUTPUT_RUBY_DIR}" --proto-path "${gz_msgs_protoc_PROTO_PATH}" --input-path "${ABS_FIL}" + --generate-ignition + ) + + if(${gz_msgs_protoc_GENERATE_CPP}) + list(APPEND GENERATE_ARGS + --generate-cpp + --output-cpp-path "${gz_msgs_protoc_OUTPUT_CPP_DIR}") + endif() + + if(${gz_msgs_protoc_GENERATE_RUBY}) + list(APPEND GENERATE_ARGS + --generate-ruby + --output-ruby-path "${gz_msgs_protoc_OUTPUT_RUBY_DIR}") + endif() + + add_custom_command( + OUTPUT ${output_files} + COMMAND Python3::Interpreter + ARGS tools/gz_msgs_generate.py ${GENERATE_ARGS} DEPENDS ${ABS_FIL} gz_msgs_gen @@ -270,6 +282,11 @@ gz_build_tests(TYPE UNIT TINYXML2::TINYXML2 ) +if (TARGET UNIT_Factory_TEST) + target_compile_definitions(UNIT_Factory_TEST + PRIVATE GZ_MSGS_TEST_PATH="${PROJECT_SOURCE_DIR}/test") +endif() + ################################################## # gz msgs command if(NOT WIN32) diff --git a/src/Factory_TEST.cc b/src/Factory_TEST.cc index 500bf4ef..82b9bb77 100644 --- a/src/Factory_TEST.cc +++ b/src/Factory_TEST.cc @@ -16,17 +16,19 @@ */ #include + #include #include +#include #include "gz/msgs/vector3d.pb.h" #include "gz/msgs/serialized_map.pb.h" - #include "gz/msgs/Factory.hh" -#include "test_config.hh" using namespace gz; +static constexpr const char * kMsgsTestPath = GZ_MSGS_TEST_PATH; + ///////////////////////////////////////////////// TEST(FactoryTest, Type) { @@ -72,11 +74,12 @@ TEST(FactoryTest, NewDynamicFactory) auto msg = msgs::Factory::New("example.msgs.StringMsg"); EXPECT_TRUE(msg.get() == nullptr); - paths = - PROJECT_SOURCE_PATH "/test/desc:" - PROJECT_SOURCE_PATH "/test"; - msgs::Factory::LoadDescriptors(paths); + std::filesystem::path test_path(kMsgsTestPath); + paths += (test_path / "desc").string(); + paths += ":"; + paths += test_path.string(); + msgs::Factory::LoadDescriptors(paths); msg = msgs::Factory::New("example.msgs.StringMsg"); EXPECT_TRUE(msg.get() != nullptr); } diff --git a/test/BUILD.bazel b/test/BUILD.bazel deleted file mode 100644 index f32c1a9a..00000000 --- a/test/BUILD.bazel +++ /dev/null @@ -1,59 +0,0 @@ -load( - "//gz_bazel:build_defs.bzl", - "GZ_ROOT", - "GZ_VISIBILITY", - "cmake_configure_file", -) - -package( - default_visibility = GZ_VISIBILITY, - features = [ - "-layering_check", - ], -) - -licenses(["notice"]) - -exports_files(["LICENSE"]) - -MSGS_DIR = "./gz_msgs/" - -# Generates config.hh based on the version numbers in CMake code. -cmake_configure_file( - name = "test_config", - src = "test_config.h.in", - out = "gz/msgs/test_config.h", - cmakelists = ["CMakeLists.txt"], - defines = [ - "CMAKE_BINARY_DIR=%s" % (MSGS_DIR), - "PROJECT_BINARY_DIR=%s" % (MSGS_DIR), - "PROJECT_SOURCE_DIR=%s" % (MSGS_DIR), - ], - visibility = ["//visibility:private"], -) - -cc_library( - name = "test_utils", - srcs = ["gz/msgs/test_config.h"], - includes = [".", "gz"], -) - -[ - cc_test( - name = "INTEGRATION_" + test.split("/")[1].replace(".cc", ""), - srcs = [test], - includes = [ - ".", - "integration", - ], - deps = [ - ":test_utils", - GZ_ROOT + "gz_msgs", - "@gtest", - "@gtest//:gtest_main", - ], - ) - for test in glob(["integration/*.cc"]) -] - -exports_files(["desc/stringmsg.desc"]) diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel new file mode 100644 index 00000000..8bdd14a8 --- /dev/null +++ b/tools/BUILD.bazel @@ -0,0 +1,7 @@ +load("@gz//bazel/skylark:gz_py.bzl", "gz_py_binary") + +gz_py_binary( + name = "gz_msgs_generate_py", + srcs = ["gz_msgs_generate.py"], + visibility = ["//visibility:public"], +) diff --git a/tools/gz_msgs_generate.bzl b/tools/gz_msgs_generate.bzl new file mode 100644 index 00000000..862fcab6 --- /dev/null +++ b/tools/gz_msgs_generate.bzl @@ -0,0 +1,104 @@ +load("@rules_proto//proto:defs.bzl", "ProtoInfo") +load( + "@gz//bazel/skylark:protobuf.bzl", + "declare_out_files", + "get_include_directory", + "get_out_dir", + "proto_path_to_generated_filename", + "protos_from_context", +) + +_VIRTUAL_IMPORTS = "/_virtual_imports/" + +def _get_detail_directory(hh_file, ctx): + base = hh_file.path[hh_file.path.index(_VIRTUAL_IMPORTS) + 1:] + base = base.split("/") + base.insert(-1, "details") + return ctx.actions.declare_file("/".join(base)) + +def get_proto_headers(protos): + out = [] + for proto in protos: + split = proto.split("/")[1:] + split[2] = split[2].replace(".proto", ".pb.h") + out.append("/".join(split)) + return out + +def _gz_msgs_generate_impl(ctx): + protos = protos_from_context(ctx) + out_dir = get_out_dir(protos, ctx) + + include_dirs = depset([get_include_directory(proto) for proto in protos]) + + arguments = [ + "--protoc-exec=" + ctx.executable._protoc.path, + "--gz-generator-bin=" + ctx.executable._gz_gen_bin.path, + "--generate-cpp", + "--output-cpp-path=" + out_dir.path, + ] + + for proto in protos: + arguments.append("--input-path=" + proto.path) + + for include_dir in include_dirs.to_list(): + arguments.append("--proto-path=" + include_dir) + + out_protos = [proto for proto in protos if proto.path.find("gz/msgs") > 0] + + cc_files = declare_out_files(out_protos, ctx, "{}.pb.cc") + hh_files = declare_out_files(out_protos, ctx, "{}.pb.h") + detail_hh_files = [_get_detail_directory(file, ctx) for file in hh_files] + out_files = cc_files + hh_files + detail_hh_files + + ctx.actions.run( + inputs = protos, + outputs = out_files, + arguments = arguments, + executable = ctx.executable.gz_msgs_generate_py, + tools = [ctx.executable._protoc, ctx.executable._gz_gen_bin, ctx.executable.gz_msgs_generate_py], + ) + + compilation_context = cc_common.create_compilation_context( + headers = depset(out_files), + system_includes = depset([out_dir.path]), + ) + + return [ + DefaultInfo(files = depset(out_files)), + CcInfo(compilation_context = compilation_context), + ] + +_gz_msgs_generate_gen = rule( + attrs = { + "deps": attr.label_list( + mandatory = True, + allow_empty = False, + providers = [ProtoInfo], + ), + "_protoc": attr.label( + default = Label("@com_google_protobuf//:protoc"), + executable = True, + cfg = "host", + ), + "_gz_gen_bin": attr.label( + default = Label("@gz//msgs:gz_msgs_gen"), + executable = True, + cfg = "host", + ), + "gz_msgs_generate_py": attr.label( + default = Label("@gz//msgs/tools:gz_msgs_generate_py"), + executable = True, + cfg = "host", + ), + }, + output_to_genfiles = True, + implementation = _gz_msgs_generate_impl, +) + +def gz_msgs_generate( + deps, + **kwargs): + _gz_msgs_generate_gen( + deps = deps, + **kwargs + ) diff --git a/tools/gz_msgs_generate.py b/tools/gz_msgs_generate.py index a86bfd6e..ef4a48f7 100755 --- a/tools/gz_msgs_generate.py +++ b/tools/gz_msgs_generate.py @@ -32,13 +32,16 @@ def main(argv=sys.argv[1:]): help='The path to the gz specific protobuf generator') parser.add_argument( '--generate-cpp', - help='Flag to indicate if C++ bindings should be generated') + help='Flag to indicate if C++ bindings should be generated', + action='store_true') parser.add_argument( '--generate-ruby', - help='Flag to indicate if Ruby bindings should be generated') + help='Flag to indicate if Ruby bindings should be generated', + action='store_true') parser.add_argument( '--generate-ignition', - help='Flag to indicate if ignition/ headers should be generated') + help='Flag to indicate if ignition/ headers should be generated', + action='store_true') parser.add_argument( '--output-cpp-path', help='The basepath of the generated C++ files') @@ -48,71 +51,82 @@ def main(argv=sys.argv[1:]): parser.add_argument( '--proto-path', required=True, - help='The location of the protos') + help='The location of the protos', + action='append') parser.add_argument( '--input-path', required=True, - help='The location of the template files') + help='The location of the template files', + action='append') args = parser.parse_args(argv) - # First generate the base cpp and ruby files - cmd = [args.protoc_exec] - cmd += [f'--proto_path={args.proto_path}'] - - if args.generate_cpp: - cmd += [f'--plugin=protoc-gen-ignmsgs={args.gz_generator_bin}'] - cmd += [f'--cpp_out=dllexport_decl=IGNITION_MSGS_VISIBLE:{args.output_cpp_path}'] - cmd += [f'--ignmsgs_out={args.output_cpp_path}'] - if args.generate_ruby: - cmd += [f'--ruby_out=dllexport_decl=IGNITION_MSGS_VISIBLE:{args.output_ruby_path}'] - cmd += [args.input_path] - - try: - subprocess.check_call(cmd) - except subprocess.CalledProcessError as e: - print(f'Failed to execute protoc compiler: {e}') - sys.exit(-1) - - # Move original generated cpp to details/ - proto_file = os.path.splitext(os.path.relpath(args.input_path, args.proto_path))[0] - detail_proto_file = proto_file.split(os.sep) - - detail_proto_dir = detail_proto_file[:-1] - detail_proto_dir.append('details') - detail_proto_dir = os.path.join(*detail_proto_dir) - detail_proto_file.insert(-1, 'details') - detail_proto_file = os.path.join(*detail_proto_file) - - header = os.path.join(args.output_cpp_path, proto_file + ".pb.h") - gz_header = os.path.join(args.output_cpp_path, proto_file + ".gz.h") - detail_header = os.path.join(args.output_cpp_path, detail_proto_file + ".pb.h") - - try: - os.makedirs(os.path.join(args.output_cpp_path, detail_proto_dir), - exist_ok=True) - # Windows cannot rename a file to an existing file - if os.path.exists(detail_header): - os.remove(detail_header) - os.rename(header, detail_header) - os.rename(gz_header, header) - except e: - print(f'Failed to manipulate gz-msgs headers: {e}') - sys.exit(-1) - - ignition_header_dir = os.path.join(args.output_cpp_path, 'ignition', 'msgs') - ignition_header = proto_file.split(os.sep) - ignition_header[0] = 'ignition' - - proto_name = ignition_header[2] - - ignition_header = os.path.join(*ignition_header) - ignition_header = os.path.join(args.output_cpp_path, ignition_header + ".pb.h") - - os.makedirs(os.path.join(args.output_cpp_path, ignition_header_dir), - exist_ok=True) - - with open(ignition_header, 'w') as f: - f.write('''/* + for input_file in args.input_path: + # First generate the base cpp and ruby files + cmd = [args.protoc_exec] + + for pp in args.proto_path: + cmd += [f'--proto_path={pp}'] + + if args.generate_cpp: + cmd += [f'--plugin=protoc-gen-ignmsgs={args.gz_generator_bin}'] + cmd += [f'--cpp_out=dllexport_decl=GZ_MSGS_VISIBLE:{args.output_cpp_path}'] + cmd += [f'--ignmsgs_out={args.output_cpp_path}'] + if args.generate_ruby: + cmd += [f'--ruby_out=dllexport_decl=GZ_MSGS_VISIBLE:{args.output_ruby_path}'] + cmd += [input_file] + + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError as e: + print(f'Failed to execute protoc compiler: {e}') + sys.exit(-1) + + # Move original generated cpp to details/ + proto_file = os.path.splitext(os.path.relpath(input_file, args.proto_path[0]))[0] + detail_proto_file = proto_file.split(os.sep) + + detail_proto_dir = detail_proto_file[:-1] + detail_proto_dir.append('details') + detail_proto_dir = os.path.join(*detail_proto_dir) + detail_proto_file.insert(-1, 'details') + detail_proto_file = os.path.join(*detail_proto_file) + + header = os.path.join(args.output_cpp_path, proto_file + ".pb.h") + gz_header = os.path.join(args.output_cpp_path, proto_file + ".gz.h") + detail_header = os.path.join(args.output_cpp_path, detail_proto_file + ".pb.h") + + if proto_file.find('google/protobuf') >= 0: + continue + + try: + os.makedirs(os.path.join(args.output_cpp_path, detail_proto_dir), + exist_ok=True) + # Windows cannot rename a file to an existing file + if os.path.exists(detail_header): + os.remove(detail_header) + + os.rename(header, detail_header) + os.rename(gz_header, header) + except Exception as e: + print(f'Failed to manipulate gz-msgs headers: {e}') + sys.exit(-1) + + + if args.generate_ignition: + ignition_header_dir = os.path.join(args.output_cpp_path, 'ignition', 'msgs') + ignition_header = proto_file.split(os.sep) + ignition_header[0] = 'ignition' + + proto_name = ignition_header[2] + + ignition_header = os.path.join(*ignition_header) + ignition_header = os.path.join(args.output_cpp_path, ignition_header + ".pb.h") + + os.makedirs(os.path.join(args.output_cpp_path, ignition_header_dir), + exist_ok=True) + + with open(ignition_header, 'w') as f: + f.write('''/* * Copyright (C) 2022 Open Source Robotics Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -129,8 +143,8 @@ def main(argv=sys.argv[1:]): * */ ''') - f.write(f'#include \n') - f.write('#include \n') + f.write(f'#include \n') + f.write('#include \n') if __name__ == '__main__': sys.exit(main())