@@ -11,7 +11,6 @@ import (
1111 "os"
1212 "os/exec"
1313 "path/filepath"
14- "regexp"
1514 "runtime"
1615 "strings"
1716
@@ -135,9 +134,9 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error {
135134
136135 switch cfg .Backend {
137136 case bind .BackendPyBind11 :
138- return buildPyBind11 (cfg , buildname , pycfg )
137+ return buildPyBind11 (cfg , pycfg )
139138 case bind .BackendNanobind :
140- return buildNanobind (cfg , buildname , pycfg )
139+ return buildNanobind (cfg , pycfg )
141140 }
142141
143142 if mode == bind .ModeExe {
@@ -377,20 +376,20 @@ func buildCFFI(cfg *BuildCfg, buildLib string) error {
377376}
378377
379378// buildPyBind11 builds the pybind11 backend's module (see buildCXXModule).
380- func buildPyBind11 (cfg * BuildCfg , buildname string , pycfg bind.PyConfig ) error {
379+ func buildPyBind11 (cfg * BuildCfg , pycfg bind.PyConfig ) error {
381380 cmdout , err := exec .Command (cfg .VM , "-m" , "pybind11" , "--includes" ).CombinedOutput ()
382381 if err != nil {
383382 fmt .Printf ("cmd had error: %v output:\n %v\n (is pybind11 installed? pip install pybind11)\n " , err , string (cmdout ))
384383 return err
385384 }
386- return buildCXXModule (cfg , buildname , pycfg , strings .Fields (strings .TrimSpace (string (cmdout ))), nil )
385+ return buildCXXModule (cfg , pycfg , strings .Fields (strings .TrimSpace (string (cmdout ))), nil )
387386}
388387
389388// buildNanobind builds the nanobind backend's module (see buildCXXModule).
390389// Unlike pybind11, nanobind isn't header-only: its own runtime (libnanobind)
391390// ships as source, meant to be compiled into each extension alongside the
392391// extension's own code, which nb_combined.cpp does in one translation unit.
393- func buildNanobind (cfg * BuildCfg , buildname string , pycfg bind.PyConfig ) error {
392+ func buildNanobind (cfg * BuildCfg , pycfg bind.PyConfig ) error {
394393 cmdout , err := exec .Command (cfg .VM , "-c" ,
395394 "import nanobind; print(nanobind.include_dir()); print(nanobind.source_dir())" ).CombinedOutput ()
396395 if err != nil {
@@ -404,41 +403,27 @@ func buildNanobind(cfg *BuildCfg, buildname string, pycfg bind.PyConfig) error {
404403 incdir , srcdir := strings .TrimSpace (dirs [0 ]), strings .TrimSpace (dirs [1 ])
405404 // robin_map is a dependency nanobind vendors next to its own headers.
406405 robinmap := filepath .Join (filepath .Dir (incdir ), "ext" , "robin_map" , "include" )
407- flags := []string {
408- "-I" + incdir , "-I" + robinmap ,
409- // as nanobind's own build does for libnanobind (see the comment at
410- // the top of nb_combined.cpp); harmless for the generated .cpp too.
411- "-DNDEBUG" , "-DNB_COMPACT_ASSERTIONS" , "-fno-strict-aliasing" ,
412- }
413- return buildCXXModule (cfg , buildname , pycfg , flags , []string {filepath .Join (srcdir , "nb_combined.cpp" )})
406+ flags := append ([]string {"-I" + incdir , "-I" + robinmap }, bind .NanobindCXXFlags ... )
407+ return buildCXXModule (cfg , pycfg , flags , []string {filepath .Join (srcdir , "nb_combined.cpp" )})
414408}
415409
416410// buildCXXModule builds the cgo shim as a static archive, runs build.py to
417411// write a C++ module wrapping it (pybind11 or nanobind), and compiles+links
418- // that with a C++ compiler, passing it cxxflags (the C++ library's include
412+ // that with a C++ compiler (see bind.CXXArgs, and bind/cxxbuild.go for why
413+ // a static archive), passing it cxxflags (the C++ library's include
419414// directories, and any flags of its own) and, besides the generated .cpp,
420415// the C++ library's own sources, if any. The current directory is the
421- // output directory.
422- //
423- // Unlike cffi (buildCFFI), the wrapper Go generates (pybind11_callback.go)
424- // has Go call INTO the wrapper's own C++ code (the per-callback-shape
425- // trampolines) as well as the other way around. Two separately-built
426- // shared libraries can't have a dependency cycle like that -- neither can
427- // exist as a complete, loadable file before the other -- so instead of a
428- // shared library (buildCFFI's buildLib), the Go side here builds as a
429- // static archive (-buildmode=c-archive), with its symbols left unresolved
430- // until the single final link below, alongside the C++ object code that
431- // defines them.
432- func buildCXXModule (cfg * BuildCfg , buildname string , pycfg bind.PyConfig , cxxflags , srcs []string ) error {
433- archive := buildname + ".a"
416+ // output directory. The Makefile gopy gen writes for these backends runs
417+ // the same steps.
418+ func buildCXXModule (cfg * BuildCfg , pycfg bind.PyConfig , cxxflags , srcs []string ) error {
434419 args := []string {"build" , "-mod=mod" , "-buildmode=c-archive" }
435420 if cfg .BuildTags != "" {
436421 args = append (args , "-tags" , cfg .BuildTags )
437422 }
438423 if ! cfg .Symbols {
439424 args = append (args , "-ldflags=-s -w" )
440425 }
441- args = append (args , "-o" , archive , "." )
426+ args = append (args , "-o" , bind . CXXArchive ( cfg . Name ) , "." )
442427 fmt .Printf ("go %v\n " , strings .Join (args , " " ))
443428 cmdout , err := exec .Command ("go" , args ... ).CombinedOutput ()
444429 if err != nil {
@@ -453,85 +438,8 @@ func buildCXXModule(cfg *BuildCfg, buildname string, pycfg bind.PyConfig, cxxfla
453438 return err
454439 }
455440
456- extext := libExt
457- if runtime .GOOS == "windows" {
458- extext = ".pyd"
459- }
460- if pycfg .ExtSuffix != "" {
461- extext = pycfg .ExtSuffix
462- }
463- modlib := "_" + cfg .Name + extext
464-
465- cxx := os .Getenv ("CXX" )
466- if cxx == "" {
467- cxx = "c++"
468- }
469- // pycfg.CFlags/LdFlags quote each path (for the shell that CGO_CFLAGS/
470- // CGO_LDFLAGS normally go through); exec.Command runs the compiler
471- // directly, with no shell to strip those, so unquote each field here.
472- unquote := func (fields []string ) []string {
473- o := make ([]string , len (fields ))
474- for i , f := range fields {
475- o [i ] = strings .Trim (f , `"` )
476- }
477- return o
478- }
479- // modlib depends on libpython (wherever this VM's own one lives, e.g.
480- // not on the loader's default search path for a uv- or pyenv-managed
481- // Python); without an rpath, the loader only finds it if it happens to
482- // already be on its search path.
483- var libdir string
484- if m := regexp .MustCompile (`-L(\S+)` ).FindStringSubmatch (pycfg .LdFlags ); m != nil {
485- libdir = strings .Trim (m [1 ], `"` )
486- }
487- // The archive's Go runtime code calls into gopy_cb_N (defined below, in
488- // the .cpp), so the linker must be told to keep every object in it --
489- // left to its own judgement, it would see nothing in the .cpp calling
490- // into the archive first and drop it as unused. GNU ld (Linux, and
491- // Windows' MinGW) and ld64 (macOS) spell that differently.
492- var archiveArgs []string
493- if runtime .GOOS == "darwin" {
494- archiveArgs = []string {"-Wl,-force_load," + archive }
495- } else {
496- archiveArgs = []string {"-Wl,--whole-archive" , archive , "-Wl,--no-whole-archive" }
497- }
498- cxxArgs := []string {"-std=c++17" , "-fPIC" , "-shared" , "-O2" }
499- switch runtime .GOOS {
500- case "darwin" :
501- cxxArgs = append (cxxArgs , "-Wl,-rpath,@loader_path" )
502- if libdir != "" {
503- cxxArgs = append (cxxArgs , "-Wl,-rpath," + libdir )
504- }
505- case "windows" :
506- // No rpath equivalent; modlib depends on nothing but libpython now
507- // that the Go side is a static archive, not a separate DLL of its
508- // own (see the buildCXXModule doc comment). MinGW's own runtime
509- // (libstdc++/libgcc/libwinpthread), which g++ links dynamically by
510- // default, has no such fix available -- it isn't found by name
511- // alone unless its directory happens to be on PATH -- so link it in
512- // statically instead. The C runtime (ucrt) stays dynamic, shared
513- // with Python's own.
514- cxxArgs = append (cxxArgs , "-static-libgcc" , "-static-libstdc++" ,
515- "-Wl,-Bstatic,--whole-archive" , "-lwinpthread" , "-Wl,--no-whole-archive" , "-Wl,-Bdynamic" )
516- default :
517- cxxArgs = append (cxxArgs , "-Wl,-rpath,$ORIGIN" )
518- if libdir != "" {
519- cxxArgs = append (cxxArgs , "-Wl,-rpath," + libdir )
520- }
521- }
522- cxxArgs = append (cxxArgs , cxxflags ... )
523- cxxArgs = append (cxxArgs , unquote (strings .Fields (pycfg .CFlags ))... )
524- cxxArgs = append (cxxArgs , cfg .Name + ".cpp" )
525- cxxArgs = append (cxxArgs , srcs ... )
526- cxxArgs = append (cxxArgs , archiveArgs ... )
527- cxxArgs = append (cxxArgs , unquote (strings .Fields (pycfg .LdFlags ))... )
528- // c-archive mode (unlike c-shared) doesn't resolve the Go runtime's own
529- // dependencies on these itself; TODO: verified only on Linux -- unclear
530- // yet whether Windows/macOS need anything of their own added here too.
531- if runtime .GOOS != "windows" {
532- cxxArgs = append (cxxArgs , "-lpthread" , "-ldl" , "-lm" )
533- }
534- cxxArgs = append (cxxArgs , "-o" , modlib )
441+ cxx := bind .CXX ()
442+ cxxArgs := bind .CXXArgs (cfg .Name , bind .ExtModuleName (cfg .Name , libExt , pycfg ), pycfg , cxxflags , srcs )
535443 fmt .Printf ("%v %v\n " , cxx , strings .Join (cxxArgs , " " ))
536444 cmdout , err = exec .Command (cxx , cxxArgs ... ).CombinedOutput ()
537445 if err != nil {
0 commit comments