Skip to content

Commit

Permalink
[workspace] Add reusable macro and documentation for vendor_cxx (Robo…
Browse files Browse the repository at this point in the history
…tLocomotion#17278)

Update yaml_cpp_internal to follow the new style.
  • Loading branch information
jwnimmer-tri authored May 31, 2022
1 parent 5c8672a commit e9ec8ea
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 48 deletions.
2 changes: 1 addition & 1 deletion common/yaml/yaml_read_archive.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <algorithm>
#include <cstring>

#include <drake-yaml-cpp/yaml.h>
#include <drake_vendor/yaml-cpp/yaml.h>
#include <fmt/ostream.h>

#include "drake/common/nice_type_name.h"
Expand Down
4 changes: 2 additions & 2 deletions common/yaml/yaml_write_archive.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
#include <utility>
#include <vector>

#include <drake-yaml-cpp/emitfromevents.h>
#include <drake-yaml-cpp/yaml.h>
#include <drake_vendor/yaml-cpp/emitfromevents.h>
#include <drake_vendor/yaml-cpp/yaml.h>

#include "drake/common/drake_assert.h"
#include "drake/common/unused.h"
Expand Down
11 changes: 11 additions & 0 deletions tools/workspace/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ load("@drake//tools/install:install.bzl", "install")
load(
"@drake//tools/skylark:drake_py.bzl",
"drake_py_binary",
"drake_py_test",
)
load(
"@drake_visualizer//:defs.bzl",
Expand Down Expand Up @@ -52,6 +53,16 @@ drake_py_binary(
deps = [":module_py"],
)

drake_py_test(
name = "vendor_cxx_test",
srcs = [
"vendor_cxx.py",
"vendor_cxx_test.py",
],
allow_import_unittest = True,
deps = [":module_py"],
)

drake_py_binary(
name = "cmake_configure_file",
srcs = ["cmake_configure_file.py"],
Expand Down
68 changes: 68 additions & 0 deletions tools/workspace/vendor_cxx.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# -*- python -*-

def cc_library_vendored(
name,
hdrs = None,
hdrs_vendored = None,
edit_include = None,
srcs = None,
srcs_vendored = None,
**kwargs):
"""
Compiles a third-party C++ library using altered include paths and
namespaces so that it will not interfere with co-habitating builds
of the same library by others.
The lists of hdrs and hdrs_vendored paths must be equal in length and
correspond as elementwise pairs. The hdrs gives the list of library
header file paths as found in the third-party source layout; the
hdrs_vendored gives the list of header file paths to use for Drake's
vendored build. Typically we will prefix "drake_vendor/" to the path.
The edit_include mapping provides #include patterns that should be
rewritten in all of the source files (both hdrs and srcs). The patterns
are implicitly anchored at the start of the #include statements.
For example, {"yaml-cpp/": "drake_vendor/yaml-cpp/"} would edit this line:
#include <yaml-cpp/node.h>
into this line instead:
#include <drake_vendor/yaml-cpp/node.h>
The lists of srcs and srcs_vendored paths must be equal in length and
correspond as elementwise pairs. The srcs gives the list of library
source file paths as found in the third-party source layout; the
srcs_vendored gives the list of source file paths to use for Drake's
vendored build.
"""
hdrs = hdrs or []
hdrs_vendored = hdrs_vendored or []
edit_include = edit_include or {}
srcs = srcs or []
srcs_vendored = srcs_vendored or []
if len(hdrs) != len(hdrs_vendored):
fail("The hdrs= and hdrs_vendored= list lengths must match")
if len(srcs) != len(srcs_vendored):
fail("The srcs= and srcs_vendored= list lengths must match")
native.genrule(
name = "_{}_vendoring".format(name),
srcs = hdrs + srcs,
outs = hdrs_vendored + srcs_vendored,
cmd = " ".join([
"$(execpath @drake//tools/workspace:vendor_cxx)",
] + [
"'--edit-include={}:{}'".format(k, v)
for k, v in edit_include.items()
] + [
"$(execpath {}):$(execpath {})".format(old, new)
for old, new in (zip(hdrs, hdrs_vendored) +
zip(srcs, srcs_vendored))
]),
tools = ["@drake//tools/workspace:vendor_cxx"],
tags = ["manual"],
visibility = ["//visibility:private"],
)
native.cc_library(
name = name,
hdrs = hdrs_vendored,
srcs = srcs_vendored,
**kwargs
)
41 changes: 28 additions & 13 deletions tools/workspace/vendor_cxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
Rewrites the include statements, namespace, and symbol visibility with the goal
of producing a completely independent build of some upstream library, even when
statically linking other versions of the library into the same DSO.
Note that this works only on C++ code, not plain C code.
"""

import argparse


def _rewrite_one(*, old_filename, new_filename, edit_include):
"""Read in old_filename and write into new_filename with specific
alterations:
def _rewrite_one_text(*, text, edit_include):
"""Rewrites the C++ file contents in `text` with specific alterations:
- The paths in #include statements are replaced per the (old, new) pairs in
the include_edit list. Only includes that use quotation marks will be
Expand All @@ -19,14 +20,12 @@ def _rewrite_one(*, old_filename, new_filename, edit_include):
- Wraps an inline namespace "drake_vendor" with hidden symbol visibility
around the entire file; it is withdrawn prior to any include statement.
Returns the new C++ contents.
These changes should suffice for the most typical flavors of C++ code.
Tricks like including non-standalone files (`#include "helpers.inc"`)
may not work.
"""
# Read the original.
with open(old_filename, 'r', encoding='utf-8') as in_file:
text = in_file.read()

# Re-spell the project's own include statements.
for old_inc, new_inc in edit_include:
text = text.replace(f'#include "{old_inc}', f'#include "{new_inc}')
Expand Down Expand Up @@ -54,9 +53,22 @@ def _rewrite_one(*, old_filename, new_filename, edit_include):
+ text[last:])
search_start = last + len(open_inline) + len(close_inline) - 1

return text


def _rewrite_one_file(*, old_filename, new_filename, edit_include):
"""Reads in old_filename and write into new_filename with specific
alterations as described by _rewrite_one_string().
"""
# Read the original.
with open(old_filename, 'r', encoding='utf-8') as in_file:
old_text = in_file.read()

new_text = _rewrite_one_text(text=old_text, edit_include=edit_include)

# Write out the altered file.
with open(new_filename, 'w', encoding='utf-8') as out_file:
out_file.write(text)
out_file.write(new_text)


def _split_pair(arg):
Expand All @@ -69,16 +81,19 @@ def _split_pair(arg):
def _main():
parser = argparse.ArgumentParser()
parser.add_argument(
'--edit-include', action='append', type=_split_pair, metavar='OLD:NEW',
'--edit-include', action='append', default=[],
type=_split_pair, metavar='OLD:NEW',
help='Project-local include spellings rewrite')
parser.add_argument(
'rewrite', nargs='+', type=_split_pair,
help='Filename pairs to rewrite, given as IN:OUT')
args = parser.parse_args()
for old_filename, new_filename in args.rewrite:
_rewrite_one(edit_include=args.edit_include, old_filename=old_filename,
new_filename=new_filename)
_rewrite_one_file(
edit_include=args.edit_include,
old_filename=old_filename,
new_filename=new_filename)


assert __name__ == '__main__'
_main()
if __name__ == '__main__':
_main()
53 changes: 53 additions & 0 deletions tools/workspace/vendor_cxx_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import unittest

from drake.tools.workspace.vendor_cxx import _rewrite_one_text


class TestVendorCxx(unittest.TestCase):

def setUp(self):
# Display all test output.
self.maxDiff = None

# These are the include paths that we want to rewrite.
self._edit_include = {
'somelib/': 'drake_vendor/somelib/',
}

# These are the boilerplate lines that vendor_cxx injects.
self._open = 'inline namespace drake_vendor __attribute__ ((visibility ("hidden"))) {' # noqa
self._close = '} /* inline namespace drake_vendor */'

def _check(self, old_lines, expected_new_lines):
"""Tests one call to _rewrite_one_text for expected output."""
old_text = '\n'.join(old_lines) + '\n'
new_text = _rewrite_one_text(
text=old_text, edit_include=self._edit_include.items())
expected_new_text = '\n'.join(expected_new_lines) + '\n'
self.assertMultiLineEqual(expected_new_text, new_text)

def test_simple(self):
self._check([
'#include "somelib/somefile.h"',
'#include "unrelated/thing.h"',
'int foo();',
], [
# Adjacent pairs of open/close are useless; ideally, we teach
# vendor_cxx to skip these.
self._open, self._close, # TODO(jwnimmer-tri) Useless filler.

# All include statements must NOT be within an open-namespace.
'#include "drake_vendor/somelib/somefile.h"',

self._open, self._close, # TODO(jwnimmer-tri) Useless filler.
'#include "unrelated/thing.h"',

# All code MUST be within an open-namespace.
self._open,
'int foo();',
self._close,
])


assert __name__ == '__main__'
unittest.main()
51 changes: 19 additions & 32 deletions tools/workspace/yaml_cpp_internal/package.BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ load(
"@drake//tools/install:install.bzl",
"install",
)
load(
"@drake//tools/workspace:vendor_cxx.bzl",
"cc_library_vendored",
)

licenses(["notice"]) # MIT

Expand All @@ -20,39 +24,22 @@ _SRCS = glob([
"src/*.h",
], allow_empty = False)

# In the next few stanzas, we'll rewrite the upstream headers and sources to
# use a different include paths and a private namespace. This gives Drake a
# completely independent build of the upstream library, even when statically
# linking other versions of the library into the same DSO.
_VENDORED_HDRS = [
x.replace("include/yaml-cpp/", "include/drake-yaml-cpp/")
for x in _HDRS
]

_VENDORED_SRCS = [
x.replace("src/", "drake-src/")
for x in _SRCS
]

genrule(
name = "vendoring",
srcs = _HDRS + _SRCS,
outs = _VENDORED_HDRS + _VENDORED_SRCS,
cmd = " ".join([
"$(execpath @drake//tools/workspace:vendor_cxx)",
"--edit-include=yaml-cpp/:drake-yaml-cpp/",
] + [
"$(execpath {}):$(execpath {})".format(old, new)
for old, new in zip(_HDRS, _VENDORED_HDRS) + zip(_SRCS, _VENDORED_SRCS)
]),
tools = ["@drake//tools/workspace:vendor_cxx"],
)

cc_library(
cc_library_vendored(
name = "yaml_cpp",
hdrs = _VENDORED_HDRS,
srcs = _VENDORED_SRCS,
includes = ["include"],
srcs = _SRCS,
srcs_vendored = [
x.replace("src/", "drake_src/")
for x in _SRCS
],
hdrs = _HDRS,
hdrs_vendored = [
x.replace("include/yaml-cpp/", "drake_src/drake_vendor/yaml-cpp/")
for x in _HDRS
],
edit_include = {
"yaml-cpp/": "drake_vendor/yaml-cpp/",
},
includes = ["drake_src"],
linkstatic = 1,
visibility = ["//visibility:public"],
)
Expand Down

0 comments on commit e9ec8ea

Please sign in to comment.