mirror of
https://github.com/python-LimeReport/python-LimeReport.git
synced 2025-09-01 15:53:43 +03:00
prepare the project for distribution
This commit is contained in:
parent
c313f5a946
commit
676a6be463
@ -3,9 +3,15 @@
|
||||
"image": "ghcr.io/python-limereport/devcontainer:Qt-6.4.2-Python-3.9",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [],
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.autopep8"
|
||||
],
|
||||
"settings": {
|
||||
"cmake.configureOnOpen": false
|
||||
"cmake.configureOnOpen": false,
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.autopep8"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
179
.gitignore
vendored
179
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/cmake,qt,c++
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=cmake,qt,c++
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/cmake,qt,c++,python
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=cmake,qt,c++,python
|
||||
|
||||
### C++ ###
|
||||
# Prerequisites
|
||||
@ -52,6 +52,177 @@ _deps
|
||||
# External projects
|
||||
*-prefix/
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
### Qt ###
|
||||
# C++ objects and libs
|
||||
*.so.*
|
||||
@ -98,6 +269,4 @@ CMakeLists.txt.user*
|
||||
|
||||
*_qmlcache.qrc
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/cmake,qt,c++
|
||||
|
||||
build
|
||||
# End of https://www.toptal.com/developers/gitignore/api/cmake,qt,c++,python
|
||||
|
@ -9,7 +9,9 @@ project(LimeReport6)
|
||||
|
||||
# LimeReport options
|
||||
set(USE_QT6 ON)
|
||||
set(ENABLE_ZINT ON)
|
||||
set(ENABLE_ZINT OFF)
|
||||
#
|
||||
set(LIMEREPORT_STATIC ON)
|
||||
|
||||
if (NOT DEFINED PYSIDE_INSTALL_DIR)
|
||||
set(PYSIDE_INSTALL_DIR $ENV{PYSIDE_INSTALL_DIR})
|
||||
@ -126,4 +128,13 @@ endforeach()
|
||||
# TODO:
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE /home/vscode/pyside-setup/sources/pyside6/PySide6)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES BUILD_RPATH "$ORIGIN/")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES BUILD_RPATH "$ORIGIN/:$ORIGIN/PySide6/:$ORIGIN/shiboken6/")
|
||||
|
||||
|
||||
# ===============
|
||||
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${PYTHON_EXECUTABLE}
|
||||
${CMAKE_SOURCE_DIR}/build_scripts/pyi_generator.py
|
||||
$<TARGET_FILE:${PROJECT_NAME}>
|
||||
COMMENT "Running pyi_generator")
|
17
README.md
Normal file
17
README.md
Normal file
@ -0,0 +1,17 @@
|
||||
Python bindings for [LimeReport](https://github.com/fralx/LimeReport)
|
||||
|
||||
🚧 work in progress 🚧
|
||||
|
||||
## Build
|
||||
|
||||
Default:
|
||||
|
||||
```
|
||||
$ python setup.py build --parallel $(nproc) bdist_wheel
|
||||
```
|
||||
|
||||
With zint (for barcodes):
|
||||
|
||||
```
|
||||
$ LIMEREPORT_USE_ZINT=TRUE python setup.py build --parallel $(nproc) bdist_wheel
|
||||
```
|
309
build_scripts/pyi_generator.py
Normal file
309
build_scripts/pyi_generator.py
Normal file
@ -0,0 +1,309 @@
|
||||
LICENSE_TEXT = """
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
"""
|
||||
|
||||
"""
|
||||
pyi_generator.py
|
||||
|
||||
This script generates .pyi files for arbitrary modules.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import typing
|
||||
|
||||
from pathlib import Path
|
||||
from contextlib import contextmanager
|
||||
from textwrap import dedent
|
||||
|
||||
from shiboken6 import Shiboken
|
||||
from shibokensupport.signature.lib.enum_sig import HintingEnumerator
|
||||
from shibokensupport.signature.lib.tool import build_brace_pattern
|
||||
|
||||
import PySide6
|
||||
|
||||
# Can we use forward references?
|
||||
USE_PEP563 = sys.version_info[:2] >= (3, 7)
|
||||
|
||||
indent = " " * 4
|
||||
|
||||
|
||||
class Writer(object):
|
||||
def __init__(self, outfile, *args):
|
||||
self.outfile = outfile
|
||||
self.history = [True, True]
|
||||
|
||||
def print(self, *args, **kw):
|
||||
# controlling too much blank lines
|
||||
if self.outfile:
|
||||
if args == () or args == ("",):
|
||||
# We use that to skip too many blank lines:
|
||||
if self.history[-2:] == [True, True]:
|
||||
return
|
||||
print("", file=self.outfile, **kw)
|
||||
self.history.append(True)
|
||||
else:
|
||||
print(*args, file=self.outfile, **kw)
|
||||
self.history.append(False)
|
||||
|
||||
|
||||
class Formatter(Writer):
|
||||
"""
|
||||
Formatter is formatting the signature listing of an enumerator.
|
||||
|
||||
It is written as context managers in order to avoid many callbacks.
|
||||
The separation in formatter and enumerator is done to keep the
|
||||
unrelated tasks of enumeration and formatting apart.
|
||||
"""
|
||||
def __init__(self, outfile, options, *args):
|
||||
self.options = options
|
||||
Writer.__init__(self, outfile, *args)
|
||||
# patching __repr__ to disable the __repr__ of typing.TypeVar:
|
||||
"""
|
||||
def __repr__(self):
|
||||
if self.__covariant__:
|
||||
prefix = '+'
|
||||
elif self.__contravariant__:
|
||||
prefix = '-'
|
||||
else:
|
||||
prefix = '~'
|
||||
return prefix + self.__name__
|
||||
"""
|
||||
def _typevar__repr__(self):
|
||||
return f"typing.{self.__name__}"
|
||||
typing.TypeVar.__repr__ = _typevar__repr__
|
||||
|
||||
# Adding a pattern to substitute "Union[T, NoneType]" by "Optional[T]"
|
||||
# I tried hard to replace typing.Optional by a simple override, but
|
||||
# this became _way_ too much.
|
||||
# See also the comment in layout.py .
|
||||
brace_pat = build_brace_pattern(3, ",")
|
||||
pattern = fr"\b Union \s* \[ \s* {brace_pat} \s*, \s* NoneType \s* \]"
|
||||
replace = r"Optional[\1]"
|
||||
optional_searcher = re.compile(pattern, flags=re.VERBOSE)
|
||||
def optional_replacer(source):
|
||||
return optional_searcher.sub(replace, str(source))
|
||||
self.optional_replacer = optional_replacer
|
||||
# self.level is maintained by enum_sig.py
|
||||
# self.is_method() is true for non-plain functions.
|
||||
|
||||
def section(self):
|
||||
if self.level == 0:
|
||||
self.print()
|
||||
self.print()
|
||||
|
||||
@contextmanager
|
||||
def module(self, mod_name):
|
||||
self.mod_name = mod_name
|
||||
txt = f"""\
|
||||
# Module `{mod_name}`
|
||||
|
||||
<<IMPORTS>>
|
||||
"""
|
||||
self.print(dedent(txt))
|
||||
yield
|
||||
|
||||
@contextmanager
|
||||
def klass(self, class_name, class_str):
|
||||
spaces = indent * self.level
|
||||
while "." in class_name:
|
||||
class_name = class_name.split(".", 1)[-1]
|
||||
class_str = class_str.split(".", 1)[-1]
|
||||
if self.have_body:
|
||||
self.print(f"{spaces}class {class_str}:")
|
||||
else:
|
||||
self.print(f"{spaces}class {class_str}: ...")
|
||||
yield
|
||||
|
||||
@contextmanager
|
||||
def function(self, func_name, signature, decorator=None):
|
||||
if func_name == "__init__":
|
||||
self.print()
|
||||
key = func_name
|
||||
spaces = indent * self.level
|
||||
if isinstance(signature, list):
|
||||
for sig in signature:
|
||||
self.print(f'{spaces}@overload')
|
||||
self._function(func_name, sig, spaces)
|
||||
else:
|
||||
self._function(func_name, signature, spaces, decorator)
|
||||
if func_name == "__init__":
|
||||
self.print()
|
||||
yield key
|
||||
|
||||
def _function(self, func_name, signature, spaces, decorator=None):
|
||||
if decorator:
|
||||
# In case of a PyClassProperty the classmethod decorator is not used.
|
||||
self.print(f'{spaces}@{decorator}')
|
||||
elif self.is_method() and "self" not in signature.parameters:
|
||||
kind = "class" if "cls" in signature.parameters else "static"
|
||||
self.print(f'{spaces}@{kind}method')
|
||||
signature = self.optional_replacer(signature)
|
||||
self.print(f'{spaces}def {func_name}{signature}: ...')
|
||||
|
||||
@contextmanager
|
||||
def enum(self, class_name, enum_name, value):
|
||||
spaces = indent * self.level
|
||||
hexval = hex(value)
|
||||
self.print(f"{spaces}{enum_name:25}: {class_name} = ... # {hexval}")
|
||||
yield
|
||||
|
||||
def find_imports(text):
|
||||
return [imp for imp in PySide6.__all__ if f"PySide6.{imp}." in text]
|
||||
|
||||
FROM_IMPORTS = [
|
||||
(None, ["builtins"]),
|
||||
(None, ["os"]),
|
||||
(None, ["enum"] if sys.pyside63_option_python_enum else []),
|
||||
("typing", typing.__all__),
|
||||
("PySide6.QtCore", ["PyClassProperty"]),
|
||||
("shiboken6", ["Shiboken"]),
|
||||
]
|
||||
|
||||
def filter_from_imports(from_struct, text):
|
||||
"""
|
||||
Build a reduced new `from` structure (nfs) with found entries, only
|
||||
"""
|
||||
nfs = []
|
||||
for mod, imports in from_struct:
|
||||
lis = []
|
||||
nfs.append((mod, lis))
|
||||
for each in imports:
|
||||
if re.search(rf"(\b|@){each}\b([^\s\(:]|\n)", text):
|
||||
lis.append(each)
|
||||
if not lis:
|
||||
nfs.pop()
|
||||
return nfs
|
||||
|
||||
|
||||
def find_module(import_name, outpath, from_pyside):
|
||||
"""
|
||||
Find a module either directly by import, or use the full path,
|
||||
add the path to sys.path and import then.
|
||||
"""
|
||||
if from_pyside:
|
||||
# internal mode for generate_pyi.py
|
||||
plainname = import_name.split(".")[-1]
|
||||
outfilepath = Path(outpath) / f"{plainname}.pyi"
|
||||
return import_name, plainname, outfilepath
|
||||
# we are alone in external module mode
|
||||
p = Path(import_name).resolve()
|
||||
if not p.exists():
|
||||
raise ValueError(f"File {p} does not exist.")
|
||||
if not outpath:
|
||||
outpath = p.parent
|
||||
# temporarily add the path and do the import
|
||||
sys.path.insert(0, os.fspath(p.parent))
|
||||
plainname = p.name.split(".")[0]
|
||||
__import__(plainname)
|
||||
sys.path.pop(0)
|
||||
return plainname, plainname, Path(outpath) / (plainname + ".pyi")
|
||||
|
||||
|
||||
def generate_pyi(import_name, outpath, options):
|
||||
"""
|
||||
Generates a .pyi file.
|
||||
"""
|
||||
import_name, plainname, outfilepath = find_module(import_name, outpath, options._pyside_call)
|
||||
top = __import__(import_name)
|
||||
obj = getattr(top, plainname) if import_name != plainname else top
|
||||
if not getattr(obj, "__file__", None) or Path(obj.__file__).is_dir():
|
||||
raise ModuleNotFoundError(f"We do not accept a namespace as module `{plainname}`")
|
||||
module = sys.modules[import_name]
|
||||
|
||||
outfile = io.StringIO()
|
||||
fmt = Formatter(outfile, options)
|
||||
fmt.print(LICENSE_TEXT.strip())
|
||||
need_imports = options._pyside_call and not USE_PEP563
|
||||
if USE_PEP563:
|
||||
fmt.print("from __future__ import annotations")
|
||||
fmt.print()
|
||||
fmt.print(dedent(f'''\
|
||||
"""
|
||||
This file contains the exact signatures for all functions in module
|
||||
{import_name}, except for defaults which are replaced by "...".
|
||||
"""
|
||||
'''))
|
||||
HintingEnumerator(fmt).module(import_name)
|
||||
fmt.print("# eof")
|
||||
# Postprocess: resolve the imports
|
||||
if options._pyside_call:
|
||||
global PySide6
|
||||
import PySide6
|
||||
with outfilepath.open("w") as realfile:
|
||||
wr = Writer(realfile)
|
||||
outfile.seek(0)
|
||||
while True:
|
||||
line = outfile.readline()
|
||||
if not line:
|
||||
break
|
||||
line = line.rstrip()
|
||||
# we remove the "<<IMPORTS>>" marker and insert imports if needed
|
||||
if line == "<<IMPORTS>>":
|
||||
text = outfile.getvalue()
|
||||
wr.print("import " + import_name)
|
||||
for mod_name in find_imports(text):
|
||||
imp = "PySide6." + mod_name
|
||||
if imp != import_name:
|
||||
wr.print("import " + imp)
|
||||
wr.print()
|
||||
for mod, imports in filter_from_imports(FROM_IMPORTS, text):
|
||||
import_args = ', '.join(imports)
|
||||
if mod is None:
|
||||
# special case, a normal import
|
||||
wr.print(f"import {import_args}")
|
||||
else:
|
||||
wr.print(f"from {mod} import {import_args}")
|
||||
wr.print()
|
||||
wr.print()
|
||||
else:
|
||||
wr.print(line)
|
||||
if not options.quiet:
|
||||
options.logger.info(f"Generated: {outfilepath}")
|
||||
# PYSIDE-1735: .pyi files are no longer compatible with Python, because
|
||||
# enum classes contain ellipsis which a Python enum forbids.
|
||||
# We will implement tests with Mypy, instead.
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description=dedent("""\
|
||||
pyi_generator.py
|
||||
----------------
|
||||
|
||||
This script generates the .pyi file for an arbitrary module.
|
||||
You pass in the full path of a compiled, importable module.
|
||||
pyi_generator will try to generate an interface "<module>.pyi".
|
||||
"""))
|
||||
parser.add_argument("module",
|
||||
help="The full path name of an importable module binary (.pyd, .so)")
|
||||
parser.add_argument("--quiet", action="store_true", help="Run quietly")
|
||||
parser.add_argument("--check", action="store_true", help="Test the output")
|
||||
parser.add_argument("--outpath",
|
||||
help="the output directory (default = location of module binary)")
|
||||
options = parser.parse_args()
|
||||
module = options.module
|
||||
outpath = options.outpath
|
||||
|
||||
qtest_env = os.environ.get("QTEST_ENVIRONMENT", "")
|
||||
logging.basicConfig(level=logging.DEBUG if qtest_env else logging.INFO)
|
||||
logger = logging.getLogger("pyi_generator")
|
||||
|
||||
if outpath and not Path(outpath).exists():
|
||||
os.makedirs(outpath)
|
||||
logger.info(f"+++ Created path {outpath}")
|
||||
options._pyside_call = False
|
||||
options.is_ci = qtest_env == "ci"
|
||||
|
||||
options.logger = logger
|
||||
generate_pyi(module, outpath, options=options)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# eof
|
8
setup.cfg
Normal file
8
setup.cfg
Normal file
@ -0,0 +1,8 @@
|
||||
[metadata]
|
||||
version = 1.7.4
|
||||
home_page = https://github.com/python-LimeReport/python-LimeReport
|
||||
description = Python bindings for LimeReport
|
||||
author = Maxim Slipenko
|
||||
author_email = python-limereport@maks1ms.anonaddy.com
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
169
setup.py
Normal file
169
setup.py
Normal file
@ -0,0 +1,169 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import platform
|
||||
|
||||
from setuptools import Extension, setup
|
||||
from setuptools.command.build_ext import build_ext
|
||||
from wheel.bdist_wheel import bdist_wheel
|
||||
|
||||
|
||||
USE_ZINT_ENV_KEY = 'LIMEREPORT_USE_ZINT'
|
||||
USE_ZINT = os.environ.get(USE_ZINT_ENV_KEY, False)
|
||||
|
||||
def qt_version_tuple():
|
||||
proc = subprocess.Popen(['qmake', '-query', 'QT_VERSION'], stdout=subprocess.PIPE)
|
||||
output = proc.stdout.read()
|
||||
return output.decode('utf-8').strip().split('.')
|
||||
|
||||
qt_major = qt_version_tuple()[0]
|
||||
(p_major, p_minor, p_patchlevel) = platform.python_version_tuple()
|
||||
pyside_version = f"{(2 if qt_major == 5 else qt_major)}"
|
||||
|
||||
def get_name():
|
||||
n = f"LimeReport{pyside_version}"
|
||||
|
||||
if USE_ZINT:
|
||||
return n + "-Z"
|
||||
|
||||
return n
|
||||
|
||||
def get_license():
|
||||
if USE_ZINT:
|
||||
return "GPLv3"
|
||||
else:
|
||||
return "LGPLv3"
|
||||
|
||||
def get_license_files():
|
||||
if USE_ZINT:
|
||||
return ["COPYING.gpl3"]
|
||||
else:
|
||||
return ["COPYING.lgpl3"]
|
||||
|
||||
def get_classifiers():
|
||||
c = [
|
||||
"Environment :: X11 Applications :: Qt",
|
||||
"Programming Language :: C++",
|
||||
f"Programming Language :: Python :: {p_major}",
|
||||
f"Programming Language :: Python :: {p_major}.{p_minor}",
|
||||
]
|
||||
|
||||
if USE_ZINT:
|
||||
c.append("License :: OSI Approved :: GNU General Public License v3 (GPLv3)")
|
||||
else:
|
||||
c.append("License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)")
|
||||
|
||||
return c
|
||||
|
||||
PLAT_TO_CMAKE = {
|
||||
"win32": "Win32",
|
||||
"win-amd64": "x64",
|
||||
"win-arm32": "ARM",
|
||||
"win-arm64": "ARM64",
|
||||
}
|
||||
|
||||
class CMakeExtension(Extension):
|
||||
def __init__(self, name: str, sourcedir: str = "", py_limited_api = False) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
sources=[],
|
||||
py_limited_api=py_limited_api,
|
||||
)
|
||||
self.sourcedir = os.fspath(Path(sourcedir).resolve())
|
||||
|
||||
class BuildExt(build_ext):
|
||||
def build_extension(self, ext: CMakeExtension) -> None:
|
||||
# Must be in this form due to bug in .resolve() only fixed in Python 3.10+
|
||||
ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name)
|
||||
extdir = ext_fullpath.parent.resolve()
|
||||
|
||||
|
||||
debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug
|
||||
cfg = "Debug" if debug else "Release"
|
||||
|
||||
cmake_generator = os.environ.get("CMAKE_GENERATOR", "")
|
||||
|
||||
cmake_args = [
|
||||
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}",
|
||||
f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm
|
||||
f"-DENABLE_ZINT={USE_ZINT}"
|
||||
]
|
||||
build_args = []
|
||||
# Adding CMake arguments set as environment variable
|
||||
# (needed e.g. to build for ARM OSx on conda-forge)
|
||||
if "CMAKE_ARGS" in os.environ:
|
||||
cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item]
|
||||
|
||||
if self.compiler.compiler_type != "msvc":
|
||||
if not cmake_generator or cmake_generator == "Ninja":
|
||||
try:
|
||||
import ninja
|
||||
|
||||
ninja_executable_path = Path(ninja.BIN_DIR) / "ninja"
|
||||
cmake_args += [
|
||||
"-GNinja",
|
||||
f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}",
|
||||
]
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
# Single config generators are handled "normally"
|
||||
single_config = any(x in cmake_generator for x in {"NMake", "Ninja"})
|
||||
|
||||
# CMake allows an arch-in-generator style for backward compatibility
|
||||
contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"})
|
||||
|
||||
# Specify the arch if using MSVC generator, but only if it doesn't
|
||||
# contain a backward-compatibility arch spec already in the
|
||||
# generator name.
|
||||
if not single_config and not contains_arch:
|
||||
cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]]
|
||||
|
||||
# Multi-config generators have a different way to specify configs
|
||||
if not single_config:
|
||||
cmake_args += [
|
||||
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}"
|
||||
]
|
||||
build_args += ["--config", cfg]
|
||||
|
||||
if sys.platform.startswith("darwin"):
|
||||
# Cross-compile support for macOS - respect ARCHFLAGS if set
|
||||
archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", ""))
|
||||
if archs:
|
||||
cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))]
|
||||
|
||||
if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
|
||||
if hasattr(self, "parallel") and self.parallel:
|
||||
build_args += [f"-j{self.parallel}"]
|
||||
|
||||
build_temp = Path(self.build_temp) / ext.name
|
||||
if not build_temp.exists():
|
||||
build_temp.mkdir(parents=True)
|
||||
|
||||
subprocess.run(
|
||||
["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True
|
||||
)
|
||||
subprocess.run(
|
||||
["cmake", "--build", ".", *build_args], cwd=build_temp, check=True
|
||||
)
|
||||
|
||||
setup(
|
||||
name=get_name(),
|
||||
license = get_license(),
|
||||
classifiers = get_classifiers(),
|
||||
license_files = get_license_files(),
|
||||
ext_modules=[
|
||||
CMakeExtension(
|
||||
f"LimeReport{qt_major}",
|
||||
py_limited_api=True
|
||||
)
|
||||
],
|
||||
cmdclass={
|
||||
"build_ext": BuildExt,
|
||||
},
|
||||
install_requires=[
|
||||
f"PySide{pyside_version}"
|
||||
],
|
||||
)
|
Loading…
Reference in New Issue
Block a user