diff --git a/.github/workflows/cd-cli-extras.yml b/.github/workflows/cd-cli-extras.yml index 962cf93..7121f3a 100644 --- a/.github/workflows/cd-cli-extras.yml +++ b/.github/workflows/cd-cli-extras.yml @@ -24,6 +24,8 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # ensure history is present for automatic versioning - uses: actions/setup-node@v4 - name: Set up Python3 diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 673fd78..bc42922 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -29,8 +29,9 @@ jobs: python3-version: "3.8" steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # ensure history is present for automatic versioning - uses: actions/setup-node@v4 - - name: Set up Python3 uses: actions/setup-python@v5 with: diff --git a/.github/workflows/ci-cli-extras.yml b/.github/workflows/ci-cli-extras.yml index 562f829..0c7716f 100644 --- a/.github/workflows/ci-cli-extras.yml +++ b/.github/workflows/ci-cli-extras.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + workflow_dispatch: jobs: test: @@ -18,6 +19,8 @@ jobs: python-version: ["3.9"] steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # ensure history is present for automatic versioning - uses: actions/setup-node@v4 - name: Set up Python uses: actions/setup-python@v5 @@ -38,9 +41,9 @@ jobs: pip install . - name: Download helics library and run pip install run: | - python setup.py download - python setup.py build_ext - pip install -e ".[cli]" + pip install ".[cli]" + env: + DOWNLOAD_BINARIES: 1 - name: Run Server run: | helics server & diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c88eb0b..d782927 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + workflow_dispatch: jobs: test: @@ -18,6 +19,8 @@ jobs: python-version: ["3.9"] steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # ensure history is present for automatic versioning - uses: actions/setup-node@v4 - name: Set up Python uses: actions/setup-python@v5 @@ -28,15 +31,15 @@ jobs: python -m pip install -U pip wheel setuptools cffi - name: Download helics library and run pip install run: | - python setup.py download - python setup.py build_ext - pip install -e ".[tests]" + pip install ".[tests]" + env: + DOWNLOAD_BINARIES: 1 - name: Install pytest dependencies run: | pip install pytest pytest-ordering pytest-cov pytest-runner - name: Generate coverage report run: | - pytest -vvv --cov=./ --cov-report=xml:unit.coverage.xml + pytest --import-mode importlib -vvv --cov=./ --cov-report=xml:unit.coverage.xml - name: Upload unit test coverage to Codecov uses: codecov/codecov-action@v4 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6769293..40c98a6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,6 +10,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # ensure history is present for automatic versioning - name: Setup Python uses: actions/setup-python@v5 with: @@ -17,8 +19,9 @@ jobs: - name: Install dependencies run: | python -m pip install -U pip wheel setuptools - python setup.py download - pip install -e ".[cli,docs]" + pip install ".[cli,docs]" + env: + DOWNLOAD_BINARIES: 1 - name: Copy README.md run: | cp README.md docs/index.md diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml new file mode 100644 index 0000000..aa1cbf3 --- /dev/null +++ b/.github/workflows/pythonpackage.yml @@ -0,0 +1,142 @@ +name: Build Python Packages + +on: + workflow_dispatch: + inputs: + use_qemu: + description: 'Use qemu to build linux aarch64, ppc64le & s390x' + required: false + default: 'false' + workflow_run: + workflows: ["HELICS Version Update"] + branches: [main] + types: + - completed + release: + types: published + pull_request: + branches: + - main + +jobs: + build-helics: + # No container needed, since the package is version agnostic + name: Build ${{ matrix.build }}${{ matrix.arch }} wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-20.04 + arch: "x86_64" + build: "manylinux_" + artifact_suffix: "manylinux_x86_64" + use_qemu: false + - os: ubuntu-20.04 + arch: "aarch64" + build: "manylinux_" + artifact_suffix: "manylinux_aarch64" + use_qemu: true + - os: windows-2019 + arch: "AMD64" + build: "" + artifact_suffix: "windows_x86_64" + use_qemu: false + - os: windows-2019 + arch: "x86" + build: "" + artifact_suffix: "windows_x86" + use_qemu: false + - os: macos-14 + arch: "universal2" + build: "" + artifact_suffix: "macos" + use_qemu: false + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # ensure history is present for automatic versioning + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.1.0 + if: runner.os == 'Linux' && matrix.use_qemu + - name: Build wheels + uses: pypa/cibuildwheel@v2.17.0 + env: + CIBW_ARCHS: "${{ matrix.arch }}" + CIBW_ARCHS_MACOS: "universal2" + CIBW_BUILD: "cp39-${{ matrix.build }}*" + CIBW_BUILD_VERBOSITY: 3 + CIBW_ENVIRONMENT: "CIBW_ARTIFACT_TYPE=${{ matrix.artifact_suffix }}" + CIBW_BEFORE_ALL_LINUX: > + mkdir /boost && + pushd /boost && + curl -L -o boost_1_76_0.tar.bz2 'https://sourceforge.net/projects/boost/files/boost/1.76.0/boost_1_76_0.tar.bz2' && + tar xf boost_1_76_0.tar.bz2 && + popd + CIBW_ENVIRONMENT_LINUX: BOOST_ROOT=/boost + - uses: actions/upload-artifact@v4 + with: + name: pyhelics-python-dist-${{matrix.artifact_suffix}} + path: wheelhouse/*.whl + + build-helics-sdist: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # for setuptools_scm to find tags + + - name: Install boost + run: + mkdir /tmp/boost && + pushd /tmp/boost && + wget --no-check-certificate 'https://sourceforge.net/projects/boost/files/boost/1.76.0/boost_1_76_0.tar.bz2' && + tar xf boost_1_76_0.tar.bz2 && + popd + - name: Build sdist + run: BOOST_ROOT="/tmp/boost" pipx run build --sdist + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: pyhelics-python-sdist + path: dist/*.tar.gz + + + check_helics-dist: + name: Check helics_apps Python packages + needs: [build-helics, build-helics-sdist] + runs-on: ubuntu-20.04 + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - run: pipx run twine check --strict dist/* + + publish-helics: + needs: [build-helics, build-helics-sdist, check_helics-dist] + runs-on: ubuntu-latest + if: github.event.action == 'published' || endsWith(github.ref, 'main') + environment: + name: pypi + url: https://pypi.org/p/helics + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: + - name: Get the built packages + uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: dist + + - name: Publish package to TestPyPI + if: endsWith(github.ref, 'main') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.test_pypi_helics_password }} + repository-url: https://test.pypi.org/legacy/ + + - name: Publish package to PyPI + if: github.event.action == 'published' + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/update-helics.yml b/.github/workflows/update-helics.yml index 995a7da..788b758 100644 --- a/.github/workflows/update-helics.yml +++ b/.github/workflows/update-helics.yml @@ -25,26 +25,6 @@ jobs: - name: Update HELICS version number run: | - # Update lines in the package file - version_line="__version__ = \"v${{ inputs.version }}\"" - - echo "::group::__version__ line" - echo "$version_line" - echo "::endgroup::" - sed -i "/__version__ =/c ${version_line}" helics/_version.py - - git add helics/_version.py - - echo "::group::git diff --staged" - git diff --staged - echo "::endgroup::" - - git commit -m "build(release): Bump to v${{ inputs.version }}" - - echo "::group::git show" - git show - echo "::endgroup::" - if [ "${{ inputs.dryrun }}" = "false" ]; then echo "Pushing changes" git push diff --git a/.gitignore b/.gitignore index 4a5baff..ccab350 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ __helics-server helics/static +helics-src helics/install +helics/_version.py docs/api _source *.db diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d1f0e80 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 3.15...3.26) +project(HELICS LANGUAGES CXX) # Must be "HELICS" due to its CMake files requiring CMAKE_PROJECT_NAME == PROJECT_NAME in order to build app executables +include(FetchContent) + +# NOTE: SKBUILD_PROJECT_VERSION strips some of the extra Python version components out, like postN and devM +# The setuptools_scm "no-guess-dev" version_scheme works well, since that leaves it with just the HELICS version + +# Note: Linux requires a hint for whether this wheel should be for manylinux (glibc) or musllinux +# -- The CIBW_ARTIFACT_TYPE environment variable is only expected to be set during official release builds using cibuildwheel on GHA CI systems +if(LINUX AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(HELICS_DOWNLOAD_URL https://github.com/GMLC-TDC/HELICS/releases/download/v${SKBUILD_PROJECT_VERSION}/Helics-${SKBUILD_PROJECT_VERSION}-Linux-x86_64.tar.gz) +elseif(APPLE AND CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(HELICS_DOWNLOAD_URL https://github.com/GMLC-TDC/HELICS/releases/download/v${SKBUILD_PROJECT_VERSION}/Helics-${SKBUILD_PROJECT_VERSION}-macOS-universal2.zip) +elseif(WIN32) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(HELICS_DOWNLOAD_URL https://github.com/GMLC-TDC/HELICS/releases/download/v${SKBUILD_PROJECT_VERSION}/Helics-${SKBUILD_PROJECT_VERSION}-win64.zip) + elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(HELICS_DOWNLOAD_URL https://github.com/GMLC-TDC/HELICS/releases/download/v${SKBUILD_PROJECT_VERSION}/Helics-${SKBUILD_PROJECT_VERSION}-win32.zip) + endif() +endif() + +# If we are building a binary wheel for an official release (or installing) and CIBUILDWHEEL is set, speed things up with pre-built HELICS binaries +if((DEFINED ENV{CIBUILDWHEEL} OR DEFINED ENV{DOWNLOAD_BINARIES}) AND HELICS_DOWNLOAD_URL AND SKBUILD_STATE STREQUAL "wheel") + message(STATUS "Downloading pre-built HELICS release") + FetchContent_Declare( + helics-bin + URL ${HELICS_DOWNLOAD_URL} + ) + FetchContent_MakeAvailable(helics-bin) + # Only include the things we actually need for the helics_apps in the wheels to minimize overall file size + include(GNUInstallDirs) # used so default BIN/LIB subfolder names will match HELICS + if(EXISTS ${helics-bin_SOURCE_DIR}/lib) + install( + DIRECTORY ${helics-bin_SOURCE_DIR}/lib/ + TYPE LIB + USE_SOURCE_PERMISSIONS + PATTERN * + ) + endif() + if(EXISTS ${helics-bin_SOURCE_DIR}/lib64) + install( + DIRECTORY ${helics-bin_SOURCE_DIR}/lib64/ + TYPE LIB + USE_SOURCE_PERMISSIONS + PATTERN * + ) + endif() + install( + DIRECTORY ${helics-bin_SOURCE_DIR}/bin/ + TYPE BIN + USE_SOURCE_PERMISSIONS + PATTERN * + ) + install( + DIRECTORY ${helics-bin_SOURCE_DIR}/include/ + TYPE INCLUDE + USE_SOURCE_PERMISSIONS + PATTERN * + ) + install( + DIRECTORY ${helics-bin_SOURCE_DIR}/share/ + TYPE DATA + USE_SOURCE_PERMISSIONS + PATTERN * + ) + + # If we made a wheel from pre-built HELICS binaries, exit early! + return() +endif() + +# No pre-built HELICS binaries being used, warn about limitations of building wheels or installing from an sdist +if(SKBUILD_STATE STREQUAL "wheel") + message(WARNING "Building HELICS from source (some features including ZeroMQ Core, IPC Core, and web server won't be available)") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(HELICS_DISABLE_GIT_OPERATIONS ON) +set(HELICS_ZMQ_SUBPROJECT ON) +set(HELICS_ZMQ_FORCE_SUBPROJECT ON) +set(HELICS_ENABLE_ZMQ_CORE ON) # TODO: vendor libzmq similar to other 3rd party libraries +set(HELICS_DISABLE_BOOST OFF) # TODO: headers are just needed to compile, maybe tell users to have it installed as prereq for building from source? +set(HELICS_BUILD_APP_LIBRARY ON) +set(HELICS_BUILD_APP_EXECUTABLES ON) + +if (NOT SKBUILD_STATE STREQUAL "sdist" AND + EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/helics-src/CMakeLists.txt") + message(STATUS "Using existing HELICS source directory") + set(FETCHCONTENT_SOURCE_DIR_helics "${CMAKE_CURRENT_SOURCE_DIR}/helics-src") + set(FETCHCONTENT_UPDATES_DISCONNECTED ON) +endif() +FetchContent_Declare( + helics + URL https://github.com/GMLC-TDC/HELICS/releases/download/v${SKBUILD_PROJECT_VERSION}/Helics-v${SKBUILD_PROJECT_VERSION}-source.tar.gz + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/helics-src +) + +# For making the sdist, only need to download the source code +if(SKBUILD_STATE STREQUAL "wheel") + add_library(helicsCpp98_ide INTERFACE) +endif() +FetchContent_MakeAvailable(helics) \ No newline at end of file diff --git a/helics/_version.py b/helics/_version.py deleted file mode 100644 index 3bf2ef9..0000000 --- a/helics/_version.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -__version__ = "v3.5.3" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d70f614 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,82 @@ +[build-system] +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[project] +name = "helics" +authors = [{ name = "GMLC-TDC", email = "helicsdevelopers@helics.org" }] +description = "Python HELICS bindings" +readme = "README.md" +readme_content_type = "text/markdown" +requires-python = ">=3.8" +keywords = ["helics", "co-simulation"] +license = { text = "BSD License" } +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: Unix", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Utilities", +] +dependencies = ["cffi>=1.6.0", "strip-hints", "click>=8"] +dynamic = ["version"] + +[project.optional-dependencies] +cli = [ + "helics_cli_extras==0.0.1", + "matplotlib", +] +tests = ["pytest", "pytest-ordering", "pytest-cov", "pytest-runner"] +dev = ["build", "pre-commit"] +docs = [ + "mkdocs", + "inari[mkdocs]", + "mkdocs-material", + "black", + "pygments", + "pymdown-extensions", +] + +[project.scripts] +helics = "helics.cli:cli" +helics-cli = "helics.cli:cli" +helics_app = "helics.bin:helics_app" +helics_broker = "helics.bin:helics_broker" +helics_broker_server = "helics.bin:helics_broker_server" +helics_player = "helics.bin:helics_player" +helics_recorder = "helics.bin:helics_recorder" + +[project.urls] +Homepage = "https://github.com/GMLC-TDC/pyhelics" +Discussions = "https://github.com/GMLC-TDC/HELICS/discussions" +Documentation = "https://python.helics.org/" +"Issue Tracker" = "https://github.com/GMLC-TDC/pyhelics/issues" +"Source Code" = "https://github.com/GMLC-TDC/pyhelics" + +[tool.cibuildwheel] +test-command = ["helics --version"] +linux.manylinux-x86_64-image = "manylinux_2_28" +linux.manylinux-aarch64-image = "manylinux_2_28" + +[tool.scikit-build] +minimum-version = "0.8" +cmake.version = ">=3.15" +sdist.include = ["helics-src", "helics/_version.py"] +sdist.exclude = ["build", "dist", "client", ".github", "helics_cli_extras"] +wheel.install-dir = "helics/install" +wheel.py-api = "py2.py3" +wheel.expand-macos-universal-tags = true +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +sdist.cmake = true + +[tool.setuptools_scm] # Section required +version_scheme = "no-guess-dev" # Works well with SKBUILD version having postN and devM tags dropped, good for getting current HELICS version to download +root = "." +version_file = "helics/_version.py" diff --git a/setup.py b/setup.py deleted file mode 100755 index 1b8b972..0000000 --- a/setup.py +++ /dev/null @@ -1,577 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import print_function - -import io -from glob import glob -from os.path import basename -from os.path import dirname -from os.path import join -from os.path import splitext - -try: - from pathlib import Path -except ImportError: - from pathlib2 import Path - -import re -import sys -import os -import struct -import shutil -import platform -import tarfile -import zipfile -import subprocess -import shlex - -from setuptools import setup, Extension, Command -from setuptools.dist import Distribution -from setuptools.command.build_ext import build_ext -from setuptools.command.build_py import build_py -from setuptools.command.develop import develop -from setuptools.command.egg_info import egg_info -from setuptools.command.sdist import sdist - -from wheel.bdist_wheel import bdist_wheel -from distutils.dir_util import copy_tree -from distutils import log - -try: - from urllib2 import urlopen -except ImportError: - from urllib.request import urlopen - - -HERE = os.path.dirname(os.path.abspath(__file__)) -IS_REPO = os.path.exists(os.path.join(HERE, ".git")) -STATIC_DIR = os.path.join(HERE, "helics", "static") -NODE_ROOT = os.path.join(HERE, "client") -NPM_PATH = os.pathsep.join( - [ - os.path.join(NODE_ROOT, "node_modules", ".bin"), - os.environ.get("PATH", os.defpath), - ] -) - - -def update_package_data(distribution): - """update package_data to catch changes during setup""" - build_py = distribution.get_command_obj("build_py") - # distribution.package_data = find_package_data() - # re-init build_py options which load package_data - build_py.finalize_options() - - -def read(*names, **kwargs): - with io.open( - join(dirname(__file__), *names), encoding=kwargs.get("encoding", "utf8") - ) as fh: - return fh.read() - - -PYHELICS_VERSION = read( - os.path.join(os.path.dirname(__file__), "helics", "_version.py"), encoding="utf-8" -) -PYHELICS_VERSION = ( - PYHELICS_VERSION.splitlines()[1].split()[2].strip('"').strip("'").lstrip("v") -) - -HELICS_VERSION = re.findall(r"(?:(\d+\.(?:\d+\.)*\d+))", PYHELICS_VERSION)[0] -# HELICS_VERSION = "{}-beta".format(HELICS_VERSION) - -CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) - -HELICS_SOURCE = os.path.join(CURRENT_DIRECTORY, "./_source") -PYHELICS_INSTALL = os.environ.get( - "PYHELICS_INSTALL", os.path.join(CURRENT_DIRECTORY, "./helics/install") -) - -DOWNLOAD_URL = "https://github.com/GMLC-TDC/HELICS/releases/download/v{version}/Helics-v{version}-source.tar.gz".format( - version=HELICS_VERSION -) - - -def create_default_url(helics_version, plat_name=""): - if "macos" in plat_name.lower(): - if ( - helics_version.startswith("3") and int(helics_version.split(".")[1]) >= 1 - ): # >= 3.1.x - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-macOS-universal2.zip".format( - helics_version=helics_version - ) - else: - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-macOS-x86_64.zip".format( - helics_version=helics_version - ) - elif "win" in plat_name.lower(): - if "win32" in plat_name.lower(): - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-win32.zip".format( - helics_version=helics_version - ) - else: - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-win64.zip".format( - helics_version=helics_version - ) - - elif "linux" in plat_name.lower(): - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-Linux-x86_64.tar.gz".format( - helics_version=helics_version - ) - elif platform.system() == "Darwin": - if ( - helics_version.startswith("3") and int(helics_version.split(".")[1]) >= 1 - ): # >= 3.1.x - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-macOS-universal2.zip".format( - helics_version=helics_version - ) - else: - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-macOS-x86_64.zip".format( - helics_version=helics_version - ) - elif platform.system() == "Windows": - if struct.calcsize("P") * 8 == 32: - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-win32.zip".format( - helics_version=helics_version - ) - else: - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-win64.zip".format( - helics_version=helics_version - ) - - elif platform.system() == "Linux": - default_url = "https://github.com/GMLC-TDC/HELICS/releases/download/v{helics_version}/Helics-{helics_version}-Linux-x86_64.tar.gz".format( - helics_version=helics_version - ) - else: - raise NotImplementedError("Unsupported platform {}".format(platform.system())) - - return default_url - - -def _is_symlink(file_info): - """ - Check the upper 4 bits of the external attribute for a symlink. - See: https://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute - Parameters - ---------- - file_info : zipfile.ZipInfo - The ZipInfo for a ZipFile - Returns - ------- - bool - A response regarding whether the ZipInfo defines a symlink or not. - """ - - return (file_info.external_attr >> 28) == 0xA - - -def _extract(file_info, output_dir, zip_ref): - """ - Unzip the given file into the given directory while preserving file permissions in the process. - Parameters - ---------- - file_info : zipfile.ZipInfo - The ZipInfo for a ZipFile - output_dir : str - Path to the directory where the it should be unzipped to - zip_ref : zipfile.ZipFile - The ZipFile we are working with. - Returns - ------- - string - Returns the target path the Zip Entry was extracted to. - """ - - # Handle any regular file/directory entries - if not _is_symlink(file_info): - return zip_ref.extract(file_info, output_dir) - - source = zip_ref.read(file_info.filename).decode("utf8") - link_name = os.path.normpath(os.path.join(output_dir, file_info.filename)) - - # make leading dirs if needed - leading_dirs = os.path.dirname(link_name) - if not os.path.exists(leading_dirs): - os.makedirs(leading_dirs) - - # If the link already exists, delete it or symlink() fails - if os.path.lexists(link_name): - os.remove(link_name) - - # Create a symbolic link pointing to source named link_name. - os.symlink(source, link_name) - - return link_name - - -def _set_permissions(zip_file_info, extracted_path): - """ - Sets permissions on the extracted file by reading the ``external_attr`` property of given file info. - Parameters - ---------- - zip_file_info : zipfile.ZipInfo - Object containing information about a file within a zip archive - extracted_path : str - Path where the file has been extracted to - """ - - # Permission information is stored in first two bytes. - permission = zip_file_info.external_attr >> 16 - if not permission: - return - - os.chmod(extracted_path, permission) - - -def _override_permissions(path, permission): - """ - Forcefully override the permissions on the path - Parameters - ---------- - path str - Path where the file or directory - permission octal int - Permission to set - """ - if permission: - os.chmod(path, permission) - - -def unzip(zip_file_path, output_dir, permission=None): - """ - Unzip the given file into the given directory while preserving file permissions in the process. - Parameters - ---------- - zip_file_path : str - Path to the zip file - output_dir : str - Path to the directory where the it should be unzipped to - permission : int - Permission to set in an octal int form - """ - extracted_path = None - with zipfile.ZipFile(zip_file_path, "r") as zip_ref: - # For each item in the zip file, extract the file and set permissions if available - for file_info in zip_ref.infolist(): - extracted_path = _extract(file_info, output_dir, zip_ref) - - # If the extracted_path is a symlink, do not set the permissions. If the target of the symlink does not - # exist, then os.chmod will fail with FileNotFoundError - if not os.path.islink(extracted_path): - _set_permissions(file_info, extracted_path) - _override_permissions(extracted_path, permission) - - if extracted_path is not None and not os.path.islink(extracted_path): - _override_permissions(output_dir, permission) - - -class HELICSDownloadCommand(Command): - description = "Download helics libraries dependency" - user_options = [ - ("pyhelics-install=", None, "path to pyhelics install folder"), - ("plat-name=", None, "platform name to embed in generated filenames"), - ] - - def initialize_options(self): - self.plat_name = "" - self.pyhelics_install = os.path.join(CURRENT_DIRECTORY, "./helics/install") - if os.path.exists(self.pyhelics_install): - shutil.rmtree(self.pyhelics_install) - - def finalize_options(self): - pass - - def run(self): - self.helics_url = create_default_url(HELICS_VERSION, self.plat_name) - print("Downloading {}".format(self.helics_url)) - r = urlopen(self.helics_url) - if r.getcode() == 200: - if self.helics_url.endswith(".zip"): - with open("./tmp.zip", "wb") as f: - f.write(r.read()) - unzip("./tmp.zip", self.pyhelics_install) - os.remove("./tmp.zip") - - if len(os.listdir(self.pyhelics_install)) == 1 and os.listdir( - self.pyhelics_install - )[0].startswith("Helics-"): - tmp = os.listdir(self.pyhelics_install)[0] - for folder in os.listdir(os.path.join(self.pyhelics_install, tmp)): - p = Path( - os.path.join(self.pyhelics_install, tmp, folder) - ).absolute() - parent_dir = p.parents[1] - p.rename(parent_dir / p.name) - else: - content = io.BytesIO(r.read()) - content.seek(0) - with tarfile.open(fileobj=content) as tf: - dirname = tf.getnames()[0].partition("/")[0] - tf.extractall() - shutil.move(dirname, self.pyhelics_install) - for file in os.listdir(os.path.join(self.pyhelics_install, "bin")): - f = Path(os.path.join(self.pyhelics_install, "bin", file)) - try: - import stat - - f.chmod(f.stat().st_mode | stat.S_IEXEC) - except: - pass - files = [ - "helics_api.h", - "helics_enums.h", - os.path.join("shared_api_library", "api-data.h"), - os.path.join("shared_api_library", "helics.h"), - os.path.join("shared_api_library", "helics_export.h"), - os.path.join("shared_api_library", "MessageFederate.h"), - os.path.join("shared_api_library", "MessageFilters.h"), - os.path.join("shared_api_library", "ValueFederate.h"), - os.path.join("shared_api_library", "helicsCallbacks.h"), - ] - IGNOREBLOCK = False - print("Writing to {}".format(os.path.abspath(self.pyhelics_install))) - for file in files: - if not os.path.isfile( - os.path.join(self.pyhelics_install, "include", "helics", file) - ): - continue - with open( - os.path.join(self.pyhelics_install, "include", "helics", file) - ) as f: - lines = [] - for line in f: - if line.startswith("#ifdef __cplusplus"): - IGNOREBLOCK = True - continue - if IGNOREBLOCK is True and line.startswith("#endif"): - IGNOREBLOCK = False - continue - if IGNOREBLOCK is True: - continue - if line.startswith("#"): - continue - lines.append(line) - data = "\n".join(lines) - data = data.replace("HELICS_EXPORT", "") - data = data.replace("HELICS_DEPRECATED_EXPORT", "") - with open( - os.path.join(self.pyhelics_install, "include", "helics", file), "w" - ) as f: - f.write(data) - - -class CMakeExtension(Extension): - def __init__(self, name, sourcedir=HELICS_SOURCE): - Extension.__init__(self, name, sources=[]) - self.sourcedir = sourcedir - - -class HELICSCMakeBuild(build_ext): - def run(self): - try: - out = subprocess.check_output(["cmake", "--version"]) - cmake_version = re.search( - r"version\s*([\d.]+)", out.decode().lower() - ).group(1) - cmake_version = [int(i) for i in cmake_version.split(".")] - if cmake_version < [3, 5, 1]: - raise RuntimeError("CMake >= 3.5.1 is required to build helics") - - except OSError: - if not os.path.exists(PYHELICS_INSTALL): - raise RuntimeError( - "CMake must be installed to build the following extensions: " - + ", ".join(e.name for e in self.extensions) - ) - - for ext in self.extensions: - self.build_extension(ext) - - def build_extension(self, ext): - self.helics_url = DOWNLOAD_URL - self.helics_source = HELICS_SOURCE - if not os.path.exists(PYHELICS_INSTALL): - r = urlopen(self.helics_url) - if r.getcode() == 200: - content = io.BytesIO(r.read()) - content.seek(0) - with tarfile.open(fileobj=content) as tf: - tf.extractall(self.helics_source) - else: - return - - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - extdir = os.path.join(extdir, "helics", "install") - # required for auto - detection of auxiliary "native" libs - if not extdir.endswith(os.path.sep): - extdir += os.path.sep - - cmake_args = [ - "-DHELICS_DISABLE_GIT_OPERATIONS=OFF", - "-DHELICS_ZMQ_FORCE_SUBPROJECT=ON", - "-DHELICS_ZMQ_SUBPROJECT=ON", - "-DHELICS_DISABLE_BOOST=ON", - "-DCMAKE_BUILD_TYPE=Release", - "-DCMAKE_INSTALL_PREFIX={}".format(extdir), - ] - - cfg = "Debug" if self.debug else "Release" - build_args = ["--config", cfg] - - if platform.system() == "Windows": - cmake_args += [ - "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir) - ] - if sys.maxsize > 2**32: - cmake_args += ["-A", "x64"] - build_args += ["--", "/m"] - else: - cmake_args += ["-DCMAKE_BUILD_TYPE=" + cfg] - build_args += ["--", "-j"] - - env = os.environ.copy() - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) - subprocess.check_call( - ["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env - ) - cmd = " ".join(["cmake", "--build", ".", "--target", "install"] + build_args) - print(cmd) - subprocess.check_call(shlex.split(cmd), cwd=self.build_temp) - - files = [ - "helics_api.h", - "helics_enums.h", - os.path.join("shared_api_library", "api-data.h"), - os.path.join("shared_api_library", "helics.h"), - os.path.join("shared_api_library", "helics_export.h"), - os.path.join("shared_api_library", "MessageFederate.h"), - os.path.join("shared_api_library", "MessageFilters.h"), - os.path.join("shared_api_library", "ValueFederate.h"), - os.path.join("shared_api_library", "helicsCallbacks.h"), - ] - IGNOREBLOCK = False - for file in files: - if not os.path.isfile(os.path.join(extdir, "include", "helics", file)): - continue - with open(os.path.join(extdir, "include", "helics", file)) as f: - lines = [] - for line in f: - if line.startswith("#ifdef __cplusplus"): - IGNOREBLOCK = True - continue - if IGNOREBLOCK is True and line.startswith("#endif"): - IGNOREBLOCK = False - continue - if IGNOREBLOCK is True: - continue - if line.startswith("#"): - continue - lines.append(line) - data = "\n".join(lines) - data = data.replace("HELICS_EXPORT", "") - data = data.replace("HELICS_DEPRECATED_EXPORT", "") - with open(os.path.join(extdir, "include", "helics", file), "w") as f: - f.write(data) - - -install_requires = ["cffi>=1.6.0", "strip-hints", "click>=8"] - -if sys.version_info < (3, 4): - install_requires.append("enum34") - - -class HelicsBdistWheel(bdist_wheel): - def get_tag(self): - rv = bdist_wheel.get_tag(self) - return ("py3", "none") + rv[2:] - - -cmdclass = { - "download": HELICSDownloadCommand, - "build_ext": HELICSCMakeBuild, - "bdist_wheel": HelicsBdistWheel, - "develop": develop, - "build_py": build_py, - "egg_info": egg_info, - "sdist": sdist, -} - - -class BinaryDistribution(Distribution): - def is_pure(self): - return False - - -helics_cli_install_requires = [ - "helics_cli_extras==0.0.1", - "matplotlib", -] - -setup( - name="helics", - version=PYHELICS_VERSION, - license="MIT", - description="Python HELICS bindings", - long_description=read("README.md"), - long_description_content_type="text/markdown", - author="Dheepak Krishnamurthy", - author_email="me@kdheepak.com", - url="https://github.com/GMLC-TDC/pyhelics", - packages=["helics"], - distclass=BinaryDistribution, - py_modules=[splitext(basename(path))[0] for path in glob("helics/*.py")], - # data_files=[("helics", ["install/include/helics/chelics.h"])], - # cffi_modules=['helics/helics_build.py:ffibuilder'], - package_data={"helics": ["install/*"]}, - ext_modules=[CMakeExtension("helics")], - include_package_data=True, - zip_safe=False, - classifiers=[ - # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: Unix", - "Operating System :: POSIX", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Utilities", - ], - project_urls={"Issue Tracker": "https://github.com/GMLC-TDC/pyhelics/issues"}, - keywords=["helics", "co-simulation"], - python_requires=">=3.6", - install_requires=install_requires, - extras_require={ - "cli": install_requires + helics_cli_install_requires, - "tests": ["pytest", "pytest-ordering", "pytest-cov", "pytest-runner"], - "docs": [ - "mkdocs", - "inari[mkdocs]", - "mkdocs-material", - "black", - "pygments", - "pymdown-extensions", - ], - }, - cmdclass=cmdclass, - entry_points={ - "console_scripts": [ - "helics=helics.cli:cli", - "helics-cli=helics.cli:cli", - "helics_app=helics.bin:helics_app", - "helics_broker=helics.bin:helics_broker", - "helics_broker_server=helics.bin:helics_broker_server", - "helics_player=helics.bin:helics_player", - "helics_recorder=helics.bin:helics_recorder", - ] - }, -)