diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09215828c3..069bc601a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: HOST_CCACHE_DIR="$(ccache -k cache_dir)" mkdir -p $HOST_CCACHE_DIR - name: Build wheels # check https://cibuildwheel.readthedocs.io/en/stable/setup/#github-actions - uses: pypa/cibuildwheel@v2.11.3 + uses: pypa/cibuildwheel@v2.12.0 # to supply options, put them in 'env', like: # env: # CIBW_SOME_OPTION: value @@ -77,7 +77,7 @@ jobs: - uses: actions/upload-artifact@v2 with: path: dist/*.tar.gz - + upload_to_test_pypy: needs: [build, make_sdist] runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index a97cc10fe5..e30cc2354c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,5 +24,9 @@ before-build = "ccache -s" environment-pass = ["HOST_CCACHE_DIR"] [tool.cibuildwheel.macos] -# Don't repair macOS wheels -repair-wheel-command = "" +before-all = [ + "brew install libpng", + "brew deps --tree --installed", +] +# Repair macOS wheels (include libpng with the wheel, for example) +repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v --ignore-missing-dependencies {wheel}" \ No newline at end of file diff --git a/setup.py b/setup.py index a9c0dc5ab7..82ef4a2a37 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ from distutils.errors import CompileError import distutils.command.build_ext import setuptools.command.build_ext +from setuptools.command.build_ext import build_ext as _build_ext import multiprocessing # for Jenkins we do not want to be greedy @@ -137,6 +138,24 @@ def quote_path(path): else: return path +import subprocess + +def check_libpng(): + """ Check if libpng is available (Linux & Macos only)""" + try: + subprocess.check_call(["libpng-config", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + return True + except FileNotFoundError as e: + # libpng is not present. + if True: # change to False to enable installation without libpng + raise RuntimeError("libpng missing. libpng-config is not available") from e + return False + +def libpng_cflags(): + return subprocess.check_output(["libpng-config", "--cflags"]).decode().split() + +def libpng_ldflags(): + return subprocess.check_output(["libpng-config", "--ldflags"]).decode().split() # TODO: delete (Obsolete) # patch get_ext_filename @@ -161,6 +180,12 @@ def patched_get_ext_filename(self, ext_name): # end patch get_ext_filename +# TODO: customize this object instead of introspecting and patching distutils (if possible) +class klayout_build_ext(_build_ext): + """ Customize build extension class to check for dependencies before installing.""" + def finalize_options(self) -> None: + ret = super().finalize_options() + return ret # patch CCompiler's library_filename for _dbpi libraries (SOs) # Its default is to have .so for shared objects, instead of dylib, @@ -222,7 +247,6 @@ def always_link_shared_object( target_lang, ) - setuptools.command.build_ext.libtype = "shared" setuptools.command.build_ext.link_shared_object = always_link_shared_object @@ -321,6 +345,9 @@ def compile_args(self, mod): "-Wno-strict-aliasing", # Avoids many "type-punned pointer" warnings "-std=c++11", # because we use unordered_map/unordered_set ] + if platform.system() == "Darwin" and mod == "_tl": + if check_libpng(): + args += libpng_cflags() return args def libraries(self, mod): @@ -330,9 +357,14 @@ def libraries(self, mod): if platform.system() == "Windows": if mod == "_tl": return ["libcurl", "expat", "pthreadVCE2", "zlib", "wsock32", "libpng16"] + elif platform.system() == "Darwin": + if mod == "_tl": + libs = ["curl", "expat"] # libpng is included by libpng_ldflags + return libs else: if mod == "_tl": - return ["curl", "expat", "png"] + libs = ["curl", "expat", "png"] + return libs return [] def link_args(self, mod): @@ -361,6 +393,8 @@ def link_args(self, mod): "-Wl,-install_name,@rpath/%s" % self.libname_of(mod, is_lib=True), ] args += ["-Wl,-rpath,@loader_path/"] + if mod == "_tl" and check_libpng(): + args += libpng_ldflags() return args else: # this makes the libraries suitable for linking with a path - @@ -385,8 +419,7 @@ def macros(self): """ Returns the macros to use for building """ - return [ - ("HAVE_PNG", 1), + macros = [ ("HAVE_CURL", 1), ("HAVE_EXPAT", 1), ("KLAYOUT_MAJOR_VERSION", self.major_version()), @@ -394,6 +427,12 @@ def macros(self): ("GSI_ALIAS_INSPECT", 1), ] + if platform.system() == "Darwin" and check_libpng(): + macros += [("HAVE_PNG", 1)] + else: + macros += [("HAVE_PNG", 1)] + return macros + def minor_version(self): """ Gets the version string @@ -611,9 +650,9 @@ def version(self): define_macros=config.macros() + [('MAKE_LAYBASIC_LIBRARY', 1)], include_dirs=[_rdb_path, _db_path, _tl_path, _gsi_path], extra_objects=[ - config.path_of('_rdb', _rdb_path), - config.path_of('_tl', _tl_path), - config.path_of('_gsi', _gsi_path), + config.path_of('_rdb', _rdb_path), + config.path_of('_tl', _tl_path), + config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path) ], language='c++', @@ -635,10 +674,10 @@ def version(self): define_macros=config.macros() + [('MAKE_LAYVIEW_LIBRARY', 1)], include_dirs=[_laybasic_path, _rdb_path, _db_path, _tl_path, _gsi_path], extra_objects=[ - config.path_of('_laybasic', _laybasic_path), - config.path_of('_rdb', _rdb_path), - config.path_of('_tl', _tl_path), - config.path_of('_gsi', _gsi_path), + config.path_of('_laybasic', _laybasic_path), + config.path_of('_rdb', _rdb_path), + config.path_of('_tl', _tl_path), + config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path) ], language='c++', @@ -660,9 +699,9 @@ def version(self): define_macros=config.macros() + [('MAKE_LYM_LIBRARY', 1)], include_dirs=[_pya_path, _rba_path, _tl_path, _gsi_path], extra_objects=[ - config.path_of('_rba', _rba_path), - config.path_of('_pya', _pya_path), - config.path_of('_tl', _tl_path), + config.path_of('_rba', _rba_path), + config.path_of('_pya', _pya_path), + config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path) ], language='c++', @@ -684,11 +723,11 @@ def version(self): define_macros=config.macros() + [('MAKE_ANT_LIBRARY', 1)], include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path], extra_objects=[ - config.path_of('_laybasic', _laybasic_path), - config.path_of('_layview', _layview_path), - config.path_of('_rdb', _rdb_path), - config.path_of('_tl', _tl_path), - config.path_of('_gsi', _gsi_path), + config.path_of('_laybasic', _laybasic_path), + config.path_of('_layview', _layview_path), + config.path_of('_rdb', _rdb_path), + config.path_of('_tl', _tl_path), + config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path) ], language='c++', @@ -710,11 +749,11 @@ def version(self): define_macros=config.macros() + [('MAKE_IMG_LIBRARY', 1)], include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path], extra_objects=[ - config.path_of('_laybasic', _laybasic_path), - config.path_of('_layview', _layview_path), - config.path_of('_rdb', _rdb_path), - config.path_of('_tl', _tl_path), - config.path_of('_gsi', _gsi_path), + config.path_of('_laybasic', _laybasic_path), + config.path_of('_layview', _layview_path), + config.path_of('_rdb', _rdb_path), + config.path_of('_tl', _tl_path), + config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path) ], language='c++', @@ -736,11 +775,11 @@ def version(self): define_macros=config.macros() + [('MAKE_EDT_LIBRARY', 1)], include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path], extra_objects=[ - config.path_of('_laybasic', _laybasic_path), - config.path_of('_layview', _layview_path), - config.path_of('_rdb', _rdb_path), - config.path_of('_tl', _tl_path), - config.path_of('_gsi', _gsi_path), + config.path_of('_laybasic', _laybasic_path), + config.path_of('_layview', _layview_path), + config.path_of('_rdb', _rdb_path), + config.path_of('_tl', _tl_path), + config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path) ], language='c++', @@ -879,23 +918,23 @@ def version(self): lay = Extension(config.root + '.laycore', define_macros=config.macros(), - include_dirs=[_laybasic_path, - _layview_path, - _img_path, - _ant_path, - _edt_path, - _lym_path, - _tl_path, - _gsi_path, + include_dirs=[_laybasic_path, + _layview_path, + _img_path, + _ant_path, + _edt_path, + _lym_path, + _tl_path, + _gsi_path, _pya_path], - extra_objects=[config.path_of('_laybasic', _laybasic_path), - config.path_of('_layview', _layview_path), - config.path_of('_img', _img_path), - config.path_of('_ant', _ant_path), - config.path_of('_edt', _edt_path), - config.path_of('_lym', _lym_path), - config.path_of('_tl', _tl_path), - config.path_of('_gsi', _gsi_path), + extra_objects=[config.path_of('_laybasic', _laybasic_path), + config.path_of('_layview', _layview_path), + config.path_of('_img', _img_path), + config.path_of('_ant', _ant_path), + config.path_of('_edt', _edt_path), + config.path_of('_lym', _lym_path), + config.path_of('_tl', _tl_path), + config.path_of('_gsi', _gsi_path), config.path_of('_pya', _pya_path)], extra_link_args=config.link_args('laycore'), extra_compile_args=config.compile_args('laycore'), @@ -935,7 +974,8 @@ def version(self): package_data={config.root: ["src/pymod/distutils_src/klayout/*.pyi"]}, data_files=[(config.root, ["src/pymod/distutils_src/klayout/py.typed"])], include_package_data=True, - ext_modules=[_tl, _gsi, _pya, _rba, _db, _lib, _rdb, _lym, _laybasic, _layview, _ant, _edt, _img] - + db_plugins - + [tl, db, lib, rdb, lay] + ext_modules=[_tl, _gsi, _pya, _rba, _db, _lib, _rdb, _lym, _laybasic, _layview, _ant, _edt, _img] + + db_plugins + + [tl, db, lib, rdb, lay], + cmdclass={'build_ext': klayout_build_ext} )