[Python] Create Python bindings when Python package has different name to dune module

I'm trying and struggling currently to create working python bindings for python bindings, where the pypi package should have a different name to the dune module and to the actual python package. I'm using all 2.9 branches

I.e. my dune-module is called "ikarus" which is not available at pypi. Thus, as a workaround I want to name the pypi package pyikarus. Thus I want the behaviour to be pip install pyikarus and in the python code import ikarus and my cpp folder structure should also be with ikarus.

I investigated, and my current setup is:

In the top-level setup.py I have

...
metadata["name"] = "pyikarus"
setup(**metadata)

In python/ikarus/setup.py.in I have

REQUIRED_PACKAGES = '${RequiredPythonModules}'.replace(';',' ').split(' ')

setup(
    name="pyikarus",
    description="${ProjectDescription}",
    version="${ProjectVersionString}",
    author="${ProjectAuthor}",
    author_email="${ProjectMaintainerEmail}",
    packages=find_packages(exclude=["docs/*"]),
    zip_safe=0,
    package_data={"": ["*.so"], "pyikarus": ["data/*.cmake"]},
    install_requires=REQUIRED_PACKAGES,
    include_package_data=True,
)

and in python/ikarus/CMakeLists.txt

add_subdirectory(ikarus)
set(CXX_MAX_STANDARD 20)

set(PYPI_PACKAGE_NAME "pyikarus")

dune_python_configure_bindings(
  PATH
  "."
  PACKAGENAME
  ${PYPI_PACKAGE_NAME}
  CMAKE_METADATA_FLAGS
  DUNE_OPTS_FILE
  CXX_MAX_STANDARD
)
if(POLICY CMP0087)
  cmake_policy(SET CMP0087 NEW)
endif()

There are several points where I have to state ikarus or pyikarus. Brute forcing some variants of these options always returns errors. Thus, if I install my uploaded package from pypi and try to compile my code with dune-py, I get two different error messages.

If I pass to dune_python_configure_bindings above the name pyikarus, I get errors like

pyikarus error message
---------------------------------------------------------------------------
CompileError                              Traceback (most recent call last)
Cell In[35], line 1
----> 1 svk = iks.StVenantKirchhoff(emodul=1000,nu=0.3)
      3 svkPS = svk.asPlainStress()

File /dune/dune-common/build-cmake/dune-env/lib/python3.10/site-packages/ikarus/materials.py:33, in materialConstructorDecorator..wrapper(emodul, nu)
     31 includes += ["ikarus/python/finiteElements/materials/material.hh"]
     32 moduleName = func.__name__+"_" + hashIt(element_type)
---> 33 module = generator.load(
     34     includes=includes,
     35     typeName=element_type,
     36     moduleName=moduleName
     37 )
     39 # dispatcher = { 'NeoHooke' : NeoHooke, 'StVenantKirchhoff' : StVenantKirchhoff}
     42 return eval("module."+func.__name__+"(emodul,nu)")

File /dune/dune-common/build-cmake/python/dune/generator/generator.py:172, in SimpleGenerator.load(self, includes, typeName, moduleName, extraCMake, defines, preamble, postscript, options, bufferProtocol, dynamicAttr, baseClasses, holder, *args)
    168 for nr, (tn, a, o, b, d, bc, h)  in enumerate( zip(typeName, args, options, bufferProtocol, dynamicAttr, baseClasses, holder) ):
    169     source += self.main(nr, includes, tn, *a, options=o,
    170                         bufferProtocol=b, dynamicAttr=d,
    171                         baseClasses=bc, holder=h)
--> 172 return self.post(moduleName, source, postscript, extraCMake)

File /dune/dune-common/build-cmake/python/dune/generator/generator.py:124, in SimpleGenerator.post(self, moduleName, source, postscript, extraCMake)
    122 # make sure to reload the builder here in case it got updated
    123 from . import builder
--> 124 module = builder.load(moduleName, source, self.typeName[0], extraCMake)
    126 return module

File /dune/dune-common/build-cmake/python/dune/generator/cmakebuilder.py:382, in Builder.load(self, moduleName, source, pythonName, extraCMake)
    380     module = sys.modules.get("dune.generated." + moduleName)
    381     if module is None:
--> 382         self._buildModule( moduleName, source, pythonName, extraCMake )
    384 ## TODO remove barrier here
    385 comm.barrier()

File /dune/dune-common/build-cmake/python/dune/generator/cmakebuilder.py:732, in MakefileBuilder._buildModule(self, moduleName, source, pythonName, extraCMake)
    729 # check return code
    730 if exit_code > 0:
    731     # retrieve stderr output
--> 732     raise CompileError(buffer_to_str(stderr))

CompileError: /dune/dune-common/build-cmake/dune-env/.cache/dune-py/python/dune/generated/StVenantKirchhoff_de7f96cf2f383220c197a4a670079062.cc:10:10: fatal error: ikarus/finiteElements/mechanics/materials.hh: No such file or directory
   10 | #include 
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
Thus, the underlying cmake script does maybe not include the correct subdirectories to make the headers findable.

If I pass to dune_python_configure_bindings above the name ikarus I get errors like

pyikarus error message
---------------------------------------------------------------------------
CompileError                              Traceback (most recent call last)
Cell In[4], line 1
----> 1 svk = iks.StVenantKirchhoff(emodul=1000,nu=0.3)
      3 svkPS = svk.asPlainStress()

File /dune/dune-common/build-cmake/dune-env/lib/python3.10/site-packages/ikarus/materials.py:33, in materialConstructorDecorator..wrapper(emodul, nu)
     31 includes += ["ikarus/python/finiteElements/materials/material.hh"]
     32 moduleName = func.__name__+"_" + hashIt(element_type)
---> 33 module = generator.load(
     34     includes=includes,
     35     typeName=element_type,
     36     moduleName=moduleName
     37 )
     39 # dispatcher = { 'NeoHooke' : NeoHooke, 'StVenantKirchhoff' : StVenantKirchhoff}
     42 return eval("module."+func.__name__+"(emodul,nu)")

File /dune/dune-common/build-cmake/python/dune/generator/generator.py:172, in SimpleGenerator.load(self, includes, typeName, moduleName, extraCMake, defines, preamble, postscript, options, bufferProtocol, dynamicAttr, baseClasses, holder, *args)
    168 for nr, (tn, a, o, b, d, bc, h)  in enumerate( zip(typeName, args, options, bufferProtocol, dynamicAttr, baseClasses, holder) ):
    169     source += self.main(nr, includes, tn, *a, options=o,
    170                         bufferProtocol=b, dynamicAttr=d,
    171                         baseClasses=bc, holder=h)
--> 172 return self.post(moduleName, source, postscript, extraCMake)

File /dune/dune-common/build-cmake/python/dune/generator/generator.py:124, in SimpleGenerator.post(self, moduleName, source, postscript, extraCMake)
    122 # make sure to reload the builder here in case it got updated
    123 from . import builder
--> 124 module = builder.load(moduleName, source, self.typeName[0], extraCMake)
    126 return module

File /dune/dune-common/build-cmake/python/dune/generator/cmakebuilder.py:374, in Builder.load(self, moduleName, source, pythonName, extraCMake)
    370 def load(self, moduleName, source, pythonName, extraCMake=None):
    371     # check if we need to initialize dune-py either because
    372     # this is the first call to load or because an external module with metadata has been registered
    373     if not self.initialized or not self.externalPythonModules == getExternalPythonModules():
--> 374         self.initialize()
    376     # check whether module is already compiled and build it if necessary
    377     # (only try to build module on rank 0!)
    378     # TODO replace if rank with something better and remove barrier further down
    379     if comm.rank == 0:

File /dune/dune-common/build-cmake/python/dune/generator/cmakebuilder.py:616, in MakefileBuilder.initialize(self)
    615 def initialize(self):
--> 616     super().initialize()
    617     # check that the compile script is available
    618     script = os.path.join(self.generated_dir,"buildScript.sh")

File /dune/dune-common/build-cmake/python/dune/generator/cmakebuilder.py:213, in Builder.initialize(self)
    211 self.cacheExternalModules()
    212 # create dune-py module
--> 213 self.build_dunepy_from_template(self.dune_py_dir)
    214 # create tag file so that dune-py is not rebuilt on the next build
    215 open(tagfile, 'a').close()

File /dune/dune-common/build-cmake/python/dune/generator/cmakebuilder.py:472, in MakefileBuilder.build_dunepy_from_template(dunepy_dir, force)
    468 if not useNinja:
    469     # call base class dunepy_from_template (re-initialize)
    470     force = Builder.generate_dunepy_from_template(dunepy_dir, force=True)
--> 472     Builder.callCMake(["cmake"] + defaultCMakeFlags() + ["."],
    473                       cwd=dunepy_dir,
    474                       infoTxt="Configuring dune-py with CMake (make)",
    475                       active=True, # print details anyway
    476                      )
    478 # obtain bash and make command generated by cmake
    479 # when ninja is used makeCmd is the ninja command
    480 try:

File /dune/dune-common/build-cmake/python/dune/generator/cmakebuilder.py:263, in Builder.callCMake(cmake_args, cwd, infoTxt, verbose, active, logLevel, env)
    260     if cmake.returncode > 0:
    261         # retrieve stderr output
    262         print(stderr,stdout)
--> 263         raise CompileError(buffer_to_str(stderr))
    265 return stdout, stderr

CompileError: ---- LOCK
---- ACQUIRED
-- Generating CXX compiler script for CXXFLAGS overloading on command line
---- RELEASE
---- DONE
CMake Error at /dune/dune-common/build-cmake/dune-env/lib/cmake/ikarus/ikarus-targets.cmake:61 (set_target_properties):
  The link interface of target "ikarus" contains:

    autodiff::autodiff

  but the target was not found.  Possible reasons include:

    * There is a typo in the target name.
    * A find_package call is missing for an IMPORTED target.
    * An ALIAS target is missing.

Call Stack (most recent call first):
  /dune/dune-common/build-cmake/dune-env/lib/cmake/ikarus/ikarus-config.cmake:55 (include)
  /dune/dune-common/cmake/modules/DuneModuleDependencies.cmake:170 (find_package)
  /dune/dune-common/cmake/modules/DuneModuleDependencies.cmake:289 (find_dune_package)
  /dune/dune-common/cmake/modules/DuneModuleDependencies.cmake:336 (dune_process_dependency_leafs)
  /dune/dune-common/cmake/modules/DuneModuleDependencies.cmake:46 (dune_create_dependency_leafs)
  /dune/dune-common/cmake/modules/DuneProject.cmake:90 (dune_create_dependency_tree)
  CMakeLists.txt:106 (dune_project)

which indicates by the linking error, that in some cmake file the linking is not done correctly, thus indicating the first version with pyikarus is better? But anyway the headers are not findable in this first case. Changes in the files in python/ikarus/ does not change the error messages.

Can someone please point me to the correct changes. I'm willing to create then a proper MR to enable this functionality, if this is of interest. Otherwise, if this should already work can someone correct my mistakes?

The current folder structure can be found at https://github.com/ikarus-project/ikarus/tree/feature/pythonBindingsV3.

Thanks, and any help is very much appreciated.