--- /dev/null
+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)
--- /dev/null
+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)