diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23c7add3..a1b06343 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,123 +7,73 @@ on: jobs: - Linux: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Get tag - id: tag - run: | - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - - name: Building release - run: | - make linux_release - - name: Upload distributions artifacts - uses: actions/upload-artifact@v2 - with: - name: pendulum-dist - path: dist/wheelhouse - - MacOS: - runs-on: macos-latest + build: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9, "3.10"] + os: [ ubuntu, windows, macos ] steps: - - uses: actions/checkout@v2 - - name: Get tag - id: tag - run: | - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install and set up Poetry - run: | - curl -fsS https://install.python-poetry.org | python - --preview -y - - name: Build distributions - run: | - source $HOME/.poetry/env - poetry build -vvv - - name: Upload distribution artifacts - uses: actions/upload-artifact@v2 - with: - name: pendulum-dist - path: dist - - Windows: - runs-on: windows-latest - strategy: - matrix: - python-version: [3.7, 3.8, 3.9, "3.10"] + - uses: actions/checkout@v3 - steps: - - uses: actions/checkout@v2 - - name: Get tag - id: tag - shell: bash - run: | - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install and setup Poetry - run: | - (Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python - --preview -y - - name: Build distributions - run: | - $env:Path += ";$env:Userprofile\.poetry\bin" - poetry build -vvv - - name: Upload distribution artifact - uses: actions/upload-artifact@v2 - with: - name: pendulum-dist - path: dist + - name: Build wheels + uses: pypa/cibuildwheel@v2.10.1 + env: + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.7" + with: + package-dir: . + output-dir: dist + + - uses: actions/upload-artifact@v3 + with: + name: dist + path: ./dist/* Release: - needs: [Linux, MacOS, Windows] + needs: [ build-wheel ] runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - - name: Get tag - id: tag - run: | - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} - - name: Download distribution artifact - uses: actions/download-artifact@master + + - name: Download artifacts + uses: actions/download-artifact@v3 with: - name: pendulum-dist + name: dist path: dist - - name: Install and set up Poetry + + - name: Install Poetry run: | - curl -fsS https://install.python-poetry.org | python - --preview -y - - name: Set up cache - uses: actions/cache@v2 - with: - path: .venv - key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} + curl -fsS https://install.python-poetry.org | python - -y + + - name: Update PATH + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Build sdist + run: poetry build --format sdist + - name: Check distributions run: | ls -la dist + + - name: Check Version + id: check-version + run: | + [[ "${GITHUB_REF#refs/tags/}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \ + || echo ::set-output name=prerelease::true + + - name: Create Release + uses: ncipollo/release-action@v1 + with: + artifacts: "dist/*" + token: ${{ secrets.GITHUB_TOKEN }} + draft: false + prerelease: steps.check-version.outputs.prerelease == 'true' + - name: Publish to PyPI env: POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} run: | - source $HOME/.poetry/env poetry publish - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} - with: - tag_name: ${{ steps.tag.outputs.tag }} - release_name: ${{ steps.tag.outputs.tag }} - draft: false - prerelease: false diff --git a/build.py b/build.py index ae98d976..95e63e10 100644 --- a/build.py +++ b/build.py @@ -1,84 +1,32 @@ -import os -import shutil -import sys +import subprocess -from distutils.command.build_ext import build_ext -from distutils.core import Distribution -from distutils.core import Extension -from distutils.errors import CCompilerError -from distutils.errors import DistutilsExecError -from distutils.errors import DistutilsPlatformError +from pathlib import Path -# C Extensions -with_extensions = os.getenv("PENDULUM_EXTENSIONS", None) +def meson(*args): + subprocess.call(["meson"] + list(args)) -if with_extensions == "1" or with_extensions is None: - with_extensions = True -if with_extensions == "0" or hasattr(sys, "pypy_version_info"): - with_extensions = False +def _build(): + build_dir = Path(__file__).parent.joinpath("build") + build_dir.mkdir(parents=True, exist_ok=True) -extensions = [] -if with_extensions: - extensions = [ - Extension("pendulum._extensions._helpers", ["pendulum/_extensions/_helpers.c"]), - Extension("pendulum.parsing._iso8601", ["pendulum/parsing/_iso8601.c"]), - ] - - -class BuildFailed(Exception): - - pass - - -class ExtBuilder(build_ext): - # This class allows C extension building to fail. - - built_extensions = [] - - def run(self): - try: - build_ext.run(self) - except (DistutilsPlatformError, FileNotFoundError): - print( - " Unable to build the C extensions, " - "Pendulum will use the pure python code instead." - ) - - def build_extension(self, ext): - try: - build_ext.build_extension(self, ext) - except (CCompilerError, DistutilsExecError, DistutilsPlatformError, ValueError): - print( - f' Unable to build the "{ext.name}" C extension, ' - "Pendulum will use the pure python version of the extension." - ) + meson("setup", build_dir.as_posix()) + meson("compile", "-C", build_dir.as_posix()) + meson("install", "-C", build_dir.as_posix()) def build(setup_kwargs): """ This function is mandatory in order to build the extensions. """ - distribution = Distribution({"name": "pendulum", "ext_modules": extensions}) - distribution.package_dir = "pendulum" - - cmd = ExtBuilder(distribution) - cmd.ensure_finalized() - cmd.run() - - # Copy built extensions back to the project - for output in cmd.get_outputs(): - relative_extension = os.path.relpath(output, cmd.build_lib) - if not os.path.exists(output): - continue - - shutil.copyfile(output, relative_extension) - mode = os.stat(relative_extension).st_mode - mode |= (mode & 0o444) >> 2 - os.chmod(relative_extension, mode) - - return setup_kwargs + try: + _build() + except Exception: + print( + " Unable to build C extensions, " + "Pendulum will use the pure python version of the extensions." + ) if __name__ == "__main__": diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..666c2810 --- /dev/null +++ b/meson.build @@ -0,0 +1,20 @@ +project('pendulum C extensions', 'c') + +py_mod = import('python') +py = py_mod.find_installation() +py_dep = py.dependency() + +extensions = [ + ['_helpers', 'pendulum/_extensions/_helpers.c', meson.source_root() / 'pendulum/_extensions/'], + ['_iso8601', 'pendulum/parsing/_iso8601.c', meson.source_root() / 'pendulum/parsing/'], +] + +foreach extension : extensions + py.extension_module( + extension[0], + extension[1], + dependencies : py_dep, + install : true, + install_dir: extension[2] + ) +endforeach diff --git a/pendulum/parsing/_iso8601.c b/pendulum/parsing/_iso8601.c index b8c3e091..1322423a 100644 --- a/pendulum/parsing/_iso8601.c +++ b/pendulum/parsing/_iso8601.c @@ -522,7 +522,6 @@ Parsed* _parse_iso8601_datetime(char *str, Parsed *parsed) { int leap = 0; int separators = 0; int time = 0; - int has_hour = 0; int i; int j; @@ -773,7 +772,6 @@ Parsed* _parse_iso8601_datetime(char *str, Parsed *parsed) { } parsed->hour = time; - has_hour = 1; break; case 4: // Hours and minutes @@ -786,7 +784,6 @@ Parsed* _parse_iso8601_datetime(char *str, Parsed *parsed) { parsed->hour = time / 100; parsed->minute = time % 100; - has_hour = 1; break; case 6: // Hours, minutes and seconds @@ -800,7 +797,6 @@ Parsed* _parse_iso8601_datetime(char *str, Parsed *parsed) { parsed->hour = time / 10000; parsed->minute = time / 100 % 100; parsed->second = time % 100; - has_hour = 1; break; default: // Any other case is wrong @@ -942,7 +938,6 @@ Parsed* _parse_iso8601_duration(char *str, Parsed *parsed) { int fraction = 0; int has_ymd = 0; int has_week = 0; - int has_year = 0; int has_month = 0; int has_day = 0; int has_hour = 0; @@ -979,7 +974,6 @@ Parsed* _parse_iso8601_duration(char *str, Parsed *parsed) { fraction = 0; in_fraction = 0; has_ymd = 1; - has_year = 1; break; case 'M': diff --git a/poetry.lock b/poetry.lock index 2512d734..03a29754 100644 --- a/poetry.lock +++ b/poetry.lock @@ -283,6 +283,19 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "meson" +version = "0.63.2" +description = "A high performance build system" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +ninja = ["ninja (>=1.8.2)"] +progress = ["tqdm"] +typing = ["mypy", "typing-extensions"] + [[package]] name = "mkdocs" version = "1.3.0" @@ -314,6 +327,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "ninja" +version = "1.10.2.3" +description = "Ninja is a small build system with a focus on speed" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["codecov (>=2.0.5)", "coverage (>=4.2)", "flake8 (>=3.0.4)", "pytest (>=4.5.0)", "pytest-cov (>=2.7.1)", "pytest-runner (>=5.1)", "pytest-virtualenv (>=1.7.0)", "virtualenv (>=15.0.3)"] + [[package]] name = "nodeenv" version = "1.7.0" @@ -664,7 +688,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "98f2c2d2d4a4282d9c06e627d748d975a9ea7d39b29ab7fbcd0a0eaa23bb0c70" +content-hash = "d6cae20188419d1a859377f888c81aa3a057dd11104671bc2cc9831a30c1a9c1" [metadata.files] atomicwrites = [ @@ -873,6 +897,10 @@ mergedeep = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, ] +meson = [ + {file = "meson-0.63.2-py3-none-any.whl", hash = "sha256:64a83ef257b2962b52c8b07ad9ec536c2de1b72fd9f14bcd9c21fe45730edd46"}, + {file = "meson-0.63.2.tar.gz", hash = "sha256:16222f17ef76be0542c91c07994f9676ae879f46fc21c0c786a21ef2cb518bbf"}, +] mkdocs = [ {file = "mkdocs-1.3.0-py3-none-any.whl", hash = "sha256:26bd2b03d739ac57a3e6eed0b7bcc86168703b719c27b99ad6ca91dc439aacde"}, {file = "mkdocs-1.3.0.tar.gz", hash = "sha256:b504405b04da38795fec9b2e5e28f6aa3a73bb0960cb6d5d27ead28952bd35ea"}, @@ -881,6 +909,22 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +ninja = [ + {file = "ninja-1.10.2.3-py2.py3-none-macosx_10_9_universal2.macosx_10_9_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:d5e0275d28997a750a4f445c00bdd357b35cc334c13cdff13edf30e544704fbd"}, + {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ea785bf6a15727040835256577239fa3cf5da0d60e618c307aa5efc31a1f0ce"}, + {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29570a18d697fc84d361e7e6330f0021f34603ae0fcb0ef67ae781e9814aae8d"}, + {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a1d84d4c7df5881bfd86c25cce4cf7af44ba2b8b255c57bc1c434ec30a2dfc"}, + {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9ca8dbece144366d5f575ffc657af03eb11c58251268405bc8519d11cf42f113"}, + {file = "ninja-1.10.2.3-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:279836285975e3519392c93c26e75755e8a8a7fafec9f4ecbb0293119ee0f9c6"}, + {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:cc8b31b5509a2129e4d12a35fc21238c157038022560aaf22e49ef0a77039086"}, + {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:688167841b088b6802e006f911d911ffa925e078c73e8ef2f88286107d3204f8"}, + {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:840a0b042d43a8552c4004966e18271ec726e5996578f28345d9ce78e225b67e"}, + {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:84be6f9ec49f635dc40d4b871319a49fa49b8d55f1d9eae7cd50d8e57ddf7a85"}, + {file = "ninja-1.10.2.3-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:6bd76a025f26b9ae507cf8b2b01bb25bb0031df54ed685d85fc559c411c86cf4"}, + {file = "ninja-1.10.2.3-py2.py3-none-win32.whl", hash = "sha256:740d61fefb4ca13573704ee8fe89b973d40b8dc2a51aaa4e9e68367233743bb6"}, + {file = "ninja-1.10.2.3-py2.py3-none-win_amd64.whl", hash = "sha256:0560eea57199e41e86ac2c1af0108b63ae77c3ca4d05a9425a750e908135935a"}, + {file = "ninja-1.10.2.3.tar.gz", hash = "sha256:e1b86ad50d4e681a7dbdff05fc23bb52cb773edb90bc428efba33fa027738408"}, +] nodeenv = [ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, diff --git a/pyproject.toml b/pyproject.toml index 9ed564d9..53ec9906 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,9 +12,10 @@ keywords = ['datetime', 'date', 'time'] packages = [ { include = "pendulum" }, - #{include = "tests", format = "sdist"}, + { include = "tests", format = "sdist" }, ] include = [ + { path = "meson.build", format = "sdist" }, { path = "pendulum/py.typed" }, # C extensions must be included in the wheel distributions { path = "pendulum/_extensions/*.so", format = "wheel" }, @@ -56,6 +57,13 @@ babel = "^2.10.3" cleo = "^1.0.0a5" tox = "^3.25.1" +[tool.poetry.group.build] +optional = true + +[tool.poetry.group.build.dependencies] +meson = "^0.63.2" +ninja = "^1.10.2.3" + [tool.poetry.build] generate-setup-file = false script = "build.py" @@ -171,5 +179,5 @@ omit = [ ] [build-system] -requires = ["poetry-core>=1.1.0a6"] +requires = ["poetry-core>=1.1.0a6", "meson", "ninja"] build-backend = "poetry.core.masonry.api"