Skip to content

Commit

Permalink
python3: Support Python2 for lcm when being used by drake_visualizer
Browse files Browse the repository at this point in the history
  • Loading branch information
EricCousineau-TRI committed Oct 17, 2018
1 parent d667f77 commit 1945eab
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 38 deletions.
2 changes: 2 additions & 0 deletions tools/workspace/default.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ def add_default_repositories(excludes = [], mirrors = DEFAULT_MIRRORS):
pycps_repository(name = "pycps", mirrors = mirrors)
if "python" not in excludes:
python_repository(name = "python")
if "python2" not in excludes:
python_repository(name = "python2", compat_version = "2")
if "python3" not in excludes:
python3_repository(name = "python3")
if "qdldl" not in excludes:
Expand Down
2 changes: 1 addition & 1 deletion tools/workspace/drake_visualizer/repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ licenses([
py_library(
name = "drake_visualizer_python_deps",
deps = [
"@lcm//:lcm-python",
"@lcm//:lcm-python2",
"@lcmtypes_bot2_core//:lcmtypes_bot2_core_py",
# TODO(eric.cousineau): Expose VTK Python libraries here for Linux.
"@lcmtypes_robotlocomotion//:lcmtypes_robotlocomotion_py",
Expand Down
9 changes: 9 additions & 0 deletions tools/workspace/lcm/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- python -*-

load("//tools/lint:lint.bzl", "add_lint_tests")
load("@drake//tools/skylark:drake_py.bzl", "drake_py_unittest")

exports_files(
[
Expand All @@ -10,4 +11,12 @@ exports_files(
visibility = ["@lcm//:__pkg__"],
)

drake_py_unittest(
name = "lcm_py2_py3_test",
deps = [
"@lcm//:lcm-python",
"@lcm//:lcm-python2",
],
)

add_lint_tests(python_lint_extra_srcs = ["test/lcm-gen_install_test.py"])
82 changes: 61 additions & 21 deletions tools/workspace/lcm/package.BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ load(
"install_files",
)
load("@drake//tools/lint:python_lint.bzl", "python_lint")
load(
"@drake//tools/workspace/python:py2_py3.bzl",
"py2_py3",
"py_select",
)

licenses([
"notice", # BSD-3-Clause
Expand Down Expand Up @@ -169,8 +174,20 @@ cc_binary(
],
)

cc_binary(
name = "_lcm.so",
py2_py3(
rule = cc_binary,
name = "_lcm.@PYTHON_EXT_SUFFIX@",
deps = py_select(
py2 = [
":lcm",
"@python2//:python",
],
py3 = [
":lcm",
# N.B. This will only run when @python refers to Python3.
"@python//:python",
],
),
srcs = [
"lcm-python/module.c",
"lcm-python/pyeventlog.c",
Expand All @@ -184,10 +201,6 @@ cc_binary(
linkshared = 1,
linkstatic = 1,
visibility = ["//visibility:private"],
deps = [
":lcm",
"@python",
],
)

# Downstream users of lcm-python expect to say "import lcm". However, in the
Expand Down Expand Up @@ -218,11 +231,16 @@ cc_binary(
generate_file(
name = "__init__.py",
content = """
import ctypes
import os.path
from ctypes.dll import LoadLibrary
from os.path import realpath
from sysconfig import get_config_var
import six
ctypes.cdll.LoadLibrary(os.path.realpath(__path__[0] + '/_lcm.so'))
_ext = get_config_var("EXT_SUFFIX") or get_config_var("SO")
LoadLibrary(realpath(__path__[0] + '/_lcm' + _ext))
_filename = __path__[0] + \"/lcm-python/lcm/__init__.py\"
if six.PY2:
execfile(_filename)
else:
Expand All @@ -233,17 +251,26 @@ else:
visibility = ["//visibility:private"],
)

py_library(
name = "lcm-python-upstream",
py2_py3(
rule = py_library,
name = py_select(
py2 = "lcm-python2-upstream",
py3 = "lcm-python3-upstream",
),
data = ["_lcm.@PYTHON_EXT_SUFFIX@"],
srcs = ["lcm-python/lcm/__init__.py"], # Actual code from upstream.
data = [":_lcm.so"],
visibility = ["//visibility:private"],
)

py_library(
name = "lcm-python",
py2_py3(
rule = py_library,
alias = "lcm-python",
name = py_select(py2 = "lcm-python2", py3 = "lcm-python3"),
deps = py_select(
py2 = [":lcm-python2-upstream"],
py3 = [":lcm-python3-upstream"],
),
srcs = ["__init__.py"], # Shim, from the genrule above.
deps = [":lcm-python-upstream"],
)

java_library(
Expand Down Expand Up @@ -298,13 +325,23 @@ install_files(
strip_prefix = ["**/"],
)

install(
name = "install_python",
targets = [
":_lcm.so",
":lcm-python-upstream",
],
py2_py3(
rule = install,
alias = "install_python",
name = py_select(py2 = "install_python2", py3 = "install_python3"),
targets = py_select(
py2 = [
"_lcm.@PYTHON_EXT_SUFFIX@",
"lcm-python2-upstream",
],
py3 = [
"_lcm.@PYTHON_EXT_SUFFIX@",
"lcm-python3-upstream",
],
),
library_dest = "@PYTHON_SITE_PACKAGES@/lcm",
# Re-declare for `py2_py3` to intercept.
py_dest = "@PYTHON_SITE_PACKAGES@",
py_strip_prefix = ["lcm-python"],
)

Expand Down Expand Up @@ -340,7 +377,10 @@ install(
deps = [
":install_cmake_config",
":install_extra_cmake",
# Using an alias enables us to install correctly for both Python2 and
# Python3.
":install_python",
":install_python2",
],
)

Expand Down
11 changes: 11 additions & 0 deletions tools/workspace/lcm/test/lcm_py2_py3_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from subprocess import check_call
import unittest

# This will check the Bazel Python version (2 or 3).
from lcm import LCM


class TestLcmPython2and3(unittest.TestCase):
def test_python2(self):
# Explicitly check Python2.
check_call(["python2", "-c", "from lcm import LCM"])
107 changes: 107 additions & 0 deletions tools/workspace/python/py2_py3.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
load(
"@python//:version.bzl",
"PYTHON_EXT_SUFFIX",
"PYTHON_SITE_PACKAGES_RELPATH",
"PYTHON_VERSION",
)
load(
"@python2//:version.bzl",
PYTHON2_EXT_SUFFIX = "PYTHON_EXT_SUFFIX",
PYTHON2_SITE_PACKAGES_RELPATH = "PYTHON_SITE_PACKAGES_RELPATH",
PYTHON2_VERSION = "PYTHON_VERSION",
)

_BAZEL = struct(
version_field = "py" + PYTHON_VERSION.split(".")[0],
ext_suffix = PYTHON_EXT_SUFFIX,
major = PYTHON_VERSION.split(".")[0],
site_packages = PYTHON_SITE_PACKAGES_RELPATH,
)
_PY2 = struct(
version_field = "py2",
ext_suffix = PYTHON2_EXT_SUFFIX,
major = PYTHON2_VERSION.split(".")[0],
site_packages = PYTHON2_SITE_PACKAGES_RELPATH,
)

# Cannot use `type(x) == str`, etc., but can infer from literals?
_list = type([])
_str = type("")
_struct = type(struct())

def py_select(py2, py3):
return struct(py2 = py2, py3 = py3)

def py2_py3(
rule,
alias = None,
**kwargs):
"""
Provides Bazel Python and Python2 targets, without duplication or conflict.
If Bazel is using Python2, only a Python2 target will be defined.
If Bazel using Python3, both a Python2 and Python3 target will be defined.
@param rule
Starlark rule (e.g. `py_library`, `cc_binary`).
@param alias
If specified, will create an alias to the version of the target for
Bazel.
@param kwargs
Arguments for the rule.
Arguments are resolved in the following fashion:
* If an argument's value comes from `py_select`, it will be replaced
based on the Python version using the `py2` or `py3` values.
* Strings have the following tokens replaced:
@PYTHON_SITE_PACKAGES@
@PYTHON_EXT_SUFFIX@
"""

# Define Python2 target.
if _PY2.major != "2":
fail("@python2 has the wrong major version: {}".format(_PY2))
kwargs_py2 = _format(kwargs, _PY2)
rule(**kwargs_py2)
alias_actual = kwargs_py2["name"]

if _BAZEL.major == "3":
# Define Python3 target.
kwargs_py3 = _format(kwargs, _BAZEL)
alias_actual = kwargs_py3["name"]
rule(**kwargs_py3)

# Define alias.
if alias:
native.alias(
name = alias,
actual = alias_actual,
visibility = kwargs_py2.get("visibility"),
)

def _format(raw, build):
# Format a dict of arguments for `py2_py3`.
out = dict()
for key, value in raw.items():
if type(value) == _struct:
value = getattr(value, build.version_field)
out[key] = _format_value(value, build)
return out

def _format_value(value, build):
# N.B. `str` and `list` do not work in this comparison.
if type(value) == _str:
return _format_str(value, build)
elif type(value) == _list:
return [_format_str(x, build) for x in value]
else:
return value

def _format_str(value, build):
subs = (
("@PYTHON_SITE_PACKAGES@", build.site_packages),
("@PYTHON_EXT_SUFFIX@", build.ext_suffix),
)
for old, new in subs:
value = value.replace(old, new)
return value
Loading

0 comments on commit 1945eab

Please sign in to comment.