Merge "Make matplotlibcpp work with upstream Python"
diff --git a/third_party/matplotlib-cpp/BUILD b/third_party/matplotlib-cpp/BUILD
index 6221abb..bd10366 100644
--- a/third_party/matplotlib-cpp/BUILD
+++ b/third_party/matplotlib-cpp/BUILD
@@ -5,16 +5,28 @@
     hdrs = [
         "matplotlibcpp.h",
     ],
-    data = [
-        "@matplotlib_repo//:matplotlib3",
-        "@python_repo//:all_files",
+    data = select({
+        "//tools/platforms/python:debian_bundled_python": [
+            "@matplotlib_repo//:matplotlib3",
+        ],
+        "//tools/platforms/python:upstream_bundled_python": [
+            "@pip//matplotlib",
+            "@pip//pygobject",
+        ],
+    }) + [
+        "//third_party/python:python_runtime",
     ],
     # While this is technically compatible with "linux", the
     # "@python_repo//:all_files" has x86 binaries in it.
     target_compatible_with = ["@platforms//cpu:x86_64"],
     visibility = ["//visibility:public"],
-    deps = [
-        "@python_repo//:python3.9_lib",
+    deps = select({
+        "//tools/platforms/python:debian_bundled_python": [],
+        "//tools/platforms/python:upstream_bundled_python": [
+            "//third_party/python:numpy_cc",
+        ],
+    }) + [
+        "//third_party/python",
     ],
 )
 
diff --git a/third_party/matplotlib-cpp/matplotlibcpp.h b/third_party/matplotlib-cpp/matplotlibcpp.h
index 35aeb54..1c8bdc1 100644
--- a/third_party/matplotlib-cpp/matplotlibcpp.h
+++ b/third_party/matplotlib-cpp/matplotlibcpp.h
@@ -144,6 +144,7 @@
 
     _interpreter() {
       // Force PYTHONHOME and PYTHONPATH to our sandboxed python.
+#if defined(FRC971_DEBIAN_BUNDLED_PYTHON)
       wchar_t python_home[] = L"../python_repo/usr/";
       wchar_t python_path[] =
           L"../matplotlib_repo/3:../python_repo/usr/lib/python35.zip:../"
@@ -153,6 +154,23 @@
 
       Py_SetPath(python_path);
       Py_SetPythonHome(python_home);
+#elif defined(FRC971_UPSTREAM_BUNDLED_PYTHON)
+      wchar_t python_home[] = L"../python3_9_x86_64-unknown-linux-gnu/";
+      Py_SetPythonHome(python_home);
+
+      char python_path[] = "PYTHONPATH="
+        "../pip_deps_matplotlib/site-packages/:"
+        "../pip_deps_numpy/site-packages/:"
+        "../pip_deps_pillow/site-packages/:"
+        "../pip_deps_pycairo/site-packages/:"
+        "../pip_deps_pygobject/site-packages/:"
+        "../pip_deps_python_dateutil/site-packages/:"
+        "../pip_deps_six/site-packages/:"
+        "../";
+      putenv(python_path);
+#else
+#error Need one of the two defined.
+#endif
 
       // We fail really poorly if DISPLAY isn't set.  We can do better.
       if (getenv("DISPLAY") == nullptr) {
@@ -195,6 +213,13 @@
             throw std::runtime_error("Error loading module matplotlib!");
         }
 
+#if defined(FRC971_UPSTREAM_BUNDLED_PYTHON)
+        // We don't support tkinter with Python from rules_python. We use the
+        // GTK backend instead.
+        // https://github.com/matplotlib/matplotlib/issues/23074
+        s_backend = "GTK3Agg";
+#endif
+
         // matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
         // or matplotlib.backends is imported for the first time
         if (!s_backend.empty()) {
diff --git a/third_party/python/BUILD b/third_party/python/BUILD
new file mode 100644
index 0000000..325870c
--- /dev/null
+++ b/third_party/python/BUILD
@@ -0,0 +1,58 @@
+load(":defs.bzl", "extract_numpy_headers")
+
+cc_library(
+    name = "python",
+    deps = select({
+        "//tools/platforms/python:debian_bundled_python": [
+            "@python_repo//:python3.9_lib",
+        ],
+        "//tools/platforms/python:upstream_bundled_python": [
+            "@python3_9_x86_64-unknown-linux-gnu//:python_headers",
+            "@python3_9_x86_64-unknown-linux-gnu//:libpython",
+        ],
+    }),
+    defines = select({
+        "//tools/platforms/python:debian_bundled_python": [
+            "FRC971_DEBIAN_BUNDLED_PYTHON",
+        ],
+        "//tools/platforms/python:upstream_bundled_python": [
+            "FRC971_UPSTREAM_BUNDLED_PYTHON",
+            "FRC971_PYTHON_HOME=../python3_9_x86_64-unknown-linux-gnu/",
+        ],
+    }),
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "python_runtime",
+    data = select({
+        "//tools/platforms/python:debian_bundled_python": [
+            "@python_repo//:all_files",
+        ],
+        "//tools/platforms/python:upstream_bundled_python": [
+            "@python3_9_x86_64-unknown-linux-gnu//:files",
+        ],
+    }),
+    visibility = ["//visibility:public"],
+)
+
+extract_numpy_headers(
+    name = "numpy_headers",
+    numpy = "@pip//numpy",
+    header_prefix = "numpy_headers",
+    visibility = ["//visibility:private"],
+)
+
+cc_library(
+    name = "numpy_cc",
+    hdrs = [
+        ":numpy_headers",
+    ],
+    includes = [
+        "numpy_headers",
+    ],
+    deps = [
+        ":python",
+    ],
+    visibility = ["//visibility:public"],
+)
diff --git a/third_party/python/defs.bzl b/third_party/python/defs.bzl
new file mode 100644
index 0000000..f49d0ba
--- /dev/null
+++ b/third_party/python/defs.bzl
@@ -0,0 +1,34 @@
+def _extract_numpy_headers_impl(ctx):
+    files = ctx.attr.numpy[DefaultInfo].default_runfiles.files.to_list()
+    prefix = ctx.attr.header_prefix
+
+    hdrs = []
+    for file in files:
+        _, partition, include_file = file.path.partition("/numpy/core/include/numpy/")
+        if partition:
+            hdr = ctx.actions.declare_file("%s/numpy/%s" % (prefix, include_file))
+            ctx.actions.run(
+                inputs = [file],
+                outputs = [hdr],
+                executable = "cp",
+                arguments = [file.path, hdr.path],
+            )
+            hdrs.append(hdr)
+
+    return [DefaultInfo(files=depset(hdrs))]
+
+extract_numpy_headers = rule(
+    implementation = _extract_numpy_headers_impl,
+    doc = "Extracts the numpy headers from the corresponding py_library target.",
+    attrs = {
+        "numpy": attr.label(
+            mandatory = True,
+            providers = [PyInfo],
+            doc = "The label for the numpy py_library target.",
+        ),
+        "header_prefix": attr.string(
+            mandatory = True,
+            doc = "The directory to copy the headers into.",
+        ),
+    },
+)