diff --git a/.github/workflows/debian-unstable.yml b/.github/workflows/debian-unstable.yml new file mode 100644 index 00000000..2f23f6bc --- /dev/null +++ b/.github/workflows/debian-unstable.yml @@ -0,0 +1,24 @@ +name: debian-unstable-build +on: [push, pull_request] +jobs: + build: + name: subvertpy build on Debian unstable + runs-on: ubuntu-latest + steps: + - name: Checkout subvertpy code + uses: actions/checkout@v6 + - name: Build subvertpy on Debian unstable + uses: vjik/docker-run@v1 + with: + image: debian:unstable + volumes: ${{ github.workspace }}:/subvertpy:rw + workdir: /subvertpy + command: | + apt update + apt install -y python3 libpython3-dev python3-venv \ + build-essential libsvn-dev + + python3 -m venv ~/venv + . ~/venv/bin/activate + + pip install . diff --git a/.github/workflows/macos-wheels.yml b/.github/workflows/macos-wheels.yml new file mode 100644 index 00000000..d402df26 --- /dev/null +++ b/.github/workflows/macos-wheels.yml @@ -0,0 +1,73 @@ +name: macos-wheels +on: [push, pull_request] +jobs: + macos: + name: subvertpy wheel build for Python ${{ matrix.python-version }} + on ${{ matrix.config.os }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + config: + - os: macos-15-intel + arch: x86_64 + - os: macos-15 + arch: arm64 + env: + MACOSX_DEPLOYMENT_TARGET: "11.0" + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + + steps: + - name: Checkout subvertpy code + uses: actions/checkout@v6 + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + id: python-install + with: + python-version: ${{ matrix.python-version }} + - name: Install build and pytest Python packages + run: sudo pip install build pytest + - name: Install MacPorts + run: | + curl -LO https://raw.githubusercontent.com/GiovanniBussi/macports-ci/master/macports-ci + source ./macports-ci install --sync=rsync + - name: Set macOS deployment target and install MacOSX sdk 11.3 + run: | + echo 'macosx_deployment_target 11.0' | sudo tee -a /opt/local/etc/macports/macports.conf + echo 'macosx_sdk_version 11.3' | sudo tee -a /opt/local/etc/macports/macports.conf + curl -LO https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz + tar -xzf MacOSX11.3.sdk.tar.xz + sudo mv MacOSX11.3.sdk /Library/Developer/CommandLineTools/SDKs/ + - name: Install subvertpy build dependencies + run: sudo port -N -s install subversion db48 + - name: Sets CIBW_BUILD and SDKROOT environment variable + env: + pyVer: "${{matrix.python-version}}" + arch: "${{matrix.config.arch}}" + run: | + echo "CIBW_BUILD=cp${pyVer/./}-macosx_$arch" >> $GITHUB_ENV + echo "SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX11.3.sdk" >> $GITHUB_ENV + - name: Build subvertpy wheel with cibuildwheel + run: | + pip install cibuildwheel --user + python -m cibuildwheel --output-dir wheelhouse + env: + CIBW_BUILD_VERBOSITY: 1 + - name: Test built wheel can be installed and imported + working-directory: wheelhouse + run: | + pip install $(ls -t | head -1) + python -c "from subvertpy import client, ra, repos, subr, wc" + cd ../ && mv subvertpy/tests . && rm -rf subvertpy && python -m pytest tests + - uses: actions/upload-artifact@v4 + with: + name: subvertpy-wheel-${{ matrix.python-version }}-${{ matrix.config.arch }} + path: ./wheelhouse/*.whl + - name: Publish subvertpy wheel to PyPI + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + run: | + sudo pip install twine + twine check wheelhouse/* + twine upload --skip-existing wheelhouse/* diff --git a/.github/workflows/manylinux-wheels.yml b/.github/workflows/manylinux-wheels.yml new file mode 100644 index 00000000..18b43b29 --- /dev/null +++ b/.github/workflows/manylinux-wheels.yml @@ -0,0 +1,51 @@ +name: manylinux-wheels +on: [push, pull_request] +jobs: + manylinux: + name: subvertpy manylinux ${{ matrix.python-version }} wheel build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + steps: + - name: Checkout subvertpy code + uses: actions/checkout@v6 + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + id: python-install + with: + python-version: ${{ matrix.python-version }} + - name: Sets CIBW_BUILD environment variable + env: + pyVer: "${{matrix.python-version}}" + run: | + echo "CIBW_BUILD=cp${pyVer/./}-manylinux_x86_64" >> $GITHUB_ENV + - name: Build subvertpy wheel with cibuildwheel + run: | + sudo apt install python3-pip + pip3.12 install cibuildwheel --user + python3.12 -m cibuildwheel --output-dir wheelhouse + env: + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_34 + CIBW_BUILD_VERBOSITY: 1 + CIBW_BEFORE_ALL: dnf -y install subversion-devel + - name: Test wheel can be installed and subvertpy module imported + run: | + $Python3_ROOT_DIR/bin/pip install $(ls -t | head -1) + $Python3_ROOT_DIR/bin/python -c " + from subvertpy import client, ra, repos, subr, wc" + pip3 install pytest + cd ../ && mv subvertpy/tests . && rm -rf subvertpy && python3 -m pytest tests + working-directory: wheelhouse + - uses: actions/upload-artifact@v4 + with: + name: subvertpy-wheel-${{ matrix.python-version }} + path: ./wheelhouse/*.whl + - name: Publish subvertpy wheel to PyPI + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + packages-dir: wheelhouse/ + skip-existing: true diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 0023e717..aa707446 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -6,160 +6,81 @@ jobs: build: runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.experimental }} strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8] - os: [ubuntu-latest] - svn-version: [1.14.0, 1.13.0, 1.10.6, 1.9.12] - experimental: [false] - include: - - os: macos-latest - python-version: 2.7 - experimental: false - - os: macos-latest - python-version: 3.5 - experimental: false - - os: macos-latest - python-version: 3.6 - experimental: false - - os: macos-latest - python-version: 3.7 - experimental: false - - os: macos-latest - python-version: 3.8 - experimental: false - - os: windows-latest - python-version: 3.8 - svn-version: 1.9.12 - experimental: true - - os: windows-latest - python-version: 3.8 - svn-version: 1.10.6 - experimental: true - - os: windows-latest - python-version: 3.8 - svn-version: 1.13.0 - experimental: true - - os: windows-latest - python-version: 3.8 - svn-version: 1.14.0 - experimental: true - - os: windows-latest - python-version: 3.9 - svn-version: 1.9.12 - experimental: true + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + os: [ubuntu-latest, macos-latest, windows-latest] + fail-fast: false + env: + VCPKG_INSTALL_DIR: ${{ github.workspace }}\vcpkg\installed\x64-windows-release steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies (apt) run: | sudo apt update - sudo apt install libapr1-dev libaprutil1-dev libdb5.3-dev liblz4-dev libsasl2-dev libperl-dev libserf-dev libsqlite3-dev libtool python-all-dev libneon27-gnutls-dev libutf8proc-dev + sudo apt install libsvn-dev if: "matrix.os == 'ubuntu-latest'" - name: Install dependencies (brew) run: | - brew install subversion apr-util apr - echo "$(brew --prefix)/opt/subversion/libexec" >> $GITHUB_PATH - echo "$(brew --prefix)/opt/apr-util/bin" >> $GITHUB_PATH - echo "$(brew --prefix)/opt/apr/bin" >> $GITHUB_PATH + brew install subversion if: "matrix.os == 'macos-latest'" - name: Install dependencies (Windows) - run: | - vcpkg install apr:x64-windows apr-util:x64-windows sqlite3:x64-windows zlib:x64-windows utf8proc:x64-windows openssl:x64-windows berkeleydb:x64-windows lz4:x64-windows python2:x64-windows expat:x64-windows - vcpkg integrate install - echo "APR_INCLUDE_DIR=$env:VCPKG_INSTALLATION_ROOT/packages/apr_x64-windows/include" >> $env:GITHUB_ENV - echo "APR_LINK_FLAGS=/LIBPATH:$env:VCPKG_INSTALLATION_ROOT/packages/apr_x64-windows/lib libapr-1.lib" >> $env:GITHUB_ENV - Get-ChildItem -Path "$env:VCPKG_INSTALLATION_ROOT/packages/apr_x64-windows/lib/*" -Recurse -Filter "*.dll" | Copy-Item -Destination "$env:GITHUB_WORKSPACE/subvertpy" - echo "APU_INCLUDE_DIR=$env:VCPKG_INSTALLATION_ROOT/packages/apr-util_x64-windows/include" >> $env:GITHUB_ENV - Get-ChildItem -Path "$env:VCPKG_INSTALLATION_ROOT/packages/apr-util_x64-windows/lib/*" -Recurse -Filter "*.dll" | Copy-Item -Destination "$env:GITHUB_WORKSPACE/subvertpy" - echo "APU_LINK_FLAGS=/LIBPATH:$env:VCPKG_INSTALLATION_ROOT/packages/apr-util_x64-windows/lib libaprutil-1.lib" >> $env:GITHUB_ENV - if: "matrix.os == 'windows-latest'" - - name: Locate & Exec vcvarsall.bat - run: | - $VCVarsAll = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -find "VC\Auxiliary\Build\vcvarsall.bat" - if (-not $VCVarsAll) { - Write-Error "vcvarsall.bat not found" -Category NotInstalled - exit 1 - } - $Token = "#####ENV#####" - $enc = [Console]::OutputEncoding - [Console]::OutputEncoding = [Text.Encoding]::Unicode - $VCVarsAllResult = cmd /U /C "`"$VCVarsAll`" x64 && echo $TOKEN&& set" - [Console]::OutputEncoding = $enc - $TokenIndex = $VCVarsAllResult.IndexOf($Token) - if ($TokenIndex -eq -1) { - Write-Error "vcvarsall failed." - exit 1 - } - $VCVarsAllResult | Select-Object -Skip ($TokenIndex + 1) | ForEach-Object { - $k, $v = $_ -split "=", 2 - Write-Output "$k=$v" >> $GITHUB_ENV - } - shell: pwsh - if: "matrix.os == 'windows-latest'" - - name: Install serf (Windows) - run: | - curl -L https://downloads.apache.org/serf/serf-1.3.9.zip -o serf.zip - unzip -q serf.zip - cd serf-1.3.9 - curl -L http://prdownloads.sourceforge.net/scons/scons-local-2.3.0.zip -o scons-local.zip - unzip -q scons-local.zip - python2 ./scons.py --debug=stacktrace + uses: johnwason/vcpkg-action@v7 + id: vcpkg + with: + pkgs: subversion + triplet: x64-windows-release + extra-args: --allow-unsupported --recurse --keep-going + token: ${{ github.token }} if: "matrix.os == 'windows-latest'" - continue-on-error: true - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.0.2 - if: "matrix.os == 'windows-latest'" - - name: Install Subversion (Windows) - run: | - curl -L https://downloads.apache.org/subversion/subversion-${{ matrix.svn-version }}.zip -o subversion.zip - unzip -q subversion.zip - cd subversion-${{ matrix.svn-version }} - cp $env:VCPKG_INSTALLATION_ROOT/packages/expat_x64-windows/include/* $env:VCPKG_INSTALLATION_ROOT/packages/apr-util_x64-windows/include - cp $env:VCPKG_INSTALLATION_ROOT/packages/expat_x64-windows/lib/* $env:VCPKG_INSTALLATION_ROOT/packages/apr-util_x64-windows/lib - cp $env:VCPKG_INSTALLATION_ROOT/packages/expat_x64-windows/bin/* $env:VCPKG_INSTALLATION_ROOT/packages/apr-util_x64-windows/bin - python2 gen-make.py --debug --vsnet-version=2019 --with-apr=$env:VCPKG_INSTALLATION_ROOT/packages/apr_x64-windows --with-apr-util=$env:VCPKG_INSTALLATION_ROOT/packages/apr-util_x64-windows --with-zlib=$env:VCPKG_INSTALLATION_ROOT/packages/zlib_x64-windows --with-sqlite=$env:VCPKG_INSTALLATION_ROOT/packages/sqlite3_x64-windows - msbuild subversion_vcnet.sln /t:__MORE__ /p:Configuration=Release - Copy-Item -Recurse -Path $env:GITHUB_WORKSPACE/subversion-${{ matrix.svn-version }}/subversion/include -Destination $env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/include/subversion-1 - Get-ChildItem -Path "$env:GITHUB_WORKSPACE/subversion-${{ matrix.svn-version }}/Release/subversion/libsvn_*/*" -Recurse | Copy-Item -Destination "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/lib" - Get-ChildItem -Path "$env:GITHUB_WORKSPACE/subversion-${{ matrix.svn-version }}/Release/subversion/libsvn_*/*" -Recurse -Filter "*.dll" | Copy-Item -Destination "$env:GITHUB_WORKSPACE/subvertpy" - echo "SVN_HEADER_PATH=$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/include/subversion-1" >> $env:GITHUB_ENV - echo "SVN_LIBRARY_PATH=$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/lib" >> $env:GITHUB_ENV + uses: microsoft/setup-msbuild@v2 if: "matrix.os == 'windows-latest'" - - name: Install Subversion (Linux) - run: | - curl -L https://downloads.apache.org/subversion/subversion-${{ matrix.svn-version }}.tar.gz -o subversion.tar.gz - tar xfz subversion.tar.gz - cd subversion-${{ matrix.svn-version }} - ./configure - make - sudo make install - sudo ldconfig -v - cd .. - if: "matrix.os == 'ubuntu-latest'" - name: Install other dependencies run: | python -m pip install --upgrade pip - pip install -U pip coverage codecov flake8 fastimport + pip install -U pip flake8 fastimport setuptools pytest pytest-cov - name: Style checks run: | - python -m flake8 + python -m flake8 subvertpy - name: Build (Linux) run: | - python setup.py build_ext -i + pip install -e . if: "matrix.os == 'ubuntu-latest'" - - name: Build (Mac OS X) + - name: Build (macOS) run: | - python setup.py build_ext -i + export PATH="$(brew --prefix)/opt/subversion/libexec:$PATH" + export PATH="$(brew --prefix)/opt/apr-util/bin:$PATH" + export PATH="$(brew --prefix)/opt/apr/bin:$PATH" + pip install -e . if: "matrix.os == 'macos-latest'" - name: Build (Windows) + env: + APR_INCLUDE_DIR: ${{ env.VCPKG_INSTALL_DIR }}/include + APU_INCLUDE_DIR: ${{ env.VCPKG_INSTALL_DIR }}/include + SVN_HEADER_PATH: ${{ env.VCPKG_INSTALL_DIR }}/include/subversion-1 + SVN_LIBRARY_PATH: ${{ env.VCPKG_INSTALL_DIR }}/lib + LIB: ${{ env.VCPKG_INSTALL_DIR }}/lib + run: | + pip install -e . + if: "matrix.os == 'windows-latest'" + - name: Coverage test suite run (Linux) + run: | + pytest -sv --cov=subvertpy subvertpy + if: "matrix.os == 'ubuntu-latest'" + - name: Coverage test suite run run: | - python setup.py build_ext -i + pytest -sv --cov=subvertpy subvertpy + if: "matrix.os == 'macos-latest'" - name: Coverage test suite run + env: + SUBVERTPY_DLL_PATH: ${{ github.workspace }}/vcpkg/installed/x64-windows-release/bin run: | - python -m coverage run -p -m unittest -v subvertpy.tests.test_suite + # using coverage on windows make some tests fail so only run tests + pytest -sv subvertpy + if: "matrix.os == 'windows-latest'" diff --git a/.github/workflows/windows-wheels.yml b/.github/workflows/windows-wheels.yml new file mode 100644 index 00000000..6162fd1a --- /dev/null +++ b/.github/workflows/windows-wheels.yml @@ -0,0 +1,85 @@ +name: windows-wheels +on: [push, pull_request] +jobs: + windows: + name: subvertpy Python ${{ matrix.python-version }} wheel build on windows + runs-on: windows-latest + env: + VCPKG_INSTALL_DIR: ${{ github.workspace }}\vcpkg\installed\x64-windows-release + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + defaults: + run: + shell: cmd + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + steps: + - name: Checkout subvertpy code + uses: actions/checkout@v6 + - name: Install MSYS2 shell + uses: msys2/setup-msys2@v2 + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + id: python-install + with: + python-version: ${{ matrix.python-version }} + - name: Install build, cibuildwheel and pytest Python packages + run: | + set PATH=%Python3_ROOT_DIR%\Scripts;%PATH% + pip install build cibuildwheel pytest + - name: Install build dependencies with vcpkg + uses: johnwason/vcpkg-action@v7 + id: vcpkg + continue-on-error: true + with: + pkgs: subversion + triplet: x64-windows-release + extra-args: --allow-unsupported --recurse --keep-going + token: ${{ github.token }} + - name: Sets CIBW_BUILD environment variable + env: + pyVer: "${{matrix.python-version}}" + run: | + echo "CIBW_BUILD=cp${pyVer/./}-win_amd64" >> $GITHUB_ENV + shell: msys2 {0} + - name: Build subvertpy wheel with cibuildwheel + env: + APR_INCLUDE_DIR: ${{ env.VCPKG_INSTALL_DIR }}/include + APU_INCLUDE_DIR: ${{ env.VCPKG_INSTALL_DIR }}/include + SVN_HEADER_PATH: ${{ env.VCPKG_INSTALL_DIR }}/include/subversion-1 + SVN_LIBRARY_PATH: ${{ env.VCPKG_INSTALL_DIR }}/lib + LIB: ${{ env.VCPKG_INSTALL_DIR }}/lib + CIBW_BUILD_VERBOSITY: 1 + CIBW_BEFORE_BUILD_WINDOWS: pip install delvewheel + CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: > + delvewheel repair -v -w {dest_dir} --analyze-existing + --add-path ${{ env.VCPKG_INSTALL_DIR }}\bin + {wheel} + run: python -m cibuildwheel --output-dir wheelhouse + - name: Cleanup subvertpy repo to run tests with installed wheel + run: mv subvertpy/tests . && rm -rf subvertpy + shell: msys2 {0} + - name: Test built wheel can be installed and imported + working-directory: wheelhouse + run: | + set PATH=%Python3_ROOT_DIR%\Scripts:%PATH% + set PYTHONIOENCODING=utf-8 + for /f "tokens=*" %%g in ('dir /b *.whl') do (set wheel=%%g) + pip install %wheel% + python -c "from subvertpy import client, ra, repos, subr, wc" + cd .. && python -m pytest tests + - uses: actions/upload-artifact@v4 + with: + name: subvertpy-wheel-${{ matrix.python-version }} + path: ./wheelhouse/*.whl + - name: Publish subvertpy wheel to PyPI + working-directory: wheelhouse + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + run: | + set PATH=%Python3_ROOT_DIR%\Scripts;%PATH% + for /f "tokens=*" %%g in ('dir /b *.whl') do (set wheel=%%g) + pip install twine + twine check %wheel% + twine upload --skip-existing %wheel% diff --git a/setup.py b/setup.py index eb470e00..87198bcc 100755 --- a/setup.py +++ b/setup.py @@ -41,15 +41,19 @@ def apr_build_data(): try: includedir = os.environ['APR_INCLUDE_DIR'] except KeyError: - includedir = apr_config(["--includedir"]) + if os.name != "nt": + includedir = apr_config(["--includedir"]) if not os.path.isdir(includedir): raise Exception("APR development headers not found") try: extra_link_flags = split_shell_results( os.environ['APR_LINK_FLAGS']) except KeyError: - extra_link_flags = split_shell_results( - apr_config(["--link-ld", "--libs"])) + if os.name != "nt": + extra_link_flags = split_shell_results( + apr_config(["--link-ld", "--libs"])) + else: + extra_link_flags = [] return (includedir, extra_link_flags) @@ -58,15 +62,19 @@ def apu_build_data(): try: includedir = os.environ['APU_INCLUDE_DIR'] except KeyError: - includedir = apu_config(["--includedir"]) + if os.name != "nt": + includedir = apu_config(["--includedir"]) if not os.path.isdir(includedir): raise Exception("APR util development headers not found") try: extra_link_flags = split_shell_results( os.environ['APU_LINK_FLAGS']) except KeyError: - extra_link_flags = split_shell_results( - apu_config(["--link-ld", "--libs"])) + if os.name != "nt": + extra_link_flags = split_shell_results( + apu_config(["--link-ld", "--libs"])) + else: + extra_link_flags = [] return (includedir, extra_link_flags) @@ -77,7 +85,7 @@ def svn_build_data(): [os.getenv("SVN_LIBRARY_PATH")], []) svn_prefix = os.getenv("SVN_PREFIX") if svn_prefix is None: - basedirs = ["/usr/local", "/usr"] + basedirs = ["/usr/local", "/usr", "/opt/homebrew", "/opt/local"] for basedir in basedirs: includedir = os.path.join(basedir, "include/subversion-1") if os.path.isdir(includedir): @@ -85,7 +93,8 @@ def svn_build_data(): break if svn_prefix is not None: return ([os.path.join(svn_prefix, "include/subversion-1")], - [os.path.join(svn_prefix, "lib")], []) + [os.path.join(svn_prefix, "lib"), + os.path.join(svn_prefix, "lib", "db48")], []) raise Exception("Subversion development files not found. " "Please set SVN_PREFIX or (SVN_LIBRARY_PATH and " "SVN_HEADER_PATH) environment variable. ") @@ -145,15 +154,16 @@ class SvnExtension(Extension): def __init__(self, name, *args, **kwargs): if sys.platform == 'win32': libraries = kwargs.get('libraries', []) + libs = [("lib" + lib) for lib in libraries] modified = True while modified: modified = False for lib in libraries: for extra in deep_deps.get(lib, []): - if extra not in libraries: + if extra not in libs: modified = True - libraries.append(extra) - kwargs['libraries'] = libraries + libs.append(extra) + kwargs["libraries"] = libs kwargs["include_dirs"] = ([apr_includedir, apu_includedir] + svn_includedirs + ["subvertpy"]) kwargs["library_dirs"] = svn_libdirs @@ -174,6 +184,10 @@ def __init__(self, name, *args, **kwargs): ('DARWIN', None), ('SVN_KEYCHAIN_PROVIDER_AVAILABLE', '1')) ) + if sys.platform in ('darwin', 'linux'): + kwargs["extra_compile_args"] = [ + "-std=c99" # for GCC >=15 as it is using c23 by default + ] Extension.__init__(self, name, *args, **kwargs) @@ -183,7 +197,9 @@ def source_path(filename): # Urgh. It's a pain having to maintain these manually. But what else can we do? subr_deep_deps = [ - "svn_subr-1", + "libsvn_subr-1", + "libapr-1", + "libaprutil-1", "sqlite3", "zlib", "advapi32", @@ -198,19 +214,23 @@ def source_path(filename): repos_deep_deps = [ - "svn_repos-1", - "svn_fs-1", + "libsvn_repos-1", + "libsvn_fs-1", "libsvn_fs_util-1", "libsvn_fs_fs-1", "libsvn_fs_x-1", - "svn_delta-1", + "libsvn_delta-1", + "libapr-1", + "libaprutil-1", ] ra_deep_deps = [ "libsvn_ra_svn-1", "libsvn_ra_local-1", - "svn_repos-1", + "libsvn_repos-1", + "libapr-1", + "libaprutil-1", ] diff --git a/subvertpy/__init__.py b/subvertpy/__init__.py index 7d3bb759..496eefe0 100644 --- a/subvertpy/__init__.py +++ b/subvertpy/__init__.py @@ -16,6 +16,9 @@ """Python bindings for Subversion.""" +import os +import platform + __author__ = "Jelmer Vernooij " __version__ = (0, 11, 0) @@ -123,6 +126,17 @@ def _check_mtime(m): return True +if platform.system() == "Windows": + dll_dirs = [ + dll_dir for dll_dir in os.environ.get( + "SUBVERTPY_DLL_PATH", "").split(';') + if dll_dir + ] + for path in dll_dirs: + path = os.path.abspath(path) + if os.path.exists(path): + os.add_dll_directory(path) + try: from subvertpy import client, _ra, repos, wc for x in client, _ra, repos, wc: diff --git a/subvertpy/_ra.c b/subvertpy/_ra.c index 80983ab8..04e1ab99 100644 --- a/subvertpy/_ra.c +++ b/subvertpy/_ra.c @@ -279,7 +279,11 @@ static PyTypeObject Reporter_Type = { /* Methods to implement standard operations */ reporter_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -2077,7 +2081,11 @@ static PyTypeObject RemoteAccess_Type = { /* Methods to implement standard operations */ ra_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -2405,7 +2413,11 @@ static PyTypeObject Auth_Type = { /* Methods to implement standard operations */ auth_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ diff --git a/subvertpy/client.c b/subvertpy/client.c index f0604822..d2253cf4 100644 --- a/subvertpy/client.c +++ b/subvertpy/client.c @@ -1626,7 +1626,11 @@ PyTypeObject Config_Type = { /* Methods to implement standard operations */ (destructor)config_dealloc, /* destructor tp_dealloc; */ - NULL, /* printfunc tp_print; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else + NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -1748,7 +1752,11 @@ PyTypeObject Info_Type = { /* Methods to implement standard operations */ info_dealloc, /* destructor tp_dealloc; */ - NULL, /* printfunc tp_print; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else + NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -1835,7 +1843,11 @@ PyTypeObject WCInfo_Type = { /* Methods to implement standard operations */ wcinfo_dealloc, /* destructor tp_dealloc; */ - NULL, /* printfunc tp_print; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else + NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -1898,7 +1910,11 @@ PyTypeObject Client_Type = { /* Methods to implement standard operations */ client_dealloc, /* destructor tp_dealloc; */ - NULL, /* printfunc tp_print; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else + NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ diff --git a/subvertpy/editor.c b/subvertpy/editor.c index e1b97b17..de1f4ac1 100644 --- a/subvertpy/editor.c +++ b/subvertpy/editor.c @@ -108,7 +108,7 @@ static PyObject *txdelta_call(PyObject *self, PyObject *args, PyObject *kwargs) Py_RETURN_NONE; } - if (!PyArg_ParseTuple(py_window, SVN_FILESIZE_T_PYFMT "kkiOO", + if (!PyArg_ParseTuple(py_window, SVN_FILESIZE_T_PYFMT "nniOO", &window.sview_offset, &window.sview_len, &window.tview_len, &window.src_ops, &py_ops, &py_new_data)) return NULL; @@ -136,7 +136,7 @@ static PyObject *txdelta_call(PyObject *self, PyObject *args, PyObject *kwargs) for (i = 0; i < window.num_ops; i++) { PyObject *windowitem = PyList_GetItem(py_ops, i); - if (!PyArg_ParseTuple(windowitem, "ikk", &ops[i].action_code, + if (!PyArg_ParseTuple(windowitem, "inn", &ops[i].action_code, &ops[i].offset, &ops[i].length)) { free(ops); return NULL; @@ -172,7 +172,11 @@ PyTypeObject TxDeltaWindowHandler_Type = { /* Methods to implement standard operations */ py_txdelta_window_handler_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -310,7 +314,11 @@ PyTypeObject FileEditor_Type = { /* Methods to implement standard operations */ py_editor_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -737,7 +745,11 @@ PyTypeObject DirectoryEditor_Type = { /* Methods to implement standard operations */ py_editor_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -939,7 +951,11 @@ PyTypeObject Editor_Type = { /* Methods to implement standard operations */ py_editor_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ diff --git a/subvertpy/marshall.py b/subvertpy/marshall.py index 6e02b0ce..cdd843ea 100644 --- a/subvertpy/marshall.py +++ b/subvertpy/marshall.py @@ -30,7 +30,7 @@ def __repr__(self): return self.txt def __eq__(self, other): - return (type(self) == type(other) and self.txt == other.txt) + return (isinstance(other, literal) and self.txt == other.txt) # 1. Syntactic structure # ---------------------- diff --git a/subvertpy/repos.c b/subvertpy/repos.c index 1a41a106..daf78eaf 100644 --- a/subvertpy/repos.c +++ b/subvertpy/repos.c @@ -275,7 +275,11 @@ PyTypeObject FileSystem_Type = { /* Methods to implement standard operations */ fs_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -542,7 +546,11 @@ PyTypeObject Repository_Type = { /* Methods to implement standard operations */ repos_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -839,7 +847,11 @@ PyTypeObject FileSystemRoot_Type = { /* Methods to implement standard operations */ fs_root_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ diff --git a/subvertpy/tests/test_client.py b/subvertpy/tests/test_client.py index 6f2f1273..3a6e3de2 100644 --- a/subvertpy/tests/test_client.py +++ b/subvertpy/tests/test_client.py @@ -248,3 +248,22 @@ def test_info_nonexistant(self): self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.assertRaises(SubversionException, self.client.info, "dc/missing") + + def test_set_get_prop_with_path(self): + self.build_tree({"dc/foo": b"bla"}) + self.client_add("dc/foo") + self.client_set_prop("dc/foo", "svn:eol-style", "native") + self.client_commit("dc", message="Commit") + self.assertEqual( + self.client_get_prop("dc/foo", "svn:eol-style", "HEAD"), b"native") + + def test_set_get_prop_with_url(self): + self.build_tree({"dc/foo": b"bla"}) + self.client_add("dc/foo") + self.client_set_prop("dc/foo", "svn:eol-style", "native") + self.client_commit("dc", message="Commit") + self.assertEqual( + self.client_get_prop( + self.repos_url + "/foo", "svn:eol-style", "HEAD"), + b"native" + ) diff --git a/subvertpy/tests/test_subr.py b/subvertpy/tests/test_subr.py index c471e65f..a2e8bd3e 100644 --- a/subvertpy/tests/test_subr.py +++ b/subvertpy/tests/test_subr.py @@ -53,12 +53,16 @@ def test_canonicalize(self): class AbspathTests(TestCase): def test_abspath(self): + path = '/foo/bar' if os.name != 'nt' else 'C:/foo/bar' self.assertEqual( - '/foo/bar', - abspath('/foo//bar')) + path, + abspath(path)) + # os.getcwd() returns '/foo/bar' on linux/macos + # while it returns 'c:\\foo\\bar' on windows self.assertEqual( - os.path.join(os.getcwd(), 'bar'), - abspath('bar')) + os.path.join(os.getcwd(), 'bar').replace("\\", "/").lower(), + abspath('bar').lower()) self.assertEqual( - os.path.join(os.getcwd(), 'bar', 'foo'), - abspath('bar/foo')) + os.path.join( + os.getcwd(), 'bar', 'foo').replace("\\", "/").lower(), + abspath('bar/foo').lower()) diff --git a/subvertpy/util.c b/subvertpy/util.c index d8b06f69..64b7b871 100644 --- a/subvertpy/util.c +++ b/subvertpy/util.c @@ -100,7 +100,9 @@ const char *py_object_to_svn_abspath(PyObject *obj, apr_pool_t *pool) if (ret == NULL) { return NULL; } - if (svn_dirent_is_absolute(ret)) { + if (svn_path_is_url(ret)) { + return svn_uri_canonicalize(ret, pool); + } else if (svn_dirent_is_absolute(ret)) { return svn_dirent_canonicalize(ret, pool); } else { const char *absolute; diff --git a/subvertpy/wc.c b/subvertpy/wc.c index 740f13d3..bd920d26 100644 --- a/subvertpy/wc.c +++ b/subvertpy/wc.c @@ -866,7 +866,11 @@ PyTypeObject CommittedQueue_Type = { /* Methods to implement standard operations */ committed_queue_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -1308,7 +1312,11 @@ static PyTypeObject Status3_Type = { /* Methods to implement standard operations */ status_dealloc, /* destructor tp_dealloc; */ - NULL, /* printfunc tp_print; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else + NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ @@ -1793,7 +1801,11 @@ static PyTypeObject Context_Type = { /* Methods to implement standard operations */ context_dealloc, /* destructor tp_dealloc; */ +#if PY_MAJOR_VERSION >= 3 + 0, /* Py_ssize_t tp_vectorcall_offset; */ +#else NULL, /* printfunc tp_print; */ +#endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */