diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1fc8d4ee..1d661513 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -448,3 +448,72 @@ jobs: - name: Publish Packages working-directory: interop/csharp/ run: dotnet nuget push RadixDlt.RadixEngineToolkit.*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_ORG_API_KEY }} + publish-python-package: + needs: [build, generate-uniffi-bindings] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Fetch secrets + uses: radixdlt/public-iac-resuable-artifacts/fetch-secrets@main + with: + role_name: ${{ secrets.PYPI_SECRET_ROLE_NAME }} + app_name: 'radix-engine-toolkit' + step_name: 'pypi-credentials' + secret_prefix: 'PYPI' + secret_name: ${{ secrets.PYPI_SECRET_NAME }} + parse_json: true + - name: Install Dependencies + run: python3 -m pip install black build twine + - uses: actions/download-artifact@v3 + with: + path: artifacts + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Copy bindings + run: | + mkdir ./interop/python/radix_engine_toolkit + cp \ + ./artifacts/uniffi-bindings/radix_engine_toolkit_uniffi.py \ + ./interop/python/radix_engine_toolkit/__init__.py + - name: Dynamic Library Script replacement + run: | + python3 ./interop/python/replacement.py ./interop/python/radix_engine_toolkit/__init__.py + rm ./interop/python/replacement.py + - name: Stubs generation + run: | + stubgen \ + ./interop/python/radix_engine_toolkit/__init__.py \ + --output ./interop/python/ + - name: Code Formatting + run: | + python3 -m black ./interop/python/ + - name: Copy Dynamic Libraries + run: | + for TARGET_TRIPLE in "aarch64-apple-darwin" "x86_64-apple-darwin" "aarch64-unknown-linux-gnu" "x86_64-unknown-linux-gnu" "x86_64-pc-windows-gnu" + do + for FILE_NAME in "libradix_engine_toolkit_uniffi.so" "libradix_engine_toolkit_uniffi.dylib" "radix_engine_toolkit_uniffi.dll" + do + cp \ + ./artifacts/radix-engine-toolkit-uniffi-$TARGET_TRIPLE/$FILE_NAME \ + ./interop/python/radix_engine_toolkit/$TARGET_TRIPLE \ + 2>/dev/null + done + done + - name: Build Package + working-directory: ./interop/python/ + run: | + python3 -m build + - name: Check Builds + working-directory: ./interop/python/ + run: | + python3 -m twine check dist/* + - name: Publish + working-directory: ./interop/python/ + run: | + python3 -m twine upload -u ${{ env.PYPI_USERNAME }} -p ${{ env.PYPI_PASSWORD }} dist/* \ No newline at end of file diff --git a/interop/python/replacement.py b/interop/python/replacement.py new file mode 100644 index 00000000..4ef00dba --- /dev/null +++ b/interop/python/replacement.py @@ -0,0 +1,62 @@ +import platform +import ctypes +import sys +import os +import re + +def _uniffi_load_indirect() -> ctypes.CDLL: + """ + This is how we find and load the dynamic library provided by the component. + The dynamic library is assumed to exist right beside the code and it's name if the same as the + target triple. + + Currently, the supported architectures are: + * x86-64 and Arm64 Apple Darwin + * x86-64 and Arm64 Linux GNU + * x86-64 Win64 + """ + + def library_file_name() -> str: + is_x86: bool = platform.machine() in ("AMD64", "x86_64") + is_arm: bool = platform.machine() == "arm64" + system: str = platform.system() + + if is_x86 and system == "Darwin": + return "x86_64-apple-darwin" + elif is_arm and system == "Darwin": + return "aarch64-apple-darwin" + elif is_x86 and system == "Linux": + return "x86_64-unknown-linux-gnu" + elif is_arm and system == "Linux": + return "aarch64-unknown-linux-gnu" + elif is_x86 and system == "Windows": + return "x86_64-pc-windows-gnu" + else: + raise NotImplemented(f"No implementation of the Radix Engine Toolkit is available on your platform. Information detected: is_x86: {is_x86}, is_arm: {is_arm}, os: {system}") + + file_name: str = library_file_name() + path: str = os.path.join(os.path.dirname(__file__), file_name) + return ctypes.cdll.LoadLibrary(path) + +def main() -> None: + # The first arg is the path of the file which we want to do the replacement for. + path: str = sys.argv[1] + + # The regex expression used to read this function + regex: str = r'(def _uniffi_load_indirect\(\)\s*(->\s*.*)?:[\d\w\s\n:{}.\[\]=\(\)#,$`\'\"\-*\/><]*^$)' + + # Open THIS file and read the function definition through the regex expression. + with open(os.path.abspath(__file__), 'r') as file: + new_func_def: str = re.findall(regex, file.read(), re.MULTILINE)[0][0] + + # Open the replacement file, read it, apply regex replacement to it, and then write it again. + with open(path, 'r') as file: + content: str = file.read() + old_func_def: str = re.findall(regex, content, re.MULTILINE)[0][0] + + new_content: str = content.replace(old_func_def, new_func_def) + with open(path, 'w') as file: + file.write(new_content) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/interop/python/setup.py b/interop/python/setup.py new file mode 100644 index 00000000..1de462dd --- /dev/null +++ b/interop/python/setup.py @@ -0,0 +1,29 @@ +import setuptools + +setuptools.setup( + name="radix-engine-toolkit", + version="0.12.1", + packages=["radix_engine_toolkit"], + author="radixdlt", + description="A Python wrapper around the Radix Engine Toolkit that provides Radix Ledger primitives to Python.", + long_description="Long Description", + long_description_content_type="text/markdown", + license='Apache-2.0', + python_requires='>=3.6', + install_requires=[], + url="https://github.com/radixdlt/radix-engine-toolkit", + project_urls={ + "Bug Tracker": "https://github.com/radixdlt/radix-engine-toolkit/issues", + }, + classifiers=[ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Natural Language :: English", + "Operating System :: OS Independent", + "License :: OSI Approved :: MIT License", + ], +) \ No newline at end of file