_toolchain.cxx: new toolchain.
authorwhitequark <whitequark@whitequark.org>
Thu, 27 Aug 2020 06:24:18 +0000 (06:24 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 31 Dec 2021 15:10:47 +0000 (15:10 +0000)
nmigen/_toolchain/cxx.py [new file with mode: 0644]
tests/test_toolchain_cxx.py [new file with mode: 0644]

diff --git a/nmigen/_toolchain/cxx.py b/nmigen/_toolchain/cxx.py
new file mode 100644 (file)
index 0000000..32f1256
--- /dev/null
@@ -0,0 +1,52 @@
+import tempfile
+import sysconfig
+import os.path
+from distutils import ccompiler
+
+
+__all__ = ["build_cxx"]
+
+
+def build_cxx(*, cxx_sources, output_name, include_dirs, macros):
+    build_dir = tempfile.TemporaryDirectory(prefix="nmigen_cxx_")
+
+    cwd = os.getcwd()
+    try:
+        # Unforuntately, `ccompiler.compile` assumes the paths are relative, and interprets
+        # the directory name of the source path specially. That makes it necessary to build in
+        # the output directory directly.
+        os.chdir(build_dir.name)
+
+        cc_driver = ccompiler.new_compiler()
+        cc_driver.output_dir = "."
+
+        cc = sysconfig.get_config_var("CC")
+        cxx = sysconfig.get_config_var("CXX")
+        cflags = sysconfig.get_config_var("CCSHARED")
+        ld_ldflags = sysconfig.get_config_var("LDCXXSHARED")
+        cc_driver.set_executables(
+            compiler=f"{cc} {cflags}",
+            compiler_so=f"{cc} {cflags}",
+            compiler_cxx=f"{cxx} {cflags}",
+            linker_so=ld_ldflags,
+        )
+
+        for include_dir in include_dirs:
+            cc_driver.add_include_dir(include_dir)
+        for macro in macros:
+            cc_driver.define_macro(macro)
+        for cxx_filename, cxx_source in cxx_sources.items():
+            with open(cxx_filename, "w") as f:
+                f.write(cxx_source)
+
+        cxx_filenames = list(cxx_sources.keys())
+        obj_filenames = cc_driver.object_filenames(cxx_filenames)
+        so_filename = cc_driver.shared_object_filename(output_name)
+
+        cc_driver.compile(cxx_filenames)
+        cc_driver.link_shared_object(obj_filenames, output_filename=so_filename, target_lang="c++")
+
+        return build_dir, so_filename
+
+    finally:
+        os.chdir(cwd)
diff --git a/tests/test_toolchain_cxx.py b/tests/test_toolchain_cxx.py
new file mode 100644 (file)
index 0000000..01021e2
--- /dev/null
@@ -0,0 +1,68 @@
+import os
+import ctypes
+import tempfile
+import unittest
+
+from nmigen._toolchain.cxx import *
+
+
+class ToolchainCxxTestCase(unittest.TestCase):
+    def setUp(self):
+        self.include_dir = None
+        self.build_dir = None
+
+    def tearDown(self):
+        if self.include_dir:
+            self.include_dir.cleanup()
+        if self.build_dir:
+            self.build_dir.cleanup()
+
+    def test_filename(self):
+        self.build_dir, filename = build_cxx(
+            cxx_sources={"test.cc": ""},
+            output_name="answer",
+            include_dirs=[],
+            macros=[],
+        )
+        self.assertTrue(filename.startswith("answer"))
+
+    def test_simple(self):
+        self.build_dir, filename = build_cxx(
+            cxx_sources={"test.cc": """
+                extern "C" int answer() { return 42; }
+            """},
+            output_name="answer",
+            include_dirs=[],
+            macros=[],
+        )
+        library = ctypes.cdll.LoadLibrary(os.path.join(self.build_dir.name, filename))
+        self.assertEqual(library.answer(), 42)
+
+    def test_macro(self):
+        self.build_dir, filename = build_cxx(
+            cxx_sources={"test.cc": """
+                extern "C" int answer() { return ANSWER; }
+            """},
+            output_name="answer",
+            include_dirs=[],
+            macros=["ANSWER=42"],
+        )
+        library = ctypes.cdll.LoadLibrary(os.path.join(self.build_dir.name, filename))
+        self.assertEqual(library.answer(), 42)
+
+    def test_include(self):
+        self.include_dir = tempfile.TemporaryDirectory(prefix="nmigen_hxx_")
+        with open(os.path.join(self.include_dir.name, "answer.h"), "w") as f:
+            f.write("#define ANSWER 42")
+
+        self.build_dir, filename = build_cxx(
+            cxx_sources={"test.cc": """
+                #include <answer.h>
+                extern "C" int answer() { return ANSWER; }
+            """},
+            output_name="answer",
+            include_dirs=[self.include_dir.name],
+            macros=[],
+        )
+        library = ctypes.cdll.LoadLibrary(os.path.join(self.build_dir.name, filename))
+        self.assertEqual(library.answer(), 42)