Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export Drake version in drake-config.cmake #21450

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ add_custom_target(drake_version ALL

add_custom_target(drake_cxx_python ALL
COMMAND "${Bazel_EXECUTABLE}" build ${BAZEL_INSTALL_TARGET}
DEPENDS "${PROJECT_BINARY_DIR}/VERSION.TXT"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/drake_build_cwd"
USES_TERMINAL
)
Expand Down
5 changes: 5 additions & 0 deletions cmake/bazel.rc.in
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,10 @@ try-import @PROJECT_SOURCE_DIR@/user.bazelrc
# Use build-specific bazel options (if present).
try-import @PROJECT_BINARY_DIR@/drake.bazelrc

# Provide the contents of VERSION.TXT in Bazel's stable-status.txt file for use
# by the stamping rules in tools/install/libdrake/BUILD.bazel.
build --stamp
build --workspace_status_command="sed 's/^/STABLE_VERSION /' '@PROJECT_BINARY_DIR@/VERSION.TXT'"

# TODO(jwnimmer-tri) Pass along CMAKE_C_FLAGS and CMAKE_CXX_FLAGS also, and
# specifically make sure the user's -std=c++NN is respected.
44 changes: 37 additions & 7 deletions tools/install/libdrake/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
load("@bazel_skylib//lib:selects.bzl", "selects")
load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
load(
"@drake//tools/workspace:cmake_configure_file.bzl",
"cmake_configure_file",
)
load(
"@python//:version.bzl",
"PYTHON_SITE_PACKAGES_RELPATH",
Expand All @@ -20,6 +25,7 @@ load(
)
load(
"//tools/skylark:drake_py.bzl",
"drake_py_binary",
"drake_py_unittest",
)
load(":build_components.bzl", "LIBDRAKE_COMPONENTS")
Expand All @@ -28,14 +34,38 @@ load(":header_lint.bzl", "cc_check_allowed_headers")
package(default_visibility = ["//visibility:private"])

genrule(
name = "stamp_version",
outs = ["stamp_version.txt"],
cmd_bash = "sed -n '/^STABLE_VERSION/p' bazel-out/stable-status.txt > $@",
stamp = 1,
)

drake_py_binary(
name = "parse_version",
srcs = ["parse_version.py"],
)

run_binary(
name = "drake_parse_version_stamp",
srcs = ["stamp_version.txt"],
outs = ["stamp_version.cmake"],
args = [
"$(location stamp_version.txt)",
"$(location stamp_version.cmake)",
],
tool = "parse_version",
)

cmake_configure_file(
name = "drake_config_cmake_expand",
srcs = ["drake-config.cmake.in"],
outs = ["drake-config.cmake"],
cmd = "sed '" +
"s!@PYTHON_SITE_PACKAGES_RELPATH@!" +
PYTHON_SITE_PACKAGES_RELPATH + "!g;" +
"s!@PYTHON_VERSION@!" + PYTHON_VERSION + "!g;" +
"' $< > $@",
src = "drake-config.cmake.in",
out = "drake-config.cmake",
atonly = True,
cmakelists = ["stamp_version.cmake"],
defines = [
"PYTHON_SITE_PACKAGES_RELPATH=" + PYTHON_SITE_PACKAGES_RELPATH,
"PYTHON_VERSION=" + PYTHON_VERSION,
],
)

install_cmake_config(
Expand Down
15 changes: 11 additions & 4 deletions tools/install/libdrake/drake-config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,19 @@ set_target_properties(drake::drake-marker PROPERTIES

unset(_apple_soname_prologue)

set(drake_LIBRARIES "drake::drake")
set(drake_INCLUDE_DIRS "")
set(${CMAKE_FIND_PACKAGE_NAME}_LIBRARIES "drake::drake")
set(${CMAKE_FIND_PACKAGE_NAME}_INCLUDE_DIRS "")

set(drake_PYTHON_DIR "${${CMAKE_FIND_PACKAGE_NAME}_IMPORT_PREFIX}/@PYTHON_SITE_PACKAGES_RELPATH@")
set(${CMAKE_FIND_PACKAGE_NAME}_VERSION "@DRAKE_VERSION@")
set(${CMAKE_FIND_PACKAGE_NAME}_VERSION_MAJOR "@DRAKE_VERSION_MAJOR@")
set(${CMAKE_FIND_PACKAGE_NAME}_VERSION_MINOR "@DRAKE_VERSION_MINOR@")
set(${CMAKE_FIND_PACKAGE_NAME}_VERSION_PATCH "@DRAKE_VERSION_PATCH@")
set(${CMAKE_FIND_PACKAGE_NAME}_VERSION_TWEAK "@DRAKE_VERSION_TWEAK@")


set(${CMAKE_FIND_PACKAGE_NAME}_PYTHON_DIR "${${CMAKE_FIND_PACKAGE_NAME}_IMPORT_PREFIX}/@PYTHON_SITE_PACKAGES_RELPATH@")
# Allow users to easily check Drake's expected CPython version.
set(drake_PYTHON_VERSION "@PYTHON_VERSION@")
set(${CMAKE_FIND_PACKAGE_NAME}_PYTHON_VERSION "@PYTHON_VERSION@")

unset(${CMAKE_FIND_PACKAGE_NAME}_IMPORT_PREFIX)
unset(CMAKE_IMPORT_FILE_VERSION)
Expand Down
96 changes: 96 additions & 0 deletions tools/install/libdrake/parse_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Parse the version stamp file and produce a CMake cache-style script file
which specifies the variable substitutions needed for drake-config.cmake."""

import argparse
import re

VERSION_TAG = 'STABLE_VERSION'


# Check if a version string conforms to PEP 440.
def _check_version(version):
return re.match(
r'^([1-9][0-9]*!)?(0|[1-9][0-9]*)'
r'(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?'
r'(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?'
r'([+][a-z0-9]+([-_\.][a-z0-9]+)*)?$',
version) is not None


# Extract full version and version parts from version stamp file.
#
# If a version is specified, the input file should contain a line starting with
# 'STABLE_VERSION', which should be three space-separated words; the tag, the
# full version, and the git SHA.
#
# This extracts the (full) version identifier, as well as the individual
# numeric parts (separated by '.') of the version. Any pre-release, 'dev',
# 'post', and/or local identifier (i.e. portion following a '+') is discarded
# when extracting the version parts. If version information is not found,
# this returns (None, None).
def _parse_stamp(stamp_file):
# Read input.
for line in stamp_file:
if line.startswith(VERSION_TAG):
tag, version_full, git_sha = line.strip().split()
assert tag == VERSION_TAG

# Check version format and extract numerical components.
if not _check_version(version_full):
raise ValueError(f'Version {version_full} is not valid')
if re.match(r'^[1-9][0-9]*!', version_full):
raise ValueError(f'Version {version_full} contains an epoch,'
' which is not supported at this time')

m = re.match(r'^[0-9.]+', version_full)
assert m

# Check for sufficient version parts (note: user and continuous
# builds may have more than three parts) and pad to ensure we
# always have four.
version_parts = m.group(0).split('.')
if len(version_parts) < 4:
if len(version_parts) == 3:
version_parts.append(0)
else:
raise ValueError(f'Version {version_full}'
' does not have enough parts')

return version_full, tuple(map(int, version_parts))

return None, None


# Write version information to CMake cache-style script.
def _write_version_info(out, version_full, version_parts):
if version_full is None:
out.write('set(DRAKE_VERSION "unknown")\n')
out.write('set(DRAKE_VERSION_MAJOR "unknown")\n')
out.write('set(DRAKE_VERSION_MINOR "unknown")\n')
out.write('set(DRAKE_VERSION_PATCH "unknown")\n')
out.write('set(DRAKE_VERSION_TWEAK "unknown")\n')
else:
out.write(f'set(DRAKE_VERSION "{version_full}")\n')
out.write(f'set(DRAKE_VERSION_MAJOR "{version_parts[0]}")\n')
out.write(f'set(DRAKE_VERSION_MINOR "{version_parts[1]}")\n')
out.write(f'set(DRAKE_VERSION_PATCH "{version_parts[2]}")\n')
out.write(f'set(DRAKE_VERSION_TWEAK "{version_parts[3]}")\n')


def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'input', type=argparse.FileType('r'),
help='Path to file optionally containing stamp version.')
parser.add_argument(
'output', type=argparse.FileType('w'),
help='Path to output file.')
args = parser.parse_args()

_write_version_info(args.output, *_parse_stamp(args.input))

return 0


if __name__ == '__main__':
main()
10 changes: 10 additions & 0 deletions tools/workspace/cmake_configure_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def _cmake_configure_file_impl(ctx):
arguments += ["--autoconf"]
if ctx.attr.strict:
arguments += ["--strict"]
if ctx.attr.atonly:
arguments += ["--atonly"]
ctx.actions.run(
inputs = ctx.files.srcs + ctx.files.cmakelists,
outputs = ctx.outputs.outs,
Expand All @@ -43,6 +45,7 @@ _cmake_configure_file_gen = rule(
"cmakelists": attr.label_list(allow_files = True),
"autoconf": attr.bool(default = False),
"strict": attr.bool(default = False),
"atonly": attr.bool(default = False),
"cmake_configure_file_py": attr.label(
cfg = "host",
executable = True,
Expand All @@ -65,6 +68,7 @@ def cmake_configure_file(
undefines = None,
cmakelists = None,
strict = None,
atonly = None,
**kwargs):
"""Creates a rule to generate an out= file from a src= file, using CMake's
configure_file substitution semantics. This implementation is incomplete,
Expand All @@ -84,6 +88,9 @@ def cmake_configure_file(
either defines, undefines, or cmakelists is an error. When False, anything
not mentioned is silently presumed to be undefined.

When atonly is True, only substitutions like '@var@' will be made; '${var}'
will be left as-is. When False, both types of substitutions will be made.

See cmake_configure_file.py for our implementation of the configure_file
substitution rules.

Expand All @@ -99,6 +106,7 @@ def cmake_configure_file(
undefines = undefines,
cmakelists = cmakelists,
strict = strict,
atonly = atonly,
env = hermetic_python_env(),
**kwargs
)
Expand All @@ -111,6 +119,7 @@ def cmake_configure_files(
undefines = None,
cmakelists = None,
strict = None,
atonly = None,
**kwargs):
"""Like cmake_configure_file(), but with itemwise pairs of srcs => outs,
instead of just one pair of src => out.
Expand All @@ -126,6 +135,7 @@ def cmake_configure_files(
undefines = undefines,
cmakelists = cmakelists,
strict = strict,
atonly = atonly,
env = hermetic_python_env(),
**kwargs
)
Expand Down
Loading