diff --git a/.cirrus.yml b/.cirrus.yml index 4c49655..56c31a4 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -46,6 +46,8 @@ cirrus_wheels_macos_arm64_task: build_script: - which python + # needed for discover_version.py + - git fetch --all # needed for submodules - git submodule update --init - uname -m diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 11792d6..64a39fe 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -3,40 +3,86 @@ on: branches: [ master ] tags: - v* + pull_request: + branches: [ master ] jobs: build_wheels: - name: Build wheels on ${{ matrix.os }} + name: Build wheels on ${{ matrix.os }} for Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04, macos-12, windows-2019] + os: [ubuntu-22.04, macos-latest, macos-15-intel] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + exclude: + # macOS ARM64 (macos-latest) doesn't support Python 3.8 + - os: macos-latest + python-version: '3.8' + # Windows builds disabled due to meson-python issue with .lib import libraries + # See: https://github.com/libAtoms/extxyz/pull/17 fail-fast: false steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Checkout submodules - run: git submodule update --init --recursive + run: git submodule update --init --recursive + + - name: Set Python version for cibuildwheel + id: set-py-version + shell: bash + run: | + PY_VERSION="${{ matrix.python-version }}" + CIBW_PYTHON="cp${PY_VERSION//.}" + echo "cibw-python=${CIBW_PYTHON}" >> $GITHUB_OUTPUT + + - name: Install PCRE2 via vcpkg (Windows only) + if: runner.os == 'Windows' + shell: bash + run: | + # Install pkg-config lite (ensures working pkg-config before Strawberry Perl) + choco install pkgconfiglite -y + + # Install PCRE2 using vcpkg + vcpkg install pcre2:x64-windows + + # Set up environment for meson/pkg-config + VCPKG_ROOT="C:/vcpkg" + PCRE2_DIR="${VCPKG_ROOT}/installed/x64-windows" + + # Add pkgconfiglite to PATH first (before Strawberry Perl) + echo "C:/ProgramData/chocolatey/lib/pkgconfiglite/tools/bin" >> $GITHUB_PATH + + echo "CMAKE_PREFIX_PATH=${PCRE2_DIR}" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=${PCRE2_DIR}/lib/pkgconfig" >> $GITHUB_ENV + echo "PCRE2_ROOT=${PCRE2_DIR}" >> $GITHUB_ENV + echo "LIB=${PCRE2_DIR}/lib;$LIB" >> $GITHUB_ENV + echo "INCLUDE=${PCRE2_DIR}/include;$INCLUDE" >> $GITHUB_ENV + echo "PATH=${PCRE2_DIR}/bin;$PATH" >> $GITHUB_ENV + - name: Build wheels - uses: pypa/cibuildwheel@v2.12.1 + uses: pypa/cibuildwheel@v2.22.0 env: - CIBW_SKIP: cp27-* cp35-* pp* *musl* "*-macosx_arm64" + CIBW_BUILD: ${{ steps.set-py-version.outputs.cibw-python }}-* + CIBW_SKIP: pp* *musl* CIBW_ARCHS_LINUX: "auto64" - CIBW_ARCHS_MACOS: "x86_64" - CIBW_BEFORE_ALL_LINUX: "which yum && yum install -y pcre2-devel zlib-devel" - CIBW_TEST_REQUIRES: pytest - CIBW_TEST_COMMAND: "pytest -v {package}/tests" + CIBW_ARCHS_MACOS: ${{ matrix.os == 'macos-latest' && 'arm64' || 'x86_64' }} + CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=${{ matrix.os == 'macos-latest' && '15.0' || '14.0' }}" + CIBW_ARCHS_WINDOWS: "AMD64" - # # Uncomment to get SSH access for testing + # Uncomment to get SSH access for testing # - name: Setup tmate session # if: failure() # uses: mxschmitt/action-tmate@v3 # timeout-minutes: 15 - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: + name: wheels-${{ matrix.os }}-py${{ matrix.python-version }} path: ./wheelhouse/*.whl - name: Release wheels @@ -50,10 +96,10 @@ jobs: - name: Check tag id: check-tag run: | - if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo ::set-output name=match::true + if [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "match=true" >> $GITHUB_OUTPUT fi - + - name: Deploy to PyPI if: steps.check-tag.outputs.match == 'true' run: | diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 27a05f4..9d0d1de 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,22 +16,28 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Checkout submodules run: git submodule update --init --recursive - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install system dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y libpcre2-dev - name: Install Python dependencies run: | python -m pip install --upgrade pip python -m pip install flake8 pytest - python -m pip install -e . --verbose + python -m pip install . --verbose - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names @@ -63,7 +69,7 @@ jobs: - name: Test with pytest run: | USE_FORTRAN=T pytest -v --ignore QUIP - # # Uncomment to get SSH access for testing + # Uncomment to get SSH access for testing # - name: Setup tmate session # if: failure() # uses: mxschmitt/action-tmate@v3 diff --git a/.gitignore b/.gitignore index 75bd60c..bccd006 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,8 @@ extxyz.egg-info/ .DS_Store *.o pcre2-10.37/ -venv/ \ No newline at end of file +venv/ + +# Claude notes (don't commit) +MESON_BUILD_STATUS.md +CLAUDE.md \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index dcd9747..d440586 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "extxyz/libcleri"] path = libcleri - url = https://github.com/transceptor-technology/libcleri + url = https://github.com/libAtoms/libcleri diff --git a/discover_version.py b/discover_version.py new file mode 100644 index 0000000..d741435 --- /dev/null +++ b/discover_version.py @@ -0,0 +1,105 @@ +# +# Copyright 2022 Lars Pastewka +# +# ### MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +# +# This is the most minimal-idiotic way of discovering the version that I +# could come up with. It deals with the following issues: +# * If we are installed, we can get the version from package metadata, +# either via importlib.metadata or from pkg_resources. This also holds for +# wheels that contain the metadata. We are good! Yay! +# * If we are not installed, there are two options: +# - We are working within the source git repository. Then +# git describe --tags --always +# yields a reasonable version descriptor, but that is unfortunately not +# PEP 440 compliant (see https://peps.python.org/pep-0440/). We need to +# mangle the version string to yield something compatible. +# - If we install from a source tarball, we need to parse PKG-INFO manually. +# + +import re +import subprocess + +class CannotDiscoverVersion(Exception): + pass + + +def get_version_from_pkg_info(): + """ + Discover version from PKG-INFO file. + """ + f = open('PKG-INFO', 'r') + l = f.readline() + while l: + if l.startswith('Version:'): + return l[8:].strip() + l = f.readline() + raise CannotDiscoverVersion("No line starting with 'Version:' in 'PKG-INFO'.") + + +def get_version_from_git(): + """ + Discover version from git repository. + """ + try: + git_describe = subprocess.run( + ['git', 'describe', '--tags', '--dirty', '--always'], + stdout=subprocess.PIPE) + except (FileNotFoundError, OSError) as e: + # git command not found in PATH (common in isolated build environments like Windows cibuildwheel) + raise CannotDiscoverVersion(f'git command not found: {e}') + + if git_describe.returncode != 0: + raise CannotDiscoverVersion('git execution failed.') + version = git_describe.stdout.decode('latin-1').strip() + + dirty = version.endswith('-dirty') + + # Make version PEP 440 compliant + if dirty: + version = version.replace('-dirty', '') + version = version.strip('v') # Remove leading 'v' if it exists + version = version.replace('-', '.dev', 1) + version = version.replace('-', '+', 1) + if dirty: + version += '.dirty' + + return version + + +try: + version = get_version_from_git() +except CannotDiscoverVersion: + try: + version = get_version_from_pkg_info() + except (CannotDiscoverVersion, FileNotFoundError): + # Fallback for isolated build environments (e.g., Windows cibuildwheel) + # where neither git nor PKG-INFO is available + # Use a development version that will be replaced by the build system + version = '0.0.0+unknown' + +# +# Print version to screen +# + +print(version) diff --git a/libcleri b/libcleri index 968759b..2944749 160000 --- a/libcleri +++ b/libcleri @@ -1 +1 @@ -Subproject commit 968759b0ffd8a41b87f9f47422fec9c97cc26ab8 +Subproject commit 2944749ba532144c3b98aa3c06965473db1c7feb diff --git a/libcleri_meson.build b/libcleri_meson.build new file mode 100644 index 0000000..5c2e59f --- /dev/null +++ b/libcleri_meson.build @@ -0,0 +1,32 @@ +inc_dir = include_directories('inc') + +libcleri = static_library('cleri', + 'src/children.c', + 'src/choice.c', + 'src/dup.c', + 'src/expecting.c', + 'src/grammar.c', + 'src/keyword.c', + 'src/kwcache.c', + 'src/list.c', + 'src/node.c', + 'src/cleri.c', + 'src/olist.c', + 'src/optional.c', + 'src/parse.c', + 'src/prio.c', + 'src/ref.c', + 'src/regex.c', + 'src/repeat.c', + 'src/rule.c', + 'src/sequence.c', + 'src/this.c', + 'src/token.c', + 'src/tokens.c', + 'src/version.c', + dependencies: pcre2, + include_directories: inc_dir) + +cleri = declare_dependency( + link_with: libcleri, + include_directories: inc_dir) \ No newline at end of file diff --git a/libextxyz/Makefile b/libextxyz/Makefile index 0d98955..6db3ea4 100644 --- a/libextxyz/Makefile +++ b/libextxyz/Makefile @@ -22,13 +22,15 @@ else dlext ?= so endif +.PHONY: default all libcleri install_libcleri install clean + default: libextxyz.${dlext} all: libcleri extxyz_kv_grammar.c extxyz_kv_grammar.h libextxyz.${dlext} libcleri: if [ -z ${LIBCLERI_PATH} ]; then echo "LIBCLERI_PATH must be defined" 1>&2; exit 1; fi - ${MAKE} -C ${LIBCLERI_PATH} -f makefile + ${MAKE} -C ${LIBCLERI_PATH} -f makefile libcleri.a install_libcleri: libcleri ${MAKE} -C ${LIBCLERI_PATH} -f makefile install INSTALL_PATH=${prefix} @@ -53,11 +55,13 @@ install: libextxyz.${dlext} test_fortran_main.o: fextxyz.o extxyz.o extxyz_kv_grammar.o -fextxyz: test_fortran_main.o fextxyz.o extxyz.o extxyz_kv_grammar.o - ${F90} -g $^ -o $@ ${LIBCLERI_PATH}/libcleri.a ${LDFLAGS} ${QUIP_LDFLAGS} +FOBJS = test_fortran_main.o fextxyz.o extxyz.o extxyz_kv_grammar.o +fextxyz: libcleri ${FOBJS} + ${F90} -g ${FOBJS} -o $@ ${LIBCLERI_PATH}/libcleri.a ${LDFLAGS} ${QUIP_LDFLAGS} -cextxyz: test_C_main.o extxyz.o extxyz_kv_grammar.o - ${F90} -g $^ -o $@ ${LIBCLERI_PATH}/libcleri.a ${LDFLAGS} +COBJS = test_C_main.o extxyz.o extxyz_kv_grammar.o +cextxyz: libcleri ${COBJS} + ${F90} -g ${COBJS} -o $@ ${LIBCLERI_PATH}/libcleri.a ${LDFLAGS} clean: - rm -rf libextxyz.${dlext} *.o \ No newline at end of file + rm -rf libextxyz.${dlext} *.o diff --git a/libextxyz/extxyz.c b/libextxyz/extxyz.c index 9160483..7a531a3 100644 --- a/libextxyz/extxyz.c +++ b/libextxyz/extxyz.c @@ -235,9 +235,9 @@ int parse_tree(cleri_node_t *node, DictEntry **cur_entry, int *in_seq, int *in_k } //DEBUG printf("looping over children\n"); //DEBUG - for (cleri_children_t *child = node->children; child; child = child->next) { + for (cleri_node_t *child = node->children; child; child = child->next) { //DEBUG printf("child\n"); //DEBUG - int err = parse_tree(child->node, cur_entry, in_seq, in_kv_pair, in_old_one_d, error_message); + int err = parse_tree(child, cur_entry, in_seq, in_kv_pair, in_old_one_d, error_message); if (err) { return err; } @@ -319,8 +319,8 @@ void dump_tree(cleri_node_t *node, char *prefix) { printf("%snode NULL\n", prefix); } - for (cleri_children_t *child = node->children; child; child = child->next) { - dump_tree(child->node, new_prefix); + for (cleri_node_t *child = node->children; child; child = child->next) { + dump_tree(child, new_prefix); } free(new_prefix); diff --git a/libextxyz/meson.build b/libextxyz/meson.build new file mode 100644 index 0000000..e097227 --- /dev/null +++ b/libextxyz/meson.build @@ -0,0 +1,18 @@ +# Use 'python' which works cross-platform inside cibuildwheel +python_for_codegen = find_program('python', required: true) + +run_command( + python_for_codegen, + '..' / 'python' / 'extxyz' / 'extxyz_kv_grammar.py', + check: true +) + +# Build and install the extension module +module = python.extension_module( + '_extxyz', # Name of the module + ['extxyz.c', 'extxyz_kv_grammar.c'], + install: true, # Install it + subdir: 'extxyz', + gnu_symbol_visibility: 'default', # keep symbols public + dependencies: [cleri, pcre2] +) \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..0062754 --- /dev/null +++ b/meson.build @@ -0,0 +1,71 @@ +# https://mesonbuild.com/ +project( + 'extxyz', # Project name + 'c', + # Use 'python' which works cross-platform inside cibuildwheel + version: run_command('python', 'discover_version.py', check: true).stdout().strip(), # Project version +) + +# https://mesonbuild.com/Python-module.html +pymod = import('python') +# Don't specify 'python3' - let Meson find the Python used to run itself +python = pymod.find_installation( + required: true, + pure: false +) + +host_system = host_machine.system() +cc = meson.get_compiler('c') + +# Determine MSVC runtime library (for PCRE2 library name) +vs_crt = 'release' +vs_crt_opt = get_option('b_vscrt') +if vs_crt_opt in ['mdd', 'mtd'] + vs_crt = 'debug' +elif vs_crt_opt == 'from_buildtype' + if get_option('buildtype') == 'debug' + vs_crt = 'debug' + endif +endif + +# adapted from https://gitlab.gnome.org/GNOME/glib/-/blob/cd9a5c173a154e326a3ebaa28cfe41a7444625c5/meson.build#L2020-L2052 +pcre2 = dependency('libpcre2-8', version: '>= 10.23', required : false) +if not pcre2.found() + if cc.get_id() == 'msvc' or cc.get_id() == 'clang-cl' + # MSVC: Search for the PCRE2 library by the configuration, which corresponds + # to the output of CMake builds of PCRE2. Note that debugoptimized + # is really a Release build with .PDB files. + if vs_crt == 'debug' + pcre2 = cc.find_library('pcre2d-8', required : false) + else + pcre2 = cc.find_library('pcre2-8', required : false) + endif + elif host_system == 'windows' + # MinGW/GCC on Windows: Try to find the library directly + pcre2 = cc.find_library('pcre2-8', required : false) + endif +endif + +# Try again with the fallback +if not pcre2.found() + pcre2 = dependency('libpcre2-8', required : true, fallback : ['pcre2', 'libpcre2_8']) + use_pcre2_static_flag = true +elif host_system == 'windows' + pcre2_static = cc.links('''#define PCRE2_STATIC + #define PCRE2_CODE_UNIT_WIDTH 8 + #include + int main() { + void *p = NULL; + pcre2_code_free(p); + return 0; + }''', + dependencies: pcre2, + name : 'Windows system PCRE2 is a static build') + use_pcre2_static_flag = pcre2_static +else + use_pcre2_static_flag = false +endif + +subdir('libcleri') +subdir('libextxyz') +subdir('python/extxyz') \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a55b09a..35ddbae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,58 @@ [build-system] -# Minimum requirements for the build system to execute. -requires = ["setuptools", "wheel", "pyleri>=1.3.3"] -build-backend = "setuptools.build_meta" +requires = ["meson>=1.0.0", "meson-python>=0.13.0", "ninja", "pyleri>=1.3.0", "oldest-supported-numpy"] +build-backend = "mesonpy" + +[project] +name = "extxyz" +description = "Extended XYZ file format tools" +readme = "README.md" +license = { file = "LICENSE" } +authors = [ + { name = "James Kermode", email = "james.kermode@gmail.com"}, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python" +] +requires-python = ">=3.8.0" +dynamic = [ "version" ] +dependencies = [ + "numpy>=1.13.0", + 'pyleri>=1.3.3', + 'ase>=3.17', + "pip>=24.0", +] + +[project.optional-dependencies] +test = [ + "pytest", + +] + +[project.scripts] +extxyz = "extxyz.cli:main" + +[project.urls] +documentation = "http://libatoms.github.io/extxyz/" +repository = "https://github.com/libAtoms/extxyz" + +[tool.cibuildwheel] +test-requires = "pytest" +test-command = "pytest -v {package}/tests" + +[tool.cibuildwheel.linux] +before-all = "which yum && yum install -y pcre2-devel zlib-devel" + +[tool.cibuildwheel.macos] +before-all = "brew install pcre2 pkg-config" + +[tool.cibuildwheel.macos.environment] +PKG_CONFIG_PATH = "/opt/homebrew/lib/pkgconfig:/usr/local/lib/pkgconfig" + +[tool.cibuildwheel.windows.environment] +CMAKE_PREFIX_PATH = "C:/vcpkg/installed/x64-windows" +PKG_CONFIG_PATH = "C:/vcpkg/installed/x64-windows/lib/pkgconfig" +PCRE2_ROOT = "C:/vcpkg/installed/x64-windows" +LIB = "C:/vcpkg/installed/x64-windows/lib" +INCLUDE = "C:/vcpkg/installed/x64-windows/include" +PATH = "C:/vcpkg/installed/x64-windows/bin;C:/Program Files/Git/bin;C:/Windows/System32;C:/Windows;$PATH" diff --git a/python/extxyz/__init__.py b/python/extxyz/__init__.py index 8d9ec45..f640f2f 100644 --- a/python/extxyz/__init__.py +++ b/python/extxyz/__init__.py @@ -1,3 +1,4 @@ from .extxyz import iread, read, write, ExtXYZTrajectoryWriter +from ._version import __version__ -__all__ = ['iread', 'read', 'write', 'ExtXYZTrajectoryWriter'] +__all__ = ['iread', 'read', 'write', 'ExtXYZTrajectoryWriter', '__version__'] diff --git a/python/extxyz/_version.py.in b/python/extxyz/_version.py.in new file mode 100644 index 0000000..ed0561c --- /dev/null +++ b/python/extxyz/_version.py.in @@ -0,0 +1,27 @@ +# +# Copyright 2017 Lars Pastewka (U. Freiburg) +# +# matscipy - Materials science with Python at the atomic-scale +# https://github.com/libAtoms/matscipy +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (built by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +__version__ = '@VERSION@' diff --git a/python/extxyz/cextxyz.py b/python/extxyz/cextxyz.py index ae94866..ec5689b 100644 --- a/python/extxyz/cextxyz.py +++ b/python/extxyz/cextxyz.py @@ -1,6 +1,7 @@ import os import ctypes from ctypes.util import find_library +import sysconfig import copy @@ -42,9 +43,8 @@ class Dict_entry_struct(ctypes.Structure): Dict_entry_ptr = ctypes.POINTER(Dict_entry_struct) -# _extxyz.so is actually created as a python extension, but custom builder is -# used to make the name just _extxyz.so, rather than _extxyz.cpython--.so -extxyz_so = os.path.join(os.path.abspath(os.path.dirname(__file__)), '_extxyz.so') +suffix = sysconfig.get_config_var('EXT_SUFFIX') +extxyz_so = os.path.join(os.path.abspath(os.path.dirname(__file__)), f'_extxyz{suffix}') extxyz = ctypes.CDLL(extxyz_so) extxyz.compile_extxyz_kv_grammar.restype = cleri_grammar_t_ptr diff --git a/python/extxyz/extxyz.py b/python/extxyz/extxyz.py index 2fd27f7..14bf61c 100644 --- a/python/extxyz/extxyz.py +++ b/python/extxyz/extxyz.py @@ -9,8 +9,6 @@ from io import StringIO import numpy as np -from numpy.core.arrayprint import (get_printoptions, - _get_format_function) import ase.units as units from ase.utils import lazyproperty diff --git a/python/extxyz/meson.build b/python/extxyz/meson.build new file mode 100644 index 0000000..8fc4607 --- /dev/null +++ b/python/extxyz/meson.build @@ -0,0 +1,25 @@ +conf_data = configuration_data() +conf_data.set('VERSION', meson.project_version()) +version_file = configure_file( + input: '_version.py.in', + output: '_version.py', + configuration: conf_data +) + +# Pure Python sources +python_sources = [ + '__init__.py', + '__main__.py', + version_file, + 'cli.py', + 'extxyz.py', + 'cextxyz.py', + 'extxyz_kv_grammar.py', + 'utils.py' +] + +# Install pure Python +python.install_sources( + python_sources, + subdir: 'extxyz' +) \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 2eea0f9..0000000 --- a/setup.py +++ /dev/null @@ -1,139 +0,0 @@ -import sys -import tempfile -import atexit -import shutil -import sysconfig -import pathlib -import os -import subprocess -from setuptools import setup, Extension -# as per https://stackoverflow.com/questions/19569557/pip-not-picking-up-a-custom-install-cmdclass -from setuptools.command.install import install as setuptools__install -from setuptools.command.develop import develop as setuptools__develop -from setuptools.command.egg_info import egg_info as setuptools__egg_info -from setuptools.command.build_ext import build_ext as setuptools__build_ext - -def build_grammar(): - # check if we need to run the grammar definition to regenerate .c and .h - py_grammar_file = pathlib.Path('./grammar/extxyz_kv_grammar.py') - c_grammar_file = pathlib.Path('./libext/extxyz_kv_grammar.c') - - if not c_grammar_file.exists() or (py_grammar_file.stat().st_mtime > c_grammar_file.stat().st_mtime): - sys.path.insert(0, './python/extxyz') - import extxyz_kv_grammar - del sys.path[0] - extxyz_kv_grammar.write_grammar('./libextxyz') - -def which(program): - import os - - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - - return None - -def build_pcre2(): - pcre2_config = which('pcre2-config') - print(f'which(pcre2-config) = {pcre2_config}') - if pcre2_config is None: - pcre2_version = '10.42' - download_url = f"https://github.com/PCRE2Project/pcre2/releases/download/pcre2-{pcre2_version}/pcre2-{pcre2_version}.tar.gz" - print(f'pcre2-config not found so downloading and installing PCRE2-{pcre2_version} from {download_url}') - - tempdir = tempfile.mkdtemp() - # atexit.register(lambda: shutil.rmtree(tempdir)) # cleanup tempdir when Python exits - build_dir = os.path.abspath(f"{tempdir}/pcre2-{pcre2_version}/build") - pcre2_config = os.path.join(build_dir, 'bin', 'pcre2-config') - - orig_dir = os.getcwd() - os.chdir(tempdir) - try: - subprocess.call(["curl", "-L", download_url, "-o", "pcre2.tar.gz"]) - subprocess.call(["tar", "xvzf", "pcre2.tar.gz"]) - subprocess.call(["./configure", f"--prefix={build_dir}"], cwd=f"pcre2-{pcre2_version}") - subprocess.call("make", cwd=f"pcre2-{pcre2_version}") - subprocess.call(["make", "install"], cwd=f"pcre2-{pcre2_version}") - finally: - os.chdir(orig_dir) - - pcre2_cflags = subprocess.check_output([f'{pcre2_config}', '--cflags'], encoding='utf-8').strip().split() - pcre2_include_dirs = [i.replace('-I', '', 1) for i in pcre2_cflags if i.startswith('-I')] - # should we also capture other flags to pass to extra_compile_flags? - - pcre2_libs = subprocess.check_output([f'{pcre2_config}', '--libs8'], encoding='utf-8').strip().split() - pcre2_library_dirs = [l.replace('-L', '', 1) for l in pcre2_libs if l.startswith('-L')] - pcre2_libraries = [l.replace('-l', '', 1) for l in pcre2_libs if l.startswith('-l')] - - return pcre2_cflags, pcre2_include_dirs, pcre2_library_dirs, pcre2_libraries - - -def build_libcleri(pcre2_cflags): - with open('libcleri/Release/makefile', 'r') as f_in, open('libcleri/Release/makefile.extxyz', 'w') as f_out: - contents = f_in.read() - contents += """ - -libcleri.a: $(OBJS) $(USER_OBJS) -\tar rcs libcleri.a $(OBJS) $(USER_OBJS) -""" - f_out.write(contents) - env = os.environ.copy() - env['CFLAGS'] = ' '.join(pcre2_cflags) - subprocess.call(['make', '-C', 'libcleri/Release', '-f', 'makefile.extxyz', 'libcleri.a'], env=env) - -class install(setuptools__install): - def run(self): - build_libcleri(pcre2_cflags) - setuptools__install.run(self) - - -class develop(setuptools__develop): - def run(self): - build_libcleri(pcre2_cflags) - setuptools__develop.run(self) - - -class egg_info(setuptools__egg_info): - def run(self): - build_libcleri(pcre2_cflags) - setuptools__egg_info.run(self) - -# https://stackoverflow.com/questions/60284403/change-output-filename-in-setup-py-distutils-extension -class NoSuffixBuilder(setuptools__build_ext): - def get_ext_filename(self, ext_name): - filename = super().get_ext_filename(ext_name) - suffix = sysconfig.get_config_var('EXT_SUFFIX') - ext = os.path.splitext(filename)[1] - return filename.replace(suffix, "") + ext - -pcre2_cflags, pcre2_include_dirs, pcre2_library_dirs, pcre2_libraries = build_pcre2() - -_extxyz_ext = Extension('extxyz._extxyz', sources=['libextxyz/extxyz_kv_grammar.c', 'libextxyz/extxyz.c'], - include_dirs=['libcleri/inc', 'extxyz'] + pcre2_include_dirs, - library_dirs=pcre2_library_dirs, libraries=pcre2_libraries, - extra_compile_args=['-fPIC'], extra_objects=['libcleri/Release/libcleri.a']) - -build_grammar() - -setup( - name='extxyz', - version='0.1.3', - author='various', - packages=['extxyz'], - package_dir={'': 'python'}, - cmdclass={'install': install, 'develop': develop, 'egg_info': egg_info, 'build_ext': NoSuffixBuilder}, - include_package_data=True, - install_requires=['numpy>=1.13', 'pyleri>=1.3.3', 'ase>=3.17'], - ext_modules=[_extxyz_ext], - entry_points={'console_scripts': ['extxyz=extxyz.cli:main']} -) - diff --git a/subprojects/pcre2.wrap b/subprojects/pcre2.wrap new file mode 100644 index 0000000..e27ba86 --- /dev/null +++ b/subprojects/pcre2.wrap @@ -0,0 +1,14 @@ +[wrap-file] +directory = pcre2-10.44 +source_url = https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.44/pcre2-10.44.tar.gz +source_filename = pcre2-10.44.tar.gz +source_hash = 86b9cb0aa3bcb7994faa88018292bc704cdbb708e785f7c74352ff6ea7d3175b +patch_filename = pcre2_10.44-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.44-1/get_patch +patch_hash = 02c980e9f59b83f7ab2c7e8e02e2b5af11e23733e960f2e9bb531c68e2f04ca1 + +[provide] +libpcre2-8 = libpcre2_8_dep +libpcre2-16 = libpcre2_16_dep +libpcre2-32 = libpcre2_32_dep +libpcre2-posix = libpcre2_posix_dep