From 676393ca0db01ffd99969bf044e4772dbd416d5d Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Thu, 12 Mar 2026 14:17:13 -0400 Subject: [PATCH 1/9] feat!: migrate to setuptools-scm and automated releases BREAKING CHANGE: Version management system changed from versioneer to setuptools-scm. This enables automated releases via release-please and eliminates manual version updates. Changes: - Remove versioneer artifacts (versioneer.py, _version.py, .gitattributes) - Add modern pyproject.toml configuration with setuptools-scm - Eliminate src/filepattern/cpp/version.h (not needed - CMake now reads from VERSION file) - Add automated version sync script (scripts/update_versions.py) - Add release-please workflow for automated releases - Update all CI/CD workflows to sync versions before building - Add CONTRIBUTING.md with conventional commit guidelines - Add MIGRATION_TO_SETUPTOOLS_SCM.md with detailed migration guide - Update README.md with new release process Version file reduction: - Before: 3 files to update manually (pom.xml, version.h, git tags) - After: 2 files, auto-synced (VERSION, pom.xml) from git tags New workflow: 1. Use conventional commits (feat:, fix:, etc.) 2. Push to master 3. release-please creates Release PR with version bump 4. Merge Release PR -> automated release and publishing Co-Authored-By: Claude Sonnet 4.5 --- .gitattributes | 1 - .github/workflows/build_jar.yml | 28 +- .github/workflows/build_wheels.yml | 18 +- .github/workflows/publish_maven.yml | 14 + .github/workflows/publish_pypi.yml | 19 +- .github/workflows/release-please.yml | 81 + .release-please-manifest.json | 3 + CHANGELOG.md | 14 + CONTRIBUTING.md | 190 +++ MIGRATION_TO_SETUPTOOLS_SCM.md | 254 +++ README.md | 37 + VERSION | 1 + pyproject.toml | 47 + release-please-config.json | 28 + scripts/update_versions.py | 123 ++ setup.cfg | 9 +- setup.py | 32 +- src/filepattern/__init__.py | 15 +- src/filepattern/_version.py | 659 -------- src/filepattern/cpp/CMakeLists.txt | 53 +- src/filepattern/cpp/version.h | 7 - versioneer.py | 2141 -------------------------- 22 files changed, 917 insertions(+), 2857 deletions(-) delete mode 100644 .gitattributes create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 MIGRATION_TO_SETUPTOOLS_SCM.md create mode 100644 VERSION create mode 100644 pyproject.toml create mode 100644 release-please-config.json create mode 100755 scripts/update_versions.py delete mode 100644 src/filepattern/_version.py delete mode 100644 src/filepattern/cpp/version.h delete mode 100644 versioneer.py diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 7877032d..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -src/filepattern/_version.py export-subst diff --git a/.github/workflows/build_jar.yml b/.github/workflows/build_jar.yml index 962306c4..5516592f 100644 --- a/.github/workflows/build_jar.yml +++ b/.github/workflows/build_jar.yml @@ -15,6 +15,19 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all history for version detection + + - name: Set up Python (for version script) + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Update version files + run: | + python -m pip install setuptools-scm + python scripts/update_versions.py + - name: Set up JDK 11 uses: actions/setup-java@v1 with: @@ -30,12 +43,25 @@ jobs: needs: [build_and_test] steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all history for version detection + + - name: Set up Python (for version script) + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Update version files + run: | + python -m pip install setuptools-scm + python scripts/update_versions.py + - uses: actions/setup-java@v3 with: java-version: 11 distribution: 'temurin' architecture: x64 - + - run: mvn -B package --file pom.xml -DskipTests - run: mkdir staging && cp target/*jar-with-dependencies.jar staging - uses: actions/upload-artifact@v4 diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 2a9539f0..c9e4457a 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -23,6 +23,8 @@ jobs: steps: - uses: actions/checkout@v3 name: Check out + with: + fetch-depth: 0 # Fetch all history for setuptools-scm - uses: ilammy/msvc-dev-cmd@v1 name: Add MSVS Path @@ -32,6 +34,12 @@ jobs: with: python-version: '3.11' + - name: Update version files + run: | + python -m pip install setuptools-scm + python scripts/update_versions.py + shell: bash + - name: Install cibuildwheel run: | python -m pip install cibuildwheel delvewheel wheel @@ -82,6 +90,8 @@ jobs: steps: - uses: actions/checkout@v3 name: Check out + with: + fetch-depth: 0 # Fetch all history for setuptools-scm - uses: ilammy/msvc-dev-cmd@v1 name: Add MSVS Path @@ -90,7 +100,13 @@ jobs: name: Install Python with: python-version: '3.11' - + + - name: Update version files + run: | + python -m pip install setuptools-scm + python scripts/update_versions.py + shell: bash + - name: Install cibuildwheel run: | python -m pip install cibuildwheel delvewheel wheel diff --git a/.github/workflows/publish_maven.yml b/.github/workflows/publish_maven.yml index 0270715b..4c65c9d7 100644 --- a/.github/workflows/publish_maven.yml +++ b/.github/workflows/publish_maven.yml @@ -10,6 +10,19 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for version detection + + - name: Set up Python (for version script) + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Update version files + run: | + python -m pip install setuptools-scm + python scripts/update_versions.py + - name: Set up Maven Central Repository uses: actions/setup-java@v3 with: @@ -21,6 +34,7 @@ jobs: gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} # Substituted with the value stored in the referenced secret gpg-passphrase: SIGN_KEY_PASS # Env var that holds the key's passphrase cache: 'maven' + - name: Build & Deploy run: | # -U force updates just to make sure we are using latest dependencies diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 0af62678..65828372 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -21,7 +21,8 @@ jobs: steps: - uses: actions/checkout@v3 name: Check out - + with: + fetch-depth: 0 # Fetch all history for setuptools-scm - uses: ilammy/msvc-dev-cmd@v1 name: Add MSVS Path @@ -31,6 +32,12 @@ jobs: with: python-version: '3.11' + - name: Update version files + run: | + python -m pip install setuptools-scm + python scripts/update_versions.py + shell: bash + - name: Install cibuildwheel run: | python -m pip install cibuildwheel delvewheel wheel @@ -84,6 +91,8 @@ jobs: steps: - uses: actions/checkout@v3 name: Check out + with: + fetch-depth: 0 # Fetch all history for setuptools-scm - uses: ilammy/msvc-dev-cmd@v1 name: Add MSVS Path @@ -92,7 +101,13 @@ jobs: name: Install Python with: python-version: '3.11' - + + - name: Update version files + run: | + python -m pip install setuptools-scm + python scripts/update_versions.py + shell: bash + - name: Install cibuildwheel run: | python -m pip install cibuildwheel delvewheel wheel diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..f4236f8c --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,81 @@ +name: Release Please + +on: + push: + branches: + - master + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + version: ${{ steps.release.outputs.version }} + steps: + - uses: google-github-actions/release-please-action@v4 + id: release + with: + release-type: python + package-name: filepattern + + # If a release was created, update version files and create a follow-up commit + - name: Checkout code + if: ${{ steps.release.outputs.release_created }} + uses: actions/checkout@v4 + with: + ref: ${{ steps.release.outputs.tag_name }} + + - name: Set up Python + if: ${{ steps.release.outputs.release_created }} + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install setuptools-scm + if: ${{ steps.release.outputs.release_created }} + run: pip install setuptools-scm + + - name: Update version files + if: ${{ steps.release.outputs.release_created }} + run: | + python scripts/update_versions.py --version ${{ steps.release.outputs.version }} + echo "Updated version files to ${{ steps.release.outputs.version }}" + + - name: Upload VERSION file as release asset + if: ${{ steps.release.outputs.release_created }} + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload ${{ steps.release.outputs.tag_name }} VERSION --clobber + + trigger-publish: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + steps: + - name: Trigger PyPI publish + uses: actions/github-script@v7 + with: + script: | + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'publish_pypi.yml', + ref: '${{ needs.release-please.outputs.tag_name }}' + }) + + - name: Trigger Maven publish + uses: actions/github-script@v7 + with: + script: | + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'publish_maven.yml', + ref: '${{ needs.release-please.outputs.tag_name }}' + }) diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..38dc7f3f --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "2.1.4" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..46bdd88a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +This file is automatically generated by [release-please](https://github.com/googleapis/release-please). Do not edit manually. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.1.4](https://github.com/PolusAI/filepattern/releases/tag/v2.1.4) (2025-03-10) + +### Bug Fixes + +* handle non-alphanumeric characters in infer_pattern ([#102](https://github.com/PolusAI/filepattern/pull/102)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..ed356181 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,190 @@ +# Contributing to filepattern + +Thank you for your interest in contributing to filepattern! This document provides guidelines for contributing to the project. + +## Development Setup + +1. Clone the repository: + ```bash + git clone https://github.com/PolusAI/filepattern.git + cd filepattern + ``` + +2. Install dependencies: + ```bash + pip install -e ".[dev]" + ``` + +3. Run tests: + ```bash + pytest tests/ + ``` + +## Commit Message Convention + +We use [Conventional Commits](https://www.conventionalcommits.org/) for commit messages. This allows us to automatically generate changelogs and determine version bumps. + +### Format + +``` +: + +[optional body] + +[optional footer(s)] +``` + +### Types + +- **feat**: A new feature (triggers minor version bump) +- **fix**: A bug fix (triggers patch version bump) +- **docs**: Documentation only changes +- **style**: Changes that don't affect code meaning (formatting, white-space, etc.) +- **refactor**: Code change that neither fixes a bug nor adds a feature +- **perf**: Performance improvements +- **test**: Adding or updating tests +- **build**: Changes to build system or dependencies +- **ci**: Changes to CI configuration files and scripts +- **chore**: Other changes that don't modify src or test files + +### Breaking Changes + +For breaking changes (triggers major version bump), add `!` after the type or include `BREAKING CHANGE:` in the footer: + +``` +feat!: redesign the API interface + +BREAKING CHANGE: The FilePattern constructor now requires a pattern argument +``` + +### Examples + +**Good commit messages:** + +``` +feat: add support for nested directory patterns + +fix: handle non-alphanumeric characters in infer_pattern + +docs: update installation instructions for Python 3.13 + +perf: optimize file traversal for large directories + +refactor: simplify pattern matching logic + +test: add test cases for edge cases in vector patterns + +build: update pybind11 to version 2.12 + +ci: add Python 3.13 to test matrix +``` + +**Bad commit messages:** + +``` +Update code +Fix bug +WIP +changes +asdf +``` + +## Release Process + +Releases are automated using [release-please](https://github.com/googleapis/release-please): + +1. Make commits following the conventional commit format +2. When commits are pushed to `master`, release-please analyzes them +3. release-please creates/updates a "Release PR" with: + - Updated CHANGELOG.md + - Version bump (based on commit types) + - Updated version files +4. When the Release PR is merged: + - A GitHub Release is created automatically + - Publishing workflows are triggered (PyPI, Maven Central) + +### Version Bumping + +- `feat:` commits → **minor** version bump (e.g., 2.1.4 → 2.2.0) +- `fix:` commits → **patch** version bump (e.g., 2.1.4 → 2.1.5) +- `feat!:` or `BREAKING CHANGE:` → **major** version bump (e.g., 2.1.4 → 3.0.0) + +You don't need to manually update version numbers - the automation handles this! + +## Pull Request Guidelines + +1. **Branch naming**: Use descriptive names like `feat/add-recursive-search` or `fix/issue-123` +2. **Commit messages**: Follow the conventional commit format (see above) +3. **Tests**: Add tests for new features or bug fixes +4. **Documentation**: Update documentation for user-facing changes +5. **Code style**: Follow the existing code style and conventions + +## Testing + +Run the test suite before submitting a PR: + +```bash +# Python tests +pytest tests/ + +# C++ tests (if modified) +cd src/filepattern/cpp +mkdir build && cd build +cmake .. +make test +``` + +## Building Locally + +### Python Package + +```bash +# Install build dependencies +pip install build setuptools-scm + +# Build wheel +python -m build + +# Install locally +pip install dist/*.whl +``` + +### C++ Library + +```bash +cd src/filepattern/cpp +mkdir build && cd build +cmake .. +make +``` + +### Java Package + +```bash +mvn clean package +``` + +## Version Management + +The project uses **setuptools-scm** for Python versioning, which reads versions from git tags. Version files are automatically synchronized: + +- `VERSION` - Used by CMake for C++ library versioning +- `pom.xml` - Java package version +- Python version - Derived from git tags via setuptools-scm + +To manually update version files (normally not needed): + +```bash +python scripts/update_versions.py +``` + +## Questions? + +If you have questions or need help, please: +- Open an issue on GitHub +- Check the [documentation](https://filepattern.readthedocs.io/) +- Review existing issues and PRs + +## Code of Conduct + +Please be respectful and constructive in all interactions. We aim to maintain a welcoming and inclusive community. diff --git a/MIGRATION_TO_SETUPTOOLS_SCM.md b/MIGRATION_TO_SETUPTOOLS_SCM.md new file mode 100644 index 00000000..7c518ad6 --- /dev/null +++ b/MIGRATION_TO_SETUPTOOLS_SCM.md @@ -0,0 +1,254 @@ +# Migration to setuptools-scm and Automated Releases + +This document describes the migration from versioneer to setuptools-scm with automated releases via release-please. + +## What Changed + +### Version Management System + +**Before:** Versioneer +- Manual version updates in 3 files (pom.xml, version.h, git tags) +- Complex generated code (_version.py, versioneer.py) +- Unclear development versions (e.g., "0.post0.dev176") + +**After:** setuptools-scm + release-please +- **Single source of truth:** Git tags +- **Automated version syncing** via `scripts/update_versions.py` +- **Clear development versions** (e.g., "2.1.4.dev5+gd94438d") +- **Modern packaging** with pyproject.toml +- **Fully automated releases** via release-please + +### Files Modified + +#### Created/Added: +- `pyproject.toml` - Modern Python packaging configuration +- `scripts/update_versions.py` - Automated version synchronization script +- `.github/workflows/release-please.yml` - Automated release workflow +- `.release-please-manifest.json` - Release tracking +- `release-please-config.json` - Release configuration +- `CONTRIBUTING.md` - Contribution guidelines with conventional commits +- `CHANGELOG.md` - Auto-generated changelog +- `VERSION` - Version file for CMake (auto-generated) + +#### Modified: +- `setup.py` - Simplified to only handle CMake builds +- `setup.cfg` - Removed versioneer config +- `src/filepattern/__init__.py` - Updated version import +- `src/filepattern/cpp/CMakeLists.txt` - Updated version detection +- `.github/workflows/publish_pypi.yml` - Added version sync step +- `.github/workflows/publish_maven.yml` - Added version sync step +- `.github/workflows/build_wheels.yml` - Added version sync step +- `.github/workflows/build_jar.yml` - Added version sync step +- `README.md` - Added contributing and release sections + +#### Deleted: +- `versioneer.py` - No longer needed +- `src/filepattern/_version.py` - Now auto-generated by setuptools-scm +- `src/filepattern/cpp/version.h` - No longer needed (CMake reads from VERSION file) +- `.gitattributes` - Removed versioneer-specific export-subst directive + +### Version File Reduction + +**Before:** 3 version sources to maintain manually +1. `pom.xml` - Java version +2. `version.h` - C++ version +3. Git tags + +**After:** 2 version sources, auto-synchronized +1. Git tags (single source of truth) +2. `VERSION` file (auto-generated for CMake) +3. `pom.xml` (auto-updated by script) + +Note: `version.h` was **eliminated entirely** - it wasn't used by any C++ source files! + +## How It Works Now + +### Development Workflow + +1. Make changes following [Conventional Commits](https://www.conventionalcommits.org/) + - `feat:` for new features → minor version bump + - `fix:` for bug fixes → patch version bump + - `feat!:` or `BREAKING CHANGE:` → major version bump + +2. Push commits to a branch and create a PR + +3. Merge PR to `master` + +### Automated Release Process + +1. **On merge to master:** + - release-please analyzes commit messages + - Creates/updates a "Release PR" with: + - Version bump (based on commit types) + - Updated CHANGELOG.md + - Version updates in tracked files + +2. **When Release PR is merged:** + - Git tag is created automatically (e.g., `v2.2.0`) + - GitHub Release is created + - Version files are synced (VERSION, pom.xml) + - Publishing workflows are triggered + - Artifacts are published to PyPI and Maven Central + +### Version Detection + +**Python:** +- Uses setuptools-scm to read from git tags +- During development: shows versions like "2.1.4.dev5+gd94438d" +- In releases: shows clean versions like "2.1.4" + +**C++ (CMake):** +- Reads from `VERSION` file (auto-generated) +- Falls back to git tags if VERSION file doesn't exist +- Falls back to default version if neither exists + +**Java (Maven):** +- Version in `pom.xml` is auto-updated by `scripts/update_versions.py` +- Script runs automatically in CI/CD workflows + +## Testing the Migration + +### Local Testing + +1. **Test Python version detection:** + ```bash + # Install in development mode + pip install -e . + + # Check version + python -c "import filepattern; print(filepattern.__version__)" + # Should show something like: 2.1.4.dev0+gd94438d + ``` + +2. **Test version sync script:** + ```bash + # Manually run version sync + python scripts/update_versions.py + + # Check VERSION file + cat VERSION + + # Check pom.xml + grep -A2 'filepattern' pom.xml + ``` + +3. **Test CMake version detection:** + ```bash + cd src/filepattern/cpp + mkdir build && cd build + cmake .. + # Should see: "Building libfilepattern 2.1.4 (from VERSION file)" + ``` + +4. **Test building:** + ```bash + # Python wheel + pip install build + python -m build + + # Java package + mvn clean package + ``` + +### CI/CD Testing + +1. **Test release-please workflow:** + - Make a commit with conventional format: `feat: add test feature` + - Push to a branch and create PR to master + - Merge the PR + - Check that release-please creates a "Release PR" + - Review the Release PR (check version bump, changelog) + - Merge the Release PR + - Verify GitHub Release is created + - Verify publishing workflows trigger + +2. **Test publish workflows:** + - After release is created, check GitHub Actions + - Verify PyPI publishing succeeds + - Verify Maven Central publishing succeeds + +## Rollback Plan + +If issues arise, you can temporarily revert: + +1. **Revert the migration:** + ```bash + git revert + ``` + +2. **Manual version management (temporary):** + - Update `pom.xml` manually + - Create git tags manually: `git tag v2.1.5` + - Create GitHub Releases manually + +3. **Disable release-please:** + - Disable or delete `.github/workflows/release-please.yml` + - Continue with manual release process + +## Benefits of This Migration + +✅ **No more manual version updates** - Everything is automated +✅ **Single source of truth** - Git tags drive all versioning +✅ **Clear development versions** - Easy to identify dev vs release builds +✅ **Automated changelog** - Generated from commit messages +✅ **Consistent commit style** - Conventional commits improve readability +✅ **Simplified releases** - Just merge a PR, everything else is automatic +✅ **Reduced maintenance** - Fewer manual steps means fewer errors +✅ **Modern tooling** - Using actively maintained, industry-standard tools + +## Troubleshooting + +### "Version not found" errors during build + +**Cause:** VERSION file not generated +**Solution:** Run `python scripts/update_versions.py` before building + +### setuptools-scm can't determine version + +**Cause:** Not a git repository or no tags present +**Solution:** +```bash +# Make sure you're in a git repo +git status + +# Check for tags +git tag -l + +# If no tags, create one +git tag v2.1.4 +``` + +### CMake can't find version + +**Cause:** VERSION file doesn't exist and git is not available +**Solution:** +```bash +# Generate VERSION file +python scripts/update_versions.py + +# Or create manually +echo "2.1.4" > VERSION +``` + +### Release PR not being created + +**Cause:** Commits don't follow conventional format +**Solution:** Use proper commit message format: +```bash +git commit -m "feat: add new feature" +git commit -m "fix: resolve bug" +``` + +## Additional Resources + +- [Conventional Commits Specification](https://www.conventionalcommits.org/) +- [setuptools-scm Documentation](https://setuptools-scm.readthedocs.io/) +- [release-please Documentation](https://github.com/googleapis/release-please) +- [CONTRIBUTING.md](CONTRIBUTING.md) - Full contribution guidelines + +## Questions or Issues? + +If you encounter problems with the migration: +1. Check this document's Troubleshooting section +2. Review the [CONTRIBUTING.md](CONTRIBUTING.md) guide +3. Open an issue on GitHub with details about the problem diff --git a/README.md b/README.md index c7cf653c..469f552a 100644 --- a/README.md +++ b/README.md @@ -471,6 +471,43 @@ where the output is returned in blocks of `block_size`. The output is: Out of core processing can also be used for stitching vectors and text files. To utilize this functionality, call ``filepattern`` the same way as described previously, but add in the ``block_size`` parameter, as described in the (Out of Core)[#out-of-core] section. +## Contributing + +We welcome contributions to filepattern! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines. + +### Quick Start for Contributors + +1. Fork the repository +2. Create a feature branch: `git checkout -b feat/my-feature` +3. Make your changes following [Conventional Commits](https://www.conventionalcommits.org/) +4. Push to your fork and submit a pull request + +### Commit Message Format + +We use Conventional Commits for automated releases: + +- `feat:` - New features (minor version bump) +- `fix:` - Bug fixes (patch version bump) +- `docs:` - Documentation changes +- `refactor:` - Code refactoring +- `test:` - Test additions or changes +- `chore:` - Maintenance tasks + +Example: `feat: add support for nested directory patterns` + +For breaking changes, use `feat!:` or include `BREAKING CHANGE:` in the commit footer (major version bump). + +### Release Process + +Releases are fully automated: + +1. Commits following conventional format are pushed to `master` +2. Release Please creates/updates a Release PR with version bump and changelog +3. When the Release PR is merged, a GitHub Release is created +4. Automated workflows publish to PyPI and Maven Central + +You don't need to manually update version numbers - the automation handles this based on your commit messages! + ## Authors Jesse McKinzie(Jesse.McKinzie@axleinfo.com, jesse.mckinzie@nih.gov) diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..7d2ed7c7 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2.1.4 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3a2b4ba5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,47 @@ +[build-system] +requires = ["setuptools>=64", "setuptools-scm>=8", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "filepattern" +dynamic = ["version"] +description = "Utilities for parsing files in a directory based on a file name pattern." +readme = "README.md" +requires-python = ">=3.6" +license = {text = "MIT License"} +authors = [ + {name = "Jesse McKinzie", email = "Jesse.McKinzie@axleinfo.com"}, + {name = "Nick Schaub", email = "nick.schaub@nih.gov"} +] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dependencies = [ + "pydantic", +] + +[project.urls] +Documentation = "https://filepattern.readthedocs.io/en/latest/" +Source = "https://github.com/PolusAI/filepattern" +Homepage = "https://github.com/PolusAI/filepattern" + +[tool.setuptools] +package-dir = {"" = "src"} +zip-safe = false + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools_scm] +version_scheme = "post-release" +local_scheme = "no-local-version" +tag_regex = "^v(?P[0-9]+\\.[0-9]+\\.[0-9]+)$" +git_describe_command = "git describe --dirty --tags --long --match 'v*'" +write_to = "src/filepattern/_version.py" +write_to_template = """ +# This file is auto-generated by setuptools-scm +__version__ = "{version}" +__version_tuple__ = {version_tuple} +""" diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..4208a4b7 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,28 @@ +{ + "packages": { + ".": { + "release-type": "python", + "package-name": "filepattern", + "changelog-sections": [ + {"type": "feat", "section": "Features"}, + {"type": "fix", "section": "Bug Fixes"}, + {"type": "perf", "section": "Performance Improvements"}, + {"type": "revert", "section": "Reverts"}, + {"type": "docs", "section": "Documentation"}, + {"type": "style", "section": "Styles", "hidden": true}, + {"type": "chore", "section": "Miscellaneous Chores", "hidden": true}, + {"type": "refactor", "section": "Code Refactoring"}, + {"type": "test", "section": "Tests", "hidden": true}, + {"type": "build", "section": "Build System"}, + {"type": "ci", "section": "Continuous Integration", "hidden": true} + ], + "release-as": "2.1.4", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "extra-files": [ + "pom.xml", + "VERSION" + ] + } + } +} diff --git a/scripts/update_versions.py b/scripts/update_versions.py new file mode 100755 index 00000000..229f04ad --- /dev/null +++ b/scripts/update_versions.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +Update version files for filepattern project. + +This script: +1. Reads the version from setuptools-scm (git tags) +2. Writes it to the VERSION file (for CMake) +3. Updates the version in pom.xml (for Maven) + +Usage: + python scripts/update_versions.py [--version VERSION] + +If --version is not provided, it will be auto-detected from git. +""" +import argparse +import re +import sys +from pathlib import Path + + +def get_version_from_scm(): + """Get version from setuptools-scm.""" + try: + from setuptools_scm import get_version + version = get_version(root='..') + # Extract base version (remove dev/local version identifiers) + base_version = version.split('+')[0].split('.dev')[0] + return base_version + except ImportError: + print("Warning: setuptools-scm not installed, trying git directly", file=sys.stderr) + return None + except Exception as e: + print(f"Warning: Could not get version from setuptools-scm: {e}", file=sys.stderr) + return None + + +def get_version_from_git(): + """Get version from git tags as fallback.""" + import subprocess + try: + result = subprocess.run( + ['git', 'describe', '--tags', '--abbrev=0'], + capture_output=True, + text=True, + check=True + ) + version = result.stdout.strip() + # Remove 'v' prefix if present + if version.startswith('v'): + version = version[1:] + return version + except Exception as e: + print(f"Error: Could not get version from git: {e}", file=sys.stderr) + return None + + +def write_version_file(version, root_dir): + """Write VERSION file for CMake.""" + version_file = root_dir / 'VERSION' + version_file.write_text(version + '\n') + print(f"✓ Updated {version_file} to {version}") + + +def update_pom_xml(version, root_dir): + """Update version in pom.xml.""" + pom_file = root_dir / 'pom.xml' + if not pom_file.exists(): + print(f"Warning: {pom_file} not found, skipping", file=sys.stderr) + return + + content = pom_file.read_text() + + # Update the version tag (first occurrence after filepattern) + pattern = r'(filepattern\s*)[^<]+()' + replacement = rf'\g<1>{version}\g<2>' + + new_content, count = re.subn(pattern, replacement, content, count=1) + + if count == 0: + print(f"Warning: Could not find version tag in {pom_file}", file=sys.stderr) + return + + pom_file.write_text(new_content) + print(f"✓ Updated {pom_file} to {version}") + + +def main(): + parser = argparse.ArgumentParser(description='Update version files for filepattern') + parser.add_argument('--version', help='Version to set (auto-detected from git if not provided)') + parser.add_argument('--root', default='.', help='Root directory of the project') + args = parser.parse_args() + + root_dir = Path(args.root).resolve() + + # Get version + if args.version: + version = args.version + print(f"Using provided version: {version}") + else: + # Try setuptools-scm first, then fall back to git + version = get_version_from_scm() + if not version: + version = get_version_from_git() + + if not version: + print("Error: Could not determine version. Please provide it with --version", file=sys.stderr) + sys.exit(1) + + print(f"Auto-detected version: {version}") + + # Validate version format + if not re.match(r'^\d+\.\d+\.\d+$', version): + print(f"Warning: Version '{version}' does not match expected format (x.y.z)", file=sys.stderr) + + # Update files + write_version_file(version, root_dir) + update_pom_xml(version, root_dir) + + print(f"\n✓ All version files updated to {version}") + + +if __name__ == '__main__': + main() diff --git a/setup.cfg b/setup.cfg index 6c5cfe30..4b3fe996 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,2 @@ -[versioneer] -# Automatic version numbering scheme -VCS = git -style = pep440-pre -versionfile_source = src/filepattern/_version.py -versionfile_build = filepattern/_version.py -tag_prefix = v +# Versioneer has been removed - now using setuptools-scm +# See pyproject.toml for version configuration diff --git a/setup.py b/setup.py index 7f1762f4..533cdb24 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,6 @@ import shutil import subprocess import sys -import versioneer - with open("README.md", "r") as fh: long_description = fh.read() @@ -82,32 +80,8 @@ def build_extension(self, ext): setuptools.setup( - name="filepattern", - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(dict(build_ext=CMakeBuild)), - author="Jesse McKinzie, Nick Schaub", - author_email="Jesse.McKinzie@axleinfo.com, nick.schaub@nih.gov", - description="Utilities for parsing files in a directory based on a file name pattern.", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/PolusAI/filepattern", - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - project_urls={ - 'Documentation': 'https://filepattern.readthedocs.io/en/latest/', - 'Source': 'https://github.com/PolusAI/filepattern' - }, - python_requires='>=3.6', - packages=find_packages('src'), - # tell setuptools that all packages will be under the 'src' directory - # and nowhere else - package_dir={'': 'src'}, - # add an extension module named 'python_cpp_example' to the package - # 'python_cpp_example' + # Metadata now comes from pyproject.toml + # Keep only the custom build configuration here + cmdclass={'build_ext': CMakeBuild}, ext_modules=[CMakeExtension('filepattern/backend')], - install_requires=["pydantic"], - zip_safe=False, ) diff --git a/src/filepattern/__init__.py b/src/filepattern/__init__.py index 62ae28b4..d989e760 100644 --- a/src/filepattern/__init__.py +++ b/src/filepattern/__init__.py @@ -6,5 +6,16 @@ from .functions import infer_pattern, get_regex, get_variables __all__ = ["FilePattern", "infer_pattern", "get_regex"] -from . import _version -__version__ = _version.get_versions()['version'] + +# Get version from setuptools-scm +try: + from importlib.metadata import version, PackageNotFoundError +except ImportError: + # Python < 3.8 + from importlib_metadata import version, PackageNotFoundError + +try: + __version__ = version("filepattern") +except PackageNotFoundError: + # Package is not installed + __version__ = "unknown" diff --git a/src/filepattern/_version.py b/src/filepattern/_version.py deleted file mode 100644 index fb00ee84..00000000 --- a/src/filepattern/_version.py +++ /dev/null @@ -1,659 +0,0 @@ -# -*- coding: utf-8 -*- - -# 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. - -# This file is released into the public domain. Generated by -# versioneer-0.22 (https://github.com/python-versioneer/python-versioneer) - -"""Git implementation of _version.py.""" - -import errno -import os -import re -import subprocess -import sys -from typing import Callable, Dict -import functools - - -def get_keywords(): - """Get the keywords needed to look up the version information.""" - # these strings will be replaced by git during git-archive. - # setup.py/versioneer.py will grep for the variable names, so they must - # each be defined on a line of their own. _version.py will just call - # get_keywords(). - git_refnames = "$Format:%d$" - git_full = "$Format:%H$" - git_date = "$Format:%ci$" - keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} - return keywords - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_config(): - """Create, populate and return the VersioneerConfig() object.""" - # these strings are filled in when 'setup.py versioneer' creates - # _version.py - cfg = VersioneerConfig() - cfg.VCS = "git" - cfg.style = "pep440-pre" - cfg.tag_prefix = "" - cfg.parentdir_prefix = "None" - cfg.versionfile_source = "src/filepattern/_version.py" - cfg.verbose = False - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - - -LONG_VERSION_PY: Dict[str, str] = {} -HANDLERS: Dict[str, Dict[str, Callable]] = {} - - -def register_vcs_handler(vcs, method): # decorator - """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): - """Call the given command(s).""" - assert isinstance(commands, list) - process = None - - popen_kwargs = {} - if sys.platform == "win32": - # This hides the console window if pythonw.exe is used - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - popen_kwargs["startupinfo"] = startupinfo - - for command in commands: - try: - dispcmd = str([command] + args) - # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None), **popen_kwargs) - break - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % dispcmd) - print(e) - return None, None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None, None - stdout = process.communicate()[0].strip().decode() - if process.returncode != 0: - if verbose: - print("unable to run %s (error)" % dispcmd) - print("stdout was %s" % stdout) - return None, process.returncode - return stdout, process.returncode - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes both - the project name and a version string. We will also support searching up - two directory levels for an appropriately named parent directory - """ - rootdirs = [] - - for _ in range(3): - dirname = os.path.basename(root) - if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} - rootdirs.append(root) - root = os.path.dirname(root) # up a level - - if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - with open(versionfile_abs, "r") as fobj: - for line in fobj: - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - except OSError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if "refnames" not in keywords: - raise NotThisMethod("Short version file found") - date = keywords.get("date") - if date is not None: - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - - # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant - # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 - # -like" string, which we must then edit to make compliant), because - # it's been around since git-1.5.3, and it's too difficult to - # discover which version we're using, or to work around using an - # older one. - date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = {r.strip() for r in refnames.strip("()").split(",")} - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = {r for r in refs if re.search(r'\d', r)} - if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - # Filter out refs that exactly match prefix or that don't start - # with a number once the prefix is stripped (mostly a concern - # when prefix is '') - if not re.match(r'\d', r): - continue - if verbose: - print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - - # GIT_DIR can interfere with correct operation of Versioneer. - # It may be intended to be passed to the Versioneer-versioned project, - # but that should not change where we get our version from. - env = os.environ.copy() - env.pop("GIT_DIR", None) - runner = functools.partial(runner, env=env) - - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) - if rc != 0: - if verbose: - print("Directory %s not under git control" % root) - raise NotThisMethod("'git rev-parse --git-dir' returned error") - - MATCH_ARGS = ["--match", "%s*" % tag_prefix] if tag_prefix else [] - - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", *MATCH_ARGS], - cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], - cwd=root) - # --abbrev-ref was added in git-1.6.3 - if rc != 0 or branch_name is None: - raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") - branch_name = branch_name.strip() - - if branch_name == "HEAD": - # If we aren't exactly on a branch, pick a branch which represents - # the current commit. If all else fails, we are on a branchless - # commit. - branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) - # --contains was added in git-1.5.4 - if rc != 0 or branches is None: - raise NotThisMethod("'git branch --contains' returned error") - branches = branches.split("\n") - - # Remove the first line if we're running detached - if "(" in branches[0]: - branches.pop(0) - - # Strip off the leading "* " from the list of branches. - branches = [branch[2:] for branch in branches] - if "master" in branches: - branch_name = "master" - elif not branches: - branch_name = None - else: - # Pick the first branch that is returned. Good or bad. - branch_name = branches[0] - - pieces["branch"] = branch_name - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%s' doesn't start with prefix '%s'" - print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) - pieces["distance"] = int(count_out) # total number of commits - - # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - - return pieces - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_branch(pieces): - """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . - - The ".dev0" means not master branch. Note that .dev0 sorts backwards - (a feature branch will appear "older" than the master branch). - - Exceptions: - 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0" - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def pep440_split_post(ver): - """Split pep440 version string at the post-release segment. - - Returns the release segments before the post-release and the - post-release version number (or -1 if no post-release segment is present). - """ - vc = str.split(ver, ".post") - return vc[0], int(vc[1] or 0) if len(vc) == 2 else None - - -def render_pep440_pre(pieces): - """TAG[.postN.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post0.devDISTANCE - """ - if pieces["closest-tag"]: - if pieces["distance"]: - # update the post release segment - tag_version, post_version = pep440_split_post(pieces["closest-tag"]) - rendered = tag_version - if post_version is not None: - rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) - else: - rendered += ".post0.dev%d" % (pieces["distance"]) - else: - # no commits, use the tag as the version - rendered = pieces["closest-tag"] - else: - # exception #1 - rendered = "0.post0.dev%d" % pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - return rendered - - -def render_pep440_post_branch(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . - - The ".dev0" means not master branch. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-branch": - rendered = render_pep440_branch(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-post-branch": - rendered = render_pep440_post_branch(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%s'" % style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} - - -def get_versions(): - """Get version information or return default if unable to do so.""" - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded keywords. - - cfg = get_config() - verbose = cfg.verbose - - try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) - except NotThisMethod: - pass - - try: - root = os.path.realpath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for _ in cfg.versionfile_source.split('/'): - root = os.path.dirname(root) - except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} - - try: - pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) - return render(pieces, cfg.style) - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - except NotThisMethod: - pass - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} diff --git a/src/filepattern/cpp/CMakeLists.txt b/src/filepattern/cpp/CMakeLists.txt index f8e76c20..0af946aa 100644 --- a/src/filepattern/cpp/CMakeLists.txt +++ b/src/filepattern/cpp/CMakeLists.txt @@ -9,13 +9,52 @@ if(DEFINED ENV{FILEPATTERN_DEP_DIR}) link_directories($ENV{FILEPATTERN_DEP_DIR}/lib) endif() -file(READ version.h VER_FILE ) -string(REGEX MATCH "#define PROJECT_VER \"([0-9]+)\.([0-9]+)\.([0-9+])\"" _ "${VER_FILE}") -set (filepattern_VERSION_MAJOR ${CMAKE_MATCH_1}) -set (filepattern_VERSION_MINOR ${CMAKE_MATCH_2}) -set (filepattern_VERSION_PATCH ${CMAKE_MATCH_3}) -set(filepattern_VERSION "${filepattern_VERSION_MAJOR}.${filepattern_VERSION_MINOR}.${filepattern_VERSION_PATCH}") -message(STATUS "Building libfilepattern ${filepattern_VERSION}" ) +# Read version from VERSION file (generated by Python build) or git +set(VERSION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../../VERSION") +if(EXISTS "${VERSION_FILE}") + file(READ "${VERSION_FILE}" FILE_VERSION) + string(STRIP "${FILE_VERSION}" FILE_VERSION) + string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" _ "${FILE_VERSION}") + set(filepattern_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(filepattern_VERSION_MINOR ${CMAKE_MATCH_2}) + set(filepattern_VERSION_PATCH ${CMAKE_MATCH_3}) + set(filepattern_VERSION "${FILE_VERSION}") + message(STATUS "Building libfilepattern ${filepattern_VERSION} (from VERSION file)") +else() + # Fallback to git if VERSION file doesn't exist + find_package(Git QUIET) + if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if(GIT_VERSION) + string(REGEX MATCH "v?([0-9]+)\\.([0-9]+)\\.([0-9]+)" _ "${GIT_VERSION}") + set(filepattern_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(filepattern_VERSION_MINOR ${CMAKE_MATCH_2}) + set(filepattern_VERSION_PATCH ${CMAKE_MATCH_3}) + set(filepattern_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") + message(STATUS "Building libfilepattern ${filepattern_VERSION} (from git)") + else() + # Default fallback version + set(filepattern_VERSION_MAJOR 2) + set(filepattern_VERSION_MINOR 1) + set(filepattern_VERSION_PATCH 4) + set(filepattern_VERSION "2.1.4") + message(WARNING "Could not determine version from VERSION file or git, using default: ${filepattern_VERSION}") + endif() + else() + # Default fallback version if git is not found + set(filepattern_VERSION_MAJOR 2) + set(filepattern_VERSION_MINOR 1) + set(filepattern_VERSION_PATCH 4) + set(filepattern_VERSION "2.1.4") + message(WARNING "Git not found and VERSION file missing, using default: ${filepattern_VERSION}") + endif() +endif() if(JAVA_BINDING) add_compile_definitions(JAVA_BINDING) diff --git a/src/filepattern/cpp/version.h b/src/filepattern/cpp/version.h deleted file mode 100644 index 0c9d3177..00000000 --- a/src/filepattern/cpp/version.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef VERSIONH_INCLUDED -#define VERSIONH_INCLUDED - -#define PROJECT_NAME "filepattern" -#define PROJECT_VER "2.1.4" - -#endif // VERSIONH_INCLUDED diff --git a/versioneer.py b/versioneer.py deleted file mode 100644 index 64770edd..00000000 --- a/versioneer.py +++ /dev/null @@ -1,2141 +0,0 @@ -# -*- coding: utf-8 -*- - -# Version: 0.22 - -"""The Versioneer - like a rocketeer, but for versions. - -The Versioneer -============== - -* like a rocketeer, but for versions! -* https://github.com/python-versioneer/python-versioneer -* Brian Warner -* License: Public Domain -* Compatible with: Python 3.6, 3.7, 3.8, 3.9, 3.10 and pypy3 -* [![Latest Version][pypi-image]][pypi-url] -* [![Build Status][travis-image]][travis-url] - -This is a tool for managing a recorded version number in distutils/setuptools-based -python projects. The goal is to remove the tedious and error-prone "update -the embedded version string" step from your release process. Making a new -release should be as easy as recording a new tag in your version-control -system, and maybe making new tarballs. - - -## Quick Install - -* `pip install versioneer` to somewhere in your $PATH -* add a `[versioneer]` section to your setup.cfg (see [Install](INSTALL.md)) -* run `versioneer install` in your source tree, commit the results -* Verify version information with `python setup.py version` - -## Version Identifiers - -Source trees come from a variety of places: - -* a version-control system checkout (mostly used by developers) -* a nightly tarball, produced by build automation -* a snapshot tarball, produced by a web-based VCS browser, like github's - "tarball from tag" feature -* a release tarball, produced by "setup.py sdist", distributed through PyPI - -Within each source tree, the version identifier (either a string or a number, -this tool is format-agnostic) can come from a variety of places: - -* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows - about recent "tags" and an absolute revision-id -* the name of the directory into which the tarball was unpacked -* an expanded VCS keyword ($Id$, etc) -* a `_version.py` created by some earlier build step - -For released software, the version identifier is closely related to a VCS -tag. Some projects use tag names that include more than just the version -string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool -needs to strip the tag prefix to extract the version identifier. For -unreleased software (between tags), the version identifier should provide -enough information to help developers recreate the same tree, while also -giving them an idea of roughly how old the tree is (after version 1.2, before -version 1.3). Many VCS systems can report a description that captures this, -for example `git describe --tags --dirty --always` reports things like -"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the -0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has -uncommitted changes). - -The version identifier is used for multiple purposes: - -* to allow the module to self-identify its version: `myproject.__version__` -* to choose a name and prefix for a 'setup.py sdist' tarball - -## Theory of Operation - -Versioneer works by adding a special `_version.py` file into your source -tree, where your `__init__.py` can import it. This `_version.py` knows how to -dynamically ask the VCS tool for version information at import time. - -`_version.py` also contains `$Revision$` markers, and the installation -process marks `_version.py` to have this marker rewritten with a tag name -during the `git archive` command. As a result, generated tarballs will -contain enough information to get the proper version. - -To allow `setup.py` to compute a version too, a `versioneer.py` is added to -the top level of your source tree, next to `setup.py` and the `setup.cfg` -that configures it. This overrides several distutils/setuptools commands to -compute the version when invoked, and changes `setup.py build` and `setup.py -sdist` to replace `_version.py` with a small static file that contains just -the generated version data. - -## Installation - -See [INSTALL.md](./INSTALL.md) for detailed installation instructions. - -## Version-String Flavors - -Code which uses Versioneer can learn about its version string at runtime by -importing `_version` from your main `__init__.py` file and running the -`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can -import the top-level `versioneer.py` and run `get_versions()`. - -Both functions return a dictionary with different flavors of version -information: - -* `['version']`: A condensed version string, rendered using the selected - style. This is the most commonly used value for the project's version - string. The default "pep440" style yields strings like `0.11`, - `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section - below for alternative styles. - -* `['full-revisionid']`: detailed revision identifier. For Git, this is the - full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". - -* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the - commit date in ISO 8601 format. This will be None if the date is not - available. - -* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that - this is only accurate if run in a VCS checkout, otherwise it is likely to - be False or None - -* `['error']`: if the version string could not be computed, this will be set - to a string describing the problem, otherwise it will be None. It may be - useful to throw an exception in setup.py if this is set, to avoid e.g. - creating tarballs with a version string of "unknown". - -Some variants are more useful than others. Including `full-revisionid` in a -bug report should allow developers to reconstruct the exact code being tested -(or indicate the presence of local changes that should be shared with the -developers). `version` is suitable for display in an "about" box or a CLI -`--version` output: it can be easily compared against release notes and lists -of bugs fixed in various releases. - -The installer adds the following text to your `__init__.py` to place a basic -version in `YOURPROJECT.__version__`: - - from ._version import get_versions - __version__ = get_versions()['version'] - del get_versions - -## Styles - -The setup.cfg `style=` configuration controls how the VCS information is -rendered into a version string. - -The default style, "pep440", produces a PEP440-compliant string, equal to the -un-prefixed tag name for actual releases, and containing an additional "local -version" section with more detail for in-between builds. For Git, this is -TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags ---dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the -tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and -that this commit is two revisions ("+2") beyond the "0.11" tag. For released -software (exactly equal to a known tag), the identifier will only contain the -stripped tag, e.g. "0.11". - -Other styles are available. See [details.md](details.md) in the Versioneer -source tree for descriptions. - -## Debugging - -Versioneer tries to avoid fatal errors: if something goes wrong, it will tend -to return a version of "0+unknown". To investigate the problem, run `setup.py -version`, which will run the version-lookup code in a verbose mode, and will -display the full contents of `get_versions()` (including the `error` string, -which may help identify what went wrong). - -## Known Limitations - -Some situations are known to cause problems for Versioneer. This details the -most significant ones. More can be found on Github -[issues page](https://github.com/python-versioneer/python-versioneer/issues). - -### Subprojects - -Versioneer has limited support for source trees in which `setup.py` is not in -the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are -two common reasons why `setup.py` might not be in the root: - -* Source trees which contain multiple subprojects, such as - [Buildbot](https://github.com/buildbot/buildbot), which contains both - "master" and "slave" subprojects, each with their own `setup.py`, - `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI - distributions (and upload multiple independently-installable tarballs). -* Source trees whose main purpose is to contain a C library, but which also - provide bindings to Python (and perhaps other languages) in subdirectories. - -Versioneer will look for `.git` in parent directories, and most operations -should get the right version string. However `pip` and `setuptools` have bugs -and implementation details which frequently cause `pip install .` from a -subproject directory to fail to find a correct version string (so it usually -defaults to `0+unknown`). - -`pip install --editable .` should work correctly. `setup.py install` might -work too. - -Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in -some later version. - -[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking -this issue. The discussion in -[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the -issue from the Versioneer side in more detail. -[pip PR#3176](https://github.com/pypa/pip/pull/3176) and -[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve -pip to let Versioneer work correctly. - -Versioneer-0.16 and earlier only looked for a `.git` directory next to the -`setup.cfg`, so subprojects were completely unsupported with those releases. - -### Editable installs with setuptools <= 18.5 - -`setup.py develop` and `pip install --editable .` allow you to install a -project into a virtualenv once, then continue editing the source code (and -test) without re-installing after every change. - -"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a -convenient way to specify executable scripts that should be installed along -with the python package. - -These both work as expected when using modern setuptools. When using -setuptools-18.5 or earlier, however, certain operations will cause -`pkg_resources.DistributionNotFound` errors when running the entrypoint -script, which must be resolved by re-installing the package. This happens -when the install happens with one version, then the egg_info data is -regenerated while a different version is checked out. Many setup.py commands -cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into -a different virtualenv), so this can be surprising. - -[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes -this one, but upgrading to a newer version of setuptools should probably -resolve it. - - -## Updating Versioneer - -To upgrade your project to a new release of Versioneer, do the following: - -* install the new Versioneer (`pip install -U versioneer` or equivalent) -* edit `setup.cfg`, if necessary, to include any new configuration settings - indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. -* re-run `versioneer install` in your source tree, to replace - `SRC/_version.py` -* commit any changed files - -## Future Directions - -This tool is designed to make it easily extended to other version-control -systems: all VCS-specific components are in separate directories like -src/git/ . The top-level `versioneer.py` script is assembled from these -components by running make-versioneer.py . In the future, make-versioneer.py -will take a VCS name as an argument, and will construct a version of -`versioneer.py` that is specific to the given VCS. It might also take the -configuration arguments that are currently provided manually during -installation by editing setup.py . Alternatively, it might go the other -direction and include code from all supported VCS systems, reducing the -number of intermediate scripts. - -## Similar projects - -* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time - dependency -* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of - versioneer -* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools - plugin - -## License - -To make Versioneer easier to embed, all its code is dedicated to the public -domain. The `_version.py` that it creates is also in the public domain. -Specifically, both are released under the Creative Commons "Public Domain -Dedication" license (CC0-1.0), as described in -https://creativecommons.org/publicdomain/zero/1.0/ . - -[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg -[pypi-url]: https://pypi.python.org/pypi/versioneer/ -[travis-image]: -https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg -[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer - -""" -# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring -# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements -# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error -# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with -# pylint:disable=attribute-defined-outside-init,too-many-arguments - -import configparser -import errno -import json -import os -import re -import subprocess -import sys -from typing import Callable, Dict -import functools - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_root(): - """Get the project root directory. - - We require that all commands are run from the project root, i.e. the - directory that contains setup.py, setup.cfg, and versioneer.py . - """ - root = os.path.realpath(os.path.abspath(os.getcwd())) - setup_py = os.path.join(root, "setup.py") - versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): - # allow 'python path/to/setup.py COMMAND' - root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) - setup_py = os.path.join(root, "setup.py") - versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): - err = ("Versioneer was unable to run the project root directory. " - "Versioneer requires setup.py to be executed from " - "its immediate directory (like 'python setup.py COMMAND'), " - "or in a way that lets it use sys.argv[0] to find the root " - "(like 'python path/to/setup.py COMMAND').") - raise VersioneerBadRootError(err) - try: - # Certain runtime workflows (setup.py install/develop in a setuptools - # tree) execute all dependencies in a single python process, so - # "versioneer" may be imported multiple times, and python's shared - # module-import table will cache the first one. So we can't use - # os.path.dirname(__file__), as that will find whichever - # versioneer.py was first imported, even in later projects. - my_path = os.path.realpath(os.path.abspath(__file__)) - me_dir = os.path.normcase(os.path.splitext(my_path)[0]) - vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) - if me_dir != vsr_dir: - print("Warning: build in %s is using versioneer.py from %s" - % (os.path.dirname(my_path), versioneer_py)) - except NameError: - pass - return root - - -def get_config_from_root(root): - """Read the project setup.cfg file to determine Versioneer config.""" - # This might raise OSError (if setup.cfg is missing), or - # configparser.NoSectionError (if it lacks a [versioneer] section), or - # configparser.NoOptionError (if it lacks "VCS="). See the docstring at - # the top of versioneer.py for instructions on writing your setup.cfg . - setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.ConfigParser() - with open(setup_cfg, "r") as cfg_file: - parser.read_file(cfg_file) - VCS = parser.get("versioneer", "VCS") # mandatory - - # Dict-like interface for non-mandatory entries - section = parser["versioneer"] - - cfg = VersioneerConfig() - cfg.VCS = VCS - cfg.style = section.get("style", "") - cfg.versionfile_source = section.get("versionfile_source") - cfg.versionfile_build = section.get("versionfile_build") - cfg.tag_prefix = section.get("tag_prefix") - if cfg.tag_prefix in ("''", '""'): - cfg.tag_prefix = "" - cfg.parentdir_prefix = section.get("parentdir_prefix") - cfg.verbose = section.get("verbose") - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - - -# these dictionaries contain VCS-specific tools -LONG_VERSION_PY: Dict[str, str] = {} -HANDLERS: Dict[str, Dict[str, Callable]] = {} - - -def register_vcs_handler(vcs, method): # decorator - """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - HANDLERS.setdefault(vcs, {})[method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): - """Call the given command(s).""" - assert isinstance(commands, list) - process = None - - popen_kwargs = {} - if sys.platform == "win32": - # This hides the console window if pythonw.exe is used - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - popen_kwargs["startupinfo"] = startupinfo - - for command in commands: - try: - dispcmd = str([command] + args) - # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None), **popen_kwargs) - break - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % dispcmd) - print(e) - return None, None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None, None - stdout = process.communicate()[0].strip().decode() - if process.returncode != 0: - if verbose: - print("unable to run %s (error)" % dispcmd) - print("stdout was %s" % stdout) - return None, process.returncode - return stdout, process.returncode - - -LONG_VERSION_PY['git'] = r''' -# 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. - -# This file is released into the public domain. Generated by -# versioneer-0.22 (https://github.com/python-versioneer/python-versioneer) - -"""Git implementation of _version.py.""" - -import errno -import os -import re -import subprocess -import sys -from typing import Callable, Dict -import functools - - -def get_keywords(): - """Get the keywords needed to look up the version information.""" - # these strings will be replaced by git during git-archive. - # setup.py/versioneer.py will grep for the variable names, so they must - # each be defined on a line of their own. _version.py will just call - # get_keywords(). - git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" - git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" - git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" - keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} - return keywords - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_config(): - """Create, populate and return the VersioneerConfig() object.""" - # these strings are filled in when 'setup.py versioneer' creates - # _version.py - cfg = VersioneerConfig() - cfg.VCS = "git" - cfg.style = "%(STYLE)s" - cfg.tag_prefix = "%(TAG_PREFIX)s" - cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" - cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" - cfg.verbose = False - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - - -LONG_VERSION_PY: Dict[str, str] = {} -HANDLERS: Dict[str, Dict[str, Callable]] = {} - - -def register_vcs_handler(vcs, method): # decorator - """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): - """Call the given command(s).""" - assert isinstance(commands, list) - process = None - - popen_kwargs = {} - if sys.platform == "win32": - # This hides the console window if pythonw.exe is used - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - popen_kwargs["startupinfo"] = startupinfo - - for command in commands: - try: - dispcmd = str([command] + args) - # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None), **popen_kwargs) - break - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %%s" %% dispcmd) - print(e) - return None, None - else: - if verbose: - print("unable to find command, tried %%s" %% (commands,)) - return None, None - stdout = process.communicate()[0].strip().decode() - if process.returncode != 0: - if verbose: - print("unable to run %%s (error)" %% dispcmd) - print("stdout was %%s" %% stdout) - return None, process.returncode - return stdout, process.returncode - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes both - the project name and a version string. We will also support searching up - two directory levels for an appropriately named parent directory - """ - rootdirs = [] - - for _ in range(3): - dirname = os.path.basename(root) - if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} - rootdirs.append(root) - root = os.path.dirname(root) # up a level - - if verbose: - print("Tried directories %%s but none started with prefix %%s" %% - (str(rootdirs), parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - with open(versionfile_abs, "r") as fobj: - for line in fobj: - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - except OSError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if "refnames" not in keywords: - raise NotThisMethod("Short version file found") - date = keywords.get("date") - if date is not None: - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - - # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant - # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 - # -like" string, which we must then edit to make compliant), because - # it's been around since git-1.5.3, and it's too difficult to - # discover which version we're using, or to work around using an - # older one. - date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = {r.strip() for r in refnames.strip("()").split(",")} - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %%d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = {r for r in refs if re.search(r'\d', r)} - if verbose: - print("discarding '%%s', no digits" %% ",".join(refs - tags)) - if verbose: - print("likely tags: %%s" %% ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - # Filter out refs that exactly match prefix or that don't start - # with a number once the prefix is stripped (mostly a concern - # when prefix is '') - if not re.match(r'\d', r): - continue - if verbose: - print("picking %%s" %% r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - - # GIT_DIR can interfere with correct operation of Versioneer. - # It may be intended to be passed to the Versioneer-versioned project, - # but that should not change where we get our version from. - env = os.environ.copy() - env.pop("GIT_DIR", None) - runner = functools.partial(runner, env=env) - - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) - if rc != 0: - if verbose: - print("Directory %%s not under git control" %% root) - raise NotThisMethod("'git rev-parse --git-dir' returned error") - - MATCH_ARGS = ["--match", "%%s*" %% tag_prefix] if tag_prefix else [] - - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", *MATCH_ARGS], - cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], - cwd=root) - # --abbrev-ref was added in git-1.6.3 - if rc != 0 or branch_name is None: - raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") - branch_name = branch_name.strip() - - if branch_name == "HEAD": - # If we aren't exactly on a branch, pick a branch which represents - # the current commit. If all else fails, we are on a branchless - # commit. - branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) - # --contains was added in git-1.5.4 - if rc != 0 or branches is None: - raise NotThisMethod("'git branch --contains' returned error") - branches = branches.split("\n") - - # Remove the first line if we're running detached - if "(" in branches[0]: - branches.pop(0) - - # Strip off the leading "* " from the list of branches. - branches = [branch[2:] for branch in branches] - if "master" in branches: - branch_name = "master" - elif not branches: - branch_name = None - else: - # Pick the first branch that is returned. Good or bad. - branch_name = branches[0] - - pieces["branch"] = branch_name - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%%s'" - %% describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%%s' doesn't start with prefix '%%s'" - print(fmt %% (full_tag, tag_prefix)) - pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" - %% (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) - pieces["distance"] = int(count_out) # total number of commits - - # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - - return pieces - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_branch(pieces): - """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . - - The ".dev0" means not master branch. Note that .dev0 sorts backwards - (a feature branch will appear "older" than the master branch). - - Exceptions: - 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0" - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def pep440_split_post(ver): - """Split pep440 version string at the post-release segment. - - Returns the release segments before the post-release and the - post-release version number (or -1 if no post-release segment is present). - """ - vc = str.split(ver, ".post") - return vc[0], int(vc[1] or 0) if len(vc) == 2 else None - - -def render_pep440_pre(pieces): - """TAG[.postN.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post0.devDISTANCE - """ - if pieces["closest-tag"]: - if pieces["distance"]: - # update the post release segment - tag_version, post_version = pep440_split_post(pieces["closest-tag"]) - rendered = tag_version - if post_version is not None: - rendered += ".post%%d.dev%%d" %% (post_version+1, pieces["distance"]) - else: - rendered += ".post0.dev%%d" %% (pieces["distance"]) - else: - # no commits, use the tag as the version - rendered = pieces["closest-tag"] - else: - # exception #1 - rendered = "0.post0.dev%%d" %% pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%%s" %% pieces["short"] - else: - # exception #1 - rendered = "0.post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%%s" %% pieces["short"] - return rendered - - -def render_pep440_post_branch(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . - - The ".dev0" means not master branch. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%%d" %% pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%%s" %% pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0.post%%d" %% pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+g%%s" %% pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-branch": - rendered = render_pep440_branch(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-post-branch": - rendered = render_pep440_post_branch(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%%s'" %% style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} - - -def get_versions(): - """Get version information or return default if unable to do so.""" - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded keywords. - - cfg = get_config() - verbose = cfg.verbose - - try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) - except NotThisMethod: - pass - - try: - root = os.path.realpath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for _ in cfg.versionfile_source.split('/'): - root = os.path.dirname(root) - except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} - - try: - pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) - return render(pieces, cfg.style) - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - except NotThisMethod: - pass - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} -''' - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - with open(versionfile_abs, "r") as fobj: - for line in fobj: - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - except OSError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if "refnames" not in keywords: - raise NotThisMethod("Short version file found") - date = keywords.get("date") - if date is not None: - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - - # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant - # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 - # -like" string, which we must then edit to make compliant), because - # it's been around since git-1.5.3, and it's too difficult to - # discover which version we're using, or to work around using an - # older one. - date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = {r.strip() for r in refnames.strip("()").split(",")} - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = {r for r in refs if re.search(r'\d', r)} - if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - # Filter out refs that exactly match prefix or that don't start - # with a number once the prefix is stripped (mostly a concern - # when prefix is '') - if not re.match(r'\d', r): - continue - if verbose: - print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - - # GIT_DIR can interfere with correct operation of Versioneer. - # It may be intended to be passed to the Versioneer-versioned project, - # but that should not change where we get our version from. - env = os.environ.copy() - env.pop("GIT_DIR", None) - runner = functools.partial(runner, env=env) - - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) - if rc != 0: - if verbose: - print("Directory %s not under git control" % root) - raise NotThisMethod("'git rev-parse --git-dir' returned error") - - MATCH_ARGS = ["--match", "%s*" % tag_prefix] if tag_prefix else [] - - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", *MATCH_ARGS], - cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], - cwd=root) - # --abbrev-ref was added in git-1.6.3 - if rc != 0 or branch_name is None: - raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") - branch_name = branch_name.strip() - - if branch_name == "HEAD": - # If we aren't exactly on a branch, pick a branch which represents - # the current commit. If all else fails, we are on a branchless - # commit. - branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) - # --contains was added in git-1.5.4 - if rc != 0 or branches is None: - raise NotThisMethod("'git branch --contains' returned error") - branches = branches.split("\n") - - # Remove the first line if we're running detached - if "(" in branches[0]: - branches.pop(0) - - # Strip off the leading "* " from the list of branches. - branches = [branch[2:] for branch in branches] - if "master" in branches: - branch_name = "master" - elif not branches: - branch_name = None - else: - # Pick the first branch that is returned. Good or bad. - branch_name = branches[0] - - pieces["branch"] = branch_name - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%s' doesn't start with prefix '%s'" - print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) - pieces["distance"] = int(count_out) # total number of commits - - # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() - # Use only the last line. Previous lines may contain GPG signature - # information. - date = date.splitlines()[-1] - pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - - return pieces - - -def do_vcs_install(manifest_in, versionfile_source, ipy): - """Git-specific installation logic for Versioneer. - - For Git, this means creating/changing .gitattributes to mark _version.py - for export-subst keyword substitution. - """ - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - files = [manifest_in, versionfile_source] - if ipy: - files.append(ipy) - try: - my_path = __file__ - if my_path.endswith(".pyc") or my_path.endswith(".pyo"): - my_path = os.path.splitext(my_path)[0] + ".py" - versioneer_file = os.path.relpath(my_path) - except NameError: - versioneer_file = "versioneer.py" - files.append(versioneer_file) - present = False - try: - with open(".gitattributes", "r") as fobj: - for line in fobj: - if line.strip().startswith(versionfile_source): - if "export-subst" in line.strip().split()[1:]: - present = True - break - except OSError: - pass - if not present: - with open(".gitattributes", "a+") as fobj: - fobj.write(f"{versionfile_source} export-subst\n") - files.append(".gitattributes") - run_command(GITS, ["add", "--"] + files) - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes both - the project name and a version string. We will also support searching up - two directory levels for an appropriately named parent directory - """ - rootdirs = [] - - for _ in range(3): - dirname = os.path.basename(root) - if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} - rootdirs.append(root) - root = os.path.dirname(root) # up a level - - if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - - -SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.22) from -# revision-control system data, or from the parent directory name of an -# unpacked source archive. Distribution tarballs contain a pre-generated copy -# of this file. - -import json - -version_json = ''' -%s -''' # END VERSION_JSON - - -def get_versions(): - return json.loads(version_json) -""" - - -def versions_from_file(filename): - """Try to determine the version from _version.py if present.""" - try: - with open(filename) as f: - contents = f.read() - except OSError: - raise NotThisMethod("unable to read _version.py") - mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", - contents, re.M | re.S) - if not mo: - mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", - contents, re.M | re.S) - if not mo: - raise NotThisMethod("no version_json in _version.py") - return json.loads(mo.group(1)) - - -def write_to_version_file(filename, versions): - """Write the given version number to the given _version.py file.""" - os.unlink(filename) - contents = json.dumps(versions, sort_keys=True, - indent=1, separators=(",", ": ")) - with open(filename, "w") as f: - f.write(SHORT_VERSION_PY % contents) - - print("set %s to '%s'" % (filename, versions["version"])) - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_branch(pieces): - """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . - - The ".dev0" means not master branch. Note that .dev0 sorts backwards - (a feature branch will appear "older" than the master branch). - - Exceptions: - 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0" - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def pep440_split_post(ver): - """Split pep440 version string at the post-release segment. - - Returns the release segments before the post-release and the - post-release version number (or -1 if no post-release segment is present). - """ - vc = str.split(ver, ".post") - return vc[0], int(vc[1] or 0) if len(vc) == 2 else None - - -def render_pep440_pre(pieces): - """TAG[.postN.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post0.devDISTANCE - """ - if pieces["closest-tag"]: - if pieces["distance"]: - # update the post release segment - tag_version, post_version = pep440_split_post(pieces["closest-tag"]) - rendered = tag_version - if post_version is not None: - rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) - else: - rendered += ".post0.dev%d" % (pieces["distance"]) - else: - # no commits, use the tag as the version - rendered = pieces["closest-tag"] - else: - # exception #1 - rendered = "0.post0.dev%d" % pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - return rendered - - -def render_pep440_post_branch(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . - - The ".dev0" means not master branch. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-branch": - rendered = render_pep440_branch(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-post-branch": - rendered = render_pep440_post_branch(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%s'" % style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} - - -class VersioneerBadRootError(Exception): - """The project root directory is unknown or missing key files.""" - - -def get_versions(verbose=False): - """Get the project version from whatever source is available. - - Returns dict with two keys: 'version' and 'full'. - """ - if "versioneer" in sys.modules: - # see the discussion in cmdclass.py:get_cmdclass() - del sys.modules["versioneer"] - - root = get_root() - cfg = get_config_from_root(root) - - assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" - handlers = HANDLERS.get(cfg.VCS) - assert handlers, "unrecognized VCS '%s'" % cfg.VCS - verbose = verbose or cfg.verbose - assert cfg.versionfile_source is not None, \ - "please set versioneer.versionfile_source" - assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" - - versionfile_abs = os.path.join(root, cfg.versionfile_source) - - # extract version from first of: _version.py, VCS command (e.g. 'git - # describe'), parentdir. This is meant to work for developers using a - # source checkout, for users of a tarball created by 'setup.py sdist', - # and for users of a tarball/zipball created by 'git archive' or github's - # download-from-tag feature or the equivalent in other VCSes. - - get_keywords_f = handlers.get("get_keywords") - from_keywords_f = handlers.get("keywords") - if get_keywords_f and from_keywords_f: - try: - keywords = get_keywords_f(versionfile_abs) - ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) - if verbose: - print("got version from expanded keyword %s" % ver) - return ver - except NotThisMethod: - pass - - try: - ver = versions_from_file(versionfile_abs) - if verbose: - print("got version from file %s %s" % (versionfile_abs, ver)) - return ver - except NotThisMethod: - pass - - from_vcs_f = handlers.get("pieces_from_vcs") - if from_vcs_f: - try: - pieces = from_vcs_f(cfg.tag_prefix, root, verbose) - ver = render(pieces, cfg.style) - if verbose: - print("got version from VCS %s" % ver) - return ver - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - if verbose: - print("got version from parentdir %s" % ver) - return ver - except NotThisMethod: - pass - - if verbose: - print("unable to compute version") - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, "error": "unable to compute version", - "date": None} - - -def get_version(): - """Get the short version string for this project.""" - return get_versions()["version"] - - -def get_cmdclass(cmdclass=None): - """Get the custom setuptools/distutils subclasses used by Versioneer. - - If the package uses a different cmdclass (e.g. one from numpy), it - should be provide as an argument. - """ - if "versioneer" in sys.modules: - del sys.modules["versioneer"] - # this fixes the "python setup.py develop" case (also 'install' and - # 'easy_install .'), in which subdependencies of the main project are - # built (using setup.py bdist_egg) in the same python process. Assume - # a main project A and a dependency B, which use different versions - # of Versioneer. A's setup.py imports A's Versioneer, leaving it in - # sys.modules by the time B's setup.py is executed, causing B to run - # with the wrong versioneer. Setuptools wraps the sub-dep builds in a - # sandbox that restores sys.modules to it's pre-build state, so the - # parent is protected against the child's "import versioneer". By - # removing ourselves from sys.modules here, before the child build - # happens, we protect the child from the parent's versioneer too. - # Also see https://github.com/python-versioneer/python-versioneer/issues/52 - - cmds = {} if cmdclass is None else cmdclass.copy() - - # we add "version" to both distutils and setuptools - try: - from setuptools import Command - except ImportError: - from distutils.core import Command - - class cmd_version(Command): - description = "report generated version string" - user_options = [] - boolean_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - vers = get_versions(verbose=True) - print("Version: %s" % vers["version"]) - print(" full-revisionid: %s" % vers.get("full-revisionid")) - print(" dirty: %s" % vers.get("dirty")) - print(" date: %s" % vers.get("date")) - if vers["error"]: - print(" error: %s" % vers["error"]) - cmds["version"] = cmd_version - - # we override "build_py" in both distutils and setuptools - # - # most invocation pathways end up running build_py: - # distutils/build -> build_py - # distutils/install -> distutils/build ->.. - # setuptools/bdist_wheel -> distutils/install ->.. - # setuptools/bdist_egg -> distutils/install_lib -> build_py - # setuptools/install -> bdist_egg ->.. - # setuptools/develop -> ? - # pip install: - # copies source tree to a tempdir before running egg_info/etc - # if .git isn't copied too, 'git describe' will fail - # then does setup.py bdist_wheel, or sometimes setup.py install - # setup.py egg_info -> ? - - # we override different "build_py" commands for both environments - if 'build_py' in cmds: - _build_py = cmds['build_py'] - elif "setuptools" in sys.modules: - from setuptools.command.build_py import build_py as _build_py - else: - from distutils.command.build_py import build_py as _build_py - - class cmd_build_py(_build_py): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - _build_py.run(self) - # now locate _version.py in the new build/ directory and replace - # it with an updated value - if cfg.versionfile_build: - target_versionfile = os.path.join(self.build_lib, - cfg.versionfile_build) - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - cmds["build_py"] = cmd_build_py - - if 'build_ext' in cmds: - _build_ext = cmds['build_ext'] - elif "setuptools" in sys.modules: - from setuptools.command.build_ext import build_ext as _build_ext - else: - from distutils.command.build_ext import build_ext as _build_ext - - class cmd_build_ext(_build_ext): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - _build_ext.run(self) - if self.inplace: - # build_ext --inplace will only build extensions in - # build/lib<..> dir with no _version.py to write to. - # As in place builds will already have a _version.py - # in the module dir, we do not need to write one. - return - # now locate _version.py in the new build/ directory and replace - # it with an updated value - target_versionfile = os.path.join(self.build_lib, - cfg.versionfile_build) - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - cmds["build_ext"] = cmd_build_ext - - if "cx_Freeze" in sys.modules: # cx_freeze enabled? - from cx_Freeze.dist import build_exe as _build_exe - # nczeczulin reports that py2exe won't like the pep440-style string - # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. - # setup(console=[{ - # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION - # "product_version": versioneer.get_version(), - # ... - - class cmd_build_exe(_build_exe): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - target_versionfile = cfg.versionfile_source - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - - _build_exe.run(self) - os.unlink(target_versionfile) - with open(cfg.versionfile_source, "w") as f: - LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % - {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - cmds["build_exe"] = cmd_build_exe - del cmds["build_py"] - - if 'py2exe' in sys.modules: # py2exe enabled? - from py2exe.distutils_buildexe import py2exe as _py2exe - - class cmd_py2exe(_py2exe): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - target_versionfile = cfg.versionfile_source - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - - _py2exe.run(self) - os.unlink(target_versionfile) - with open(cfg.versionfile_source, "w") as f: - LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % - {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - cmds["py2exe"] = cmd_py2exe - - # we override different "sdist" commands for both environments - if 'sdist' in cmds: - _sdist = cmds['sdist'] - elif "setuptools" in sys.modules: - from setuptools.command.sdist import sdist as _sdist - else: - from distutils.command.sdist import sdist as _sdist - - class cmd_sdist(_sdist): - def run(self): - versions = get_versions() - self._versioneer_generated_versions = versions - # unless we update this, the command will keep using the old - # version - self.distribution.metadata.version = versions["version"] - return _sdist.run(self) - - def make_release_tree(self, base_dir, files): - root = get_root() - cfg = get_config_from_root(root) - _sdist.make_release_tree(self, base_dir, files) - # now locate _version.py in the new base_dir directory - # (remembering that it may be a hardlink) and replace it with an - # updated value - target_versionfile = os.path.join(base_dir, cfg.versionfile_source) - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, - self._versioneer_generated_versions) - cmds["sdist"] = cmd_sdist - - return cmds - - -CONFIG_ERROR = """ -setup.cfg is missing the necessary Versioneer configuration. You need -a section like: - - [versioneer] - VCS = git - style = pep440 - versionfile_source = src/myproject/_version.py - versionfile_build = myproject/_version.py - tag_prefix = - parentdir_prefix = myproject- - -You will also need to edit your setup.py to use the results: - - import versioneer - setup(version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), ...) - -Please read the docstring in ./versioneer.py for configuration instructions, -edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. -""" - -SAMPLE_CONFIG = """ -# See the docstring in versioneer.py for instructions. Note that you must -# re-run 'versioneer.py setup' after changing this section, and commit the -# resulting files. - -[versioneer] -#VCS = git -#style = pep440 -#versionfile_source = -#versionfile_build = -#tag_prefix = -#parentdir_prefix = - -""" - -OLD_SNIPPET = """ -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions -""" - -INIT_PY_SNIPPET = """ -from . import {0} -__version__ = {0}.get_versions()['version'] -""" - - -def do_setup(): - """Do main VCS-independent setup function for installing Versioneer.""" - root = get_root() - try: - cfg = get_config_from_root(root) - except (OSError, configparser.NoSectionError, - configparser.NoOptionError) as e: - if isinstance(e, (OSError, configparser.NoSectionError)): - print("Adding sample versioneer config to setup.cfg", - file=sys.stderr) - with open(os.path.join(root, "setup.cfg"), "a") as f: - f.write(SAMPLE_CONFIG) - print(CONFIG_ERROR, file=sys.stderr) - return 1 - - print(" creating %s" % cfg.versionfile_source) - with open(cfg.versionfile_source, "w") as f: - LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - - ipy = os.path.join(os.path.dirname(cfg.versionfile_source), - "__init__.py") - if os.path.exists(ipy): - try: - with open(ipy, "r") as f: - old = f.read() - except OSError: - old = "" - module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] - snippet = INIT_PY_SNIPPET.format(module) - if OLD_SNIPPET in old: - print(" replacing boilerplate in %s" % ipy) - with open(ipy, "w") as f: - f.write(old.replace(OLD_SNIPPET, snippet)) - elif snippet not in old: - print(" appending to %s" % ipy) - with open(ipy, "a") as f: - f.write(snippet) - else: - print(" %s unmodified" % ipy) - else: - print(" %s doesn't exist, ok" % ipy) - ipy = None - - # Make sure both the top-level "versioneer.py" and versionfile_source - # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so - # they'll be copied into source distributions. Pip won't be able to - # install the package without this. - manifest_in = os.path.join(root, "MANIFEST.in") - simple_includes = set() - try: - with open(manifest_in, "r") as f: - for line in f: - if line.startswith("include "): - for include in line.split()[1:]: - simple_includes.add(include) - except OSError: - pass - # That doesn't cover everything MANIFEST.in can do - # (http://docs.python.org/2/distutils/sourcedist.html#commands), so - # it might give some false negatives. Appending redundant 'include' - # lines is safe, though. - if "versioneer.py" not in simple_includes: - print(" appending 'versioneer.py' to MANIFEST.in") - with open(manifest_in, "a") as f: - f.write("include versioneer.py\n") - else: - print(" 'versioneer.py' already in MANIFEST.in") - if cfg.versionfile_source not in simple_includes: - print(" appending versionfile_source ('%s') to MANIFEST.in" % - cfg.versionfile_source) - with open(manifest_in, "a") as f: - f.write("include %s\n" % cfg.versionfile_source) - else: - print(" versionfile_source already in MANIFEST.in") - - # Make VCS-specific changes. For git, this means creating/changing - # .gitattributes to mark _version.py for export-subst keyword - # substitution. - do_vcs_install(manifest_in, cfg.versionfile_source, ipy) - return 0 - - -def scan_setup_py(): - """Validate the contents of setup.py against Versioneer's expectations.""" - found = set() - setters = False - errors = 0 - with open("setup.py", "r") as f: - for line in f.readlines(): - if "import versioneer" in line: - found.add("import") - if "versioneer.get_cmdclass()" in line: - found.add("cmdclass") - if "versioneer.get_version()" in line: - found.add("get_version") - if "versioneer.VCS" in line: - setters = True - if "versioneer.versionfile_source" in line: - setters = True - if len(found) != 3: - print("") - print("Your setup.py appears to be missing some important items") - print("(but I might be wrong). Please make sure it has something") - print("roughly like the following:") - print("") - print(" import versioneer") - print(" setup( version=versioneer.get_version(),") - print(" cmdclass=versioneer.get_cmdclass(), ...)") - print("") - errors += 1 - if setters: - print("You should remove lines like 'versioneer.VCS = ' and") - print("'versioneer.versionfile_source = ' . This configuration") - print("now lives in setup.cfg, and should be removed from setup.py") - print("") - errors += 1 - return errors - - -if __name__ == "__main__": - cmd = sys.argv[1] - if cmd == "setup": - errors = do_setup() - errors += scan_setup_py() - if errors: - sys.exit(1) From e8cc1c07cc39d14f466564e33dafc69089785bbc Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Thu, 12 Mar 2026 17:31:15 -0400 Subject: [PATCH 2/9] docs: improve build instructions in CONTRIBUTING.md - Add detailed prerequisites installation steps - Document actual build process matching CI workflows - Include cibuildwheel usage for wheel building - Add troubleshooting section for common build issues - Clarify FILEPATTERN_DEP_DIR usage and local_install directory - Add platform-specific notes for macOS builds --- CONTRIBUTING.md | 154 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 147 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed356181..a8ca1107 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -136,32 +136,95 @@ make test ## Building Locally -### Python Package +### Prerequisites + +First, install the required dependencies (pybind11): + +```bash +# Install prerequisites to local_install directory +bash ci-utils/install_prereq_linux.sh + +# On Windows, use: +# ci-utils\install_prereq_win.bat +``` + +This creates a `local_install` directory with pybind11 headers and CMake configuration. + +### Python Package (Development Mode) + +For local development and testing: ```bash -# Install build dependencies -pip install build setuptools-scm +# Install in editable mode with prerequisites +pip install -e . + +# The build will automatically: +# - Detect version from git using setuptools-scm +# - Build C++ extensions using CMake +# - Link against local_install dependencies +``` + +### Python Package (Wheel Build) + +To build distributable wheels (matching CI process): + +```bash +# Install prerequisites first +bash ci-utils/install_prereq_linux.sh + +# Install build tools +pip install setuptools-scm cibuildwheel + +# Update version files (optional, happens automatically in CI) +python scripts/update_versions.py -# Build wheel +# Build wheel for current Python version +pip install build python -m build -# Install locally +# OR use cibuildwheel to build for multiple Python versions (like CI) +export FILEPATTERN_DEP_DIR="$(pwd)/local_install" +python -m cibuildwheel --output-dir dist + +# Install the wheel pip install dist/*.whl ``` +**Note:** The `FILEPATTERN_DEP_DIR` environment variable tells the build where to find pybind11. + ### C++ Library ```bash +# Install prerequisites first +bash ci-utils/install_prereq_linux.sh + +# Build C++ library cd src/filepattern/cpp mkdir build && cd build -cmake .. -make + +# Configure with prerequisites +cmake -DCMAKE_PREFIX_PATH=$(pwd)/../../../../local_install \ + -DCMAKE_INSTALL_PREFIX=$(pwd)/../../../../local_install \ + .. + +# Build +make -j4 + +# Install (optional) +make install ``` ### Java Package ```bash +# Update version files first (if not already done) +python scripts/update_versions.py + +# Build with Maven mvn clean package + +# Or install to local Maven repository +mvn clean install ``` ## Version Management @@ -178,6 +241,83 @@ To manually update version files (normally not needed): python scripts/update_versions.py ``` +## Troubleshooting Build Issues + +### "pybind11 not found" errors + +**Problem:** CMake can't find pybind11 headers + +**Solution:** +```bash +# Make sure prerequisites are installed +bash ci-utils/install_prereq_linux.sh + +# Set environment variable for the build +export FILEPATTERN_DEP_DIR="$(pwd)/local_install" + +# Or pass to CMake directly +cmake -DCMAKE_PREFIX_PATH=$(pwd)/local_install .. +``` + +### "VERSION file not found" during CMake + +**Problem:** CMake can't find the VERSION file + +**Solution:** +```bash +# Generate VERSION file +python scripts/update_versions.py + +# Or create manually with current version +echo "2.1.4" > VERSION +``` + +### setuptools-scm version detection fails + +**Problem:** `version = '0.1.dev0'` or version detection error + +**Solution:** +```bash +# Ensure you're in a git repository +git status + +# Ensure git tags exist +git tag -l + +# Fetch all tags if cloning +git fetch --tags + +# If no tags exist, create one +git tag v2.1.4 +``` + +### Build fails on macOS with library linking errors + +**Problem:** Libraries not found during wheel repair + +**Solution:** +```bash +# Set MACOSX_DEPLOYMENT_TARGET +export MACOSX_DEPLOYMENT_TARGET="10.15" + +# Set library paths +export REPAIR_LIBRARY_PATH="$(pwd)/local_install/lib:$(pwd)/local_install/lib64" +export DYLD_LIBRARY_PATH="$REPAIR_LIBRARY_PATH" +``` + +### Tests fail during cibuildwheel + +**Problem:** Tests can't import the package + +**Solution:** Tests run inside cibuildwheel automatically. For local testing: +```bash +# Install the built wheel first +pip install dist/*.whl + +# Run tests +pytest tests/ +``` + ## Questions? If you have questions or need help, please: From e5cbe7bade17c25ed53aedd1b7e2608f8348bfa7 Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Thu, 12 Mar 2026 17:34:43 -0400 Subject: [PATCH 3/9] docs: remove migration doc --- MIGRATION_TO_SETUPTOOLS_SCM.md | 254 --------------------------------- 1 file changed, 254 deletions(-) delete mode 100644 MIGRATION_TO_SETUPTOOLS_SCM.md diff --git a/MIGRATION_TO_SETUPTOOLS_SCM.md b/MIGRATION_TO_SETUPTOOLS_SCM.md deleted file mode 100644 index 7c518ad6..00000000 --- a/MIGRATION_TO_SETUPTOOLS_SCM.md +++ /dev/null @@ -1,254 +0,0 @@ -# Migration to setuptools-scm and Automated Releases - -This document describes the migration from versioneer to setuptools-scm with automated releases via release-please. - -## What Changed - -### Version Management System - -**Before:** Versioneer -- Manual version updates in 3 files (pom.xml, version.h, git tags) -- Complex generated code (_version.py, versioneer.py) -- Unclear development versions (e.g., "0.post0.dev176") - -**After:** setuptools-scm + release-please -- **Single source of truth:** Git tags -- **Automated version syncing** via `scripts/update_versions.py` -- **Clear development versions** (e.g., "2.1.4.dev5+gd94438d") -- **Modern packaging** with pyproject.toml -- **Fully automated releases** via release-please - -### Files Modified - -#### Created/Added: -- `pyproject.toml` - Modern Python packaging configuration -- `scripts/update_versions.py` - Automated version synchronization script -- `.github/workflows/release-please.yml` - Automated release workflow -- `.release-please-manifest.json` - Release tracking -- `release-please-config.json` - Release configuration -- `CONTRIBUTING.md` - Contribution guidelines with conventional commits -- `CHANGELOG.md` - Auto-generated changelog -- `VERSION` - Version file for CMake (auto-generated) - -#### Modified: -- `setup.py` - Simplified to only handle CMake builds -- `setup.cfg` - Removed versioneer config -- `src/filepattern/__init__.py` - Updated version import -- `src/filepattern/cpp/CMakeLists.txt` - Updated version detection -- `.github/workflows/publish_pypi.yml` - Added version sync step -- `.github/workflows/publish_maven.yml` - Added version sync step -- `.github/workflows/build_wheels.yml` - Added version sync step -- `.github/workflows/build_jar.yml` - Added version sync step -- `README.md` - Added contributing and release sections - -#### Deleted: -- `versioneer.py` - No longer needed -- `src/filepattern/_version.py` - Now auto-generated by setuptools-scm -- `src/filepattern/cpp/version.h` - No longer needed (CMake reads from VERSION file) -- `.gitattributes` - Removed versioneer-specific export-subst directive - -### Version File Reduction - -**Before:** 3 version sources to maintain manually -1. `pom.xml` - Java version -2. `version.h` - C++ version -3. Git tags - -**After:** 2 version sources, auto-synchronized -1. Git tags (single source of truth) -2. `VERSION` file (auto-generated for CMake) -3. `pom.xml` (auto-updated by script) - -Note: `version.h` was **eliminated entirely** - it wasn't used by any C++ source files! - -## How It Works Now - -### Development Workflow - -1. Make changes following [Conventional Commits](https://www.conventionalcommits.org/) - - `feat:` for new features → minor version bump - - `fix:` for bug fixes → patch version bump - - `feat!:` or `BREAKING CHANGE:` → major version bump - -2. Push commits to a branch and create a PR - -3. Merge PR to `master` - -### Automated Release Process - -1. **On merge to master:** - - release-please analyzes commit messages - - Creates/updates a "Release PR" with: - - Version bump (based on commit types) - - Updated CHANGELOG.md - - Version updates in tracked files - -2. **When Release PR is merged:** - - Git tag is created automatically (e.g., `v2.2.0`) - - GitHub Release is created - - Version files are synced (VERSION, pom.xml) - - Publishing workflows are triggered - - Artifacts are published to PyPI and Maven Central - -### Version Detection - -**Python:** -- Uses setuptools-scm to read from git tags -- During development: shows versions like "2.1.4.dev5+gd94438d" -- In releases: shows clean versions like "2.1.4" - -**C++ (CMake):** -- Reads from `VERSION` file (auto-generated) -- Falls back to git tags if VERSION file doesn't exist -- Falls back to default version if neither exists - -**Java (Maven):** -- Version in `pom.xml` is auto-updated by `scripts/update_versions.py` -- Script runs automatically in CI/CD workflows - -## Testing the Migration - -### Local Testing - -1. **Test Python version detection:** - ```bash - # Install in development mode - pip install -e . - - # Check version - python -c "import filepattern; print(filepattern.__version__)" - # Should show something like: 2.1.4.dev0+gd94438d - ``` - -2. **Test version sync script:** - ```bash - # Manually run version sync - python scripts/update_versions.py - - # Check VERSION file - cat VERSION - - # Check pom.xml - grep -A2 'filepattern' pom.xml - ``` - -3. **Test CMake version detection:** - ```bash - cd src/filepattern/cpp - mkdir build && cd build - cmake .. - # Should see: "Building libfilepattern 2.1.4 (from VERSION file)" - ``` - -4. **Test building:** - ```bash - # Python wheel - pip install build - python -m build - - # Java package - mvn clean package - ``` - -### CI/CD Testing - -1. **Test release-please workflow:** - - Make a commit with conventional format: `feat: add test feature` - - Push to a branch and create PR to master - - Merge the PR - - Check that release-please creates a "Release PR" - - Review the Release PR (check version bump, changelog) - - Merge the Release PR - - Verify GitHub Release is created - - Verify publishing workflows trigger - -2. **Test publish workflows:** - - After release is created, check GitHub Actions - - Verify PyPI publishing succeeds - - Verify Maven Central publishing succeeds - -## Rollback Plan - -If issues arise, you can temporarily revert: - -1. **Revert the migration:** - ```bash - git revert - ``` - -2. **Manual version management (temporary):** - - Update `pom.xml` manually - - Create git tags manually: `git tag v2.1.5` - - Create GitHub Releases manually - -3. **Disable release-please:** - - Disable or delete `.github/workflows/release-please.yml` - - Continue with manual release process - -## Benefits of This Migration - -✅ **No more manual version updates** - Everything is automated -✅ **Single source of truth** - Git tags drive all versioning -✅ **Clear development versions** - Easy to identify dev vs release builds -✅ **Automated changelog** - Generated from commit messages -✅ **Consistent commit style** - Conventional commits improve readability -✅ **Simplified releases** - Just merge a PR, everything else is automatic -✅ **Reduced maintenance** - Fewer manual steps means fewer errors -✅ **Modern tooling** - Using actively maintained, industry-standard tools - -## Troubleshooting - -### "Version not found" errors during build - -**Cause:** VERSION file not generated -**Solution:** Run `python scripts/update_versions.py` before building - -### setuptools-scm can't determine version - -**Cause:** Not a git repository or no tags present -**Solution:** -```bash -# Make sure you're in a git repo -git status - -# Check for tags -git tag -l - -# If no tags, create one -git tag v2.1.4 -``` - -### CMake can't find version - -**Cause:** VERSION file doesn't exist and git is not available -**Solution:** -```bash -# Generate VERSION file -python scripts/update_versions.py - -# Or create manually -echo "2.1.4" > VERSION -``` - -### Release PR not being created - -**Cause:** Commits don't follow conventional format -**Solution:** Use proper commit message format: -```bash -git commit -m "feat: add new feature" -git commit -m "fix: resolve bug" -``` - -## Additional Resources - -- [Conventional Commits Specification](https://www.conventionalcommits.org/) -- [setuptools-scm Documentation](https://setuptools-scm.readthedocs.io/) -- [release-please Documentation](https://github.com/googleapis/release-please) -- [CONTRIBUTING.md](CONTRIBUTING.md) - Full contribution guidelines - -## Questions or Issues? - -If you encounter problems with the migration: -1. Check this document's Troubleshooting section -2. Review the [CONTRIBUTING.md](CONTRIBUTING.md) guide -3. Open an issue on GitHub with details about the problem From d3696fce8953a0d55a810b5ada9434bf6db5e035 Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Fri, 13 Mar 2026 14:39:47 -0400 Subject: [PATCH 4/9] fix: resolve Windows encoding and git metadata issues - Replace Unicode checkmarks with ASCII [OK] for Windows cp1252 compatibility - Add explicit git tag fetching in all workflows (fetch-tags: true) - Add git fetch --force --tags step to ensure tags are available - Improve version detection fallback logic in update_versions.py - Add support for GITHUB_REF_NAME in publish workflows for release tags - Handle edge cases where setuptools-scm can't detect version Fixes: - UnicodeEncodeError on Windows when running update_versions.py - setuptools-scm 'not a git repository' errors in CI - Missing git tags during checkout in workflows --- .github/workflows/build_jar.yml | 4 +++ .github/workflows/build_wheels.yml | 10 ++++++++ .github/workflows/publish_pypi.yml | 28 +++++++++++++++++++-- scripts/update_versions.py | 40 ++++++++++++++++++++++++------ 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build_jar.yml b/.github/workflows/build_jar.yml index 5516592f..7895dd81 100644 --- a/.github/workflows/build_jar.yml +++ b/.github/workflows/build_jar.yml @@ -17,6 +17,10 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 # Fetch all history for version detection + fetch-tags: true # Explicitly fetch tags + + - name: Fetch git tags + run: git fetch --force --tags - name: Set up Python (for version script) uses: actions/setup-python@v5 diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index c9e4457a..44c8d0ff 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -25,6 +25,11 @@ jobs: name: Check out with: fetch-depth: 0 # Fetch all history for setuptools-scm + fetch-tags: true # Explicitly fetch tags + + - name: Fetch git tags + run: git fetch --force --tags + shell: bash - uses: ilammy/msvc-dev-cmd@v1 name: Add MSVS Path @@ -92,6 +97,11 @@ jobs: name: Check out with: fetch-depth: 0 # Fetch all history for setuptools-scm + fetch-tags: true # Explicitly fetch tags + + - name: Fetch git tags + run: git fetch --force --tags + shell: bash - uses: ilammy/msvc-dev-cmd@v1 name: Add MSVS Path diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 65828372..7704dbd7 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -23,6 +23,11 @@ jobs: name: Check out with: fetch-depth: 0 # Fetch all history for setuptools-scm + fetch-tags: true # Explicitly fetch tags + + - name: Fetch git tags + run: git fetch --force --tags + shell: bash - uses: ilammy/msvc-dev-cmd@v1 name: Add MSVS Path @@ -35,7 +40,14 @@ jobs: - name: Update version files run: | python -m pip install setuptools-scm - python scripts/update_versions.py + # Extract version from tag name if this is a release + if [ -n "$GITHUB_REF_NAME" ] && [[ "$GITHUB_REF_NAME" == v* ]]; then + VERSION="${GITHUB_REF_NAME#v}" + echo "Using release version: $VERSION" + python scripts/update_versions.py --version "$VERSION" + else + python scripts/update_versions.py + fi shell: bash - name: Install cibuildwheel @@ -93,6 +105,11 @@ jobs: name: Check out with: fetch-depth: 0 # Fetch all history for setuptools-scm + fetch-tags: true # Explicitly fetch tags + + - name: Fetch git tags + run: git fetch --force --tags + shell: bash - uses: ilammy/msvc-dev-cmd@v1 name: Add MSVS Path @@ -105,7 +122,14 @@ jobs: - name: Update version files run: | python -m pip install setuptools-scm - python scripts/update_versions.py + # Extract version from tag name if this is a release + if [ -n "$GITHUB_REF_NAME" ] && [[ "$GITHUB_REF_NAME" == v* ]]; then + VERSION="${GITHUB_REF_NAME#v}" + echo "Using release version: $VERSION" + python scripts/update_versions.py --version "$VERSION" + else + python scripts/update_versions.py + fi shell: bash - name: Install cibuildwheel diff --git a/scripts/update_versions.py b/scripts/update_versions.py index 229f04ad..82def49a 100755 --- a/scripts/update_versions.py +++ b/scripts/update_versions.py @@ -22,9 +22,13 @@ def get_version_from_scm(): """Get version from setuptools-scm.""" try: from setuptools_scm import get_version - version = get_version(root='..') + version = get_version(root='..', fallback_version='0.0.0') # Extract base version (remove dev/local version identifiers) base_version = version.split('+')[0].split('.dev')[0] + # Don't return if it's the fallback version + if base_version == '0.0.0': + print("Warning: setuptools-scm returned fallback version, trying git directly", file=sys.stderr) + return None return base_version except ImportError: print("Warning: setuptools-scm not installed, trying git directly", file=sys.stderr) @@ -37,28 +41,50 @@ def get_version_from_scm(): def get_version_from_git(): """Get version from git tags as fallback.""" import subprocess + + # Try to get the latest tag try: result = subprocess.run( ['git', 'describe', '--tags', '--abbrev=0'], capture_output=True, text=True, - check=True + check=True, + cwd=Path(__file__).parent.parent ) version = result.stdout.strip() # Remove 'v' prefix if present if version.startswith('v'): version = version[1:] return version + except subprocess.CalledProcessError: + # If no tags, try to get from git tag list + try: + result = subprocess.run( + ['git', 'tag', '--sort=-version:refname'], + capture_output=True, + text=True, + check=True, + cwd=Path(__file__).parent.parent + ) + tags = result.stdout.strip().split('\n') + if tags and tags[0]: + version = tags[0] + if version.startswith('v'): + version = version[1:] + return version + except Exception: + pass except Exception as e: - print(f"Error: Could not get version from git: {e}", file=sys.stderr) - return None + print(f"Warning: Could not get version from git: {e}", file=sys.stderr) + + return None def write_version_file(version, root_dir): """Write VERSION file for CMake.""" version_file = root_dir / 'VERSION' version_file.write_text(version + '\n') - print(f"✓ Updated {version_file} to {version}") + print(f"[OK] Updated {version_file} to {version}") def update_pom_xml(version, root_dir): @@ -81,7 +107,7 @@ def update_pom_xml(version, root_dir): return pom_file.write_text(new_content) - print(f"✓ Updated {pom_file} to {version}") + print(f"[OK] Updated {pom_file} to {version}") def main(): @@ -116,7 +142,7 @@ def main(): write_version_file(version, root_dir) update_pom_xml(version, root_dir) - print(f"\n✓ All version files updated to {version}") + print(f"\n[OK] All version files updated to {version}") if __name__ == '__main__': From 643a30ff9a66563685f6f7f74aaedca41417ec2e Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Fri, 13 Mar 2026 14:53:36 -0400 Subject: [PATCH 5/9] fix: add cstddef include for ptrdiff_t on macOS Fixes compilation error on macOS with conda environments where ptrdiff_t is not resolved. This is required for libc++ on certain macOS SDK versions. Error: reference to unresolved using declaration for ::ptrdiff_t --- src/filepattern/cpp/pattern.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/filepattern/cpp/pattern.hpp b/src/filepattern/cpp/pattern.hpp index ca75e64b..ccb88b0a 100644 --- a/src/filepattern/cpp/pattern.hpp +++ b/src/filepattern/cpp/pattern.hpp @@ -10,6 +10,7 @@ */ #pragma once +#include #include #include #include From aac9475d730f790b19264a49781d76308b8a2dd6 Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Fri, 13 Mar 2026 15:45:31 -0400 Subject: [PATCH 6/9] fix: add comprehensive workaround for conda libc++ on macOS The conda-provided libc++ on macOS has compatibility issues with ptrdiff_t and other standard library types due to _LIBCPP_USING_IF_EXISTS failing to resolve types from the global namespace. Changes: - Include stddef.h (C header) before C++ headers to ensure types are defined - Add _LIBCPP_DISABLE_AVAILABILITY flag on macOS to disable availability checks that can conflict with conda environments This resolves the 'reference to unresolved using declaration' error for ptrdiff_t in char_traits.h when building with conda on macOS. References: - https://github.com/conda/conda/issues/11196 - https://github.com/llvm/llvm-project/issues/54233 --- src/filepattern/cpp/CMakeLists.txt | 5 +++++ src/filepattern/cpp/pattern.hpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/filepattern/cpp/CMakeLists.txt b/src/filepattern/cpp/CMakeLists.txt index 0af946aa..6ded35ba 100644 --- a/src/filepattern/cpp/CMakeLists.txt +++ b/src/filepattern/cpp/CMakeLists.txt @@ -60,6 +60,11 @@ if(JAVA_BINDING) add_compile_definitions(JAVA_BINDING) endif() +# Workaround for conda libc++ compatibility on macOS +if(APPLE) + add_compile_definitions(_LIBCPP_DISABLE_AVAILABILITY) +endif() + #==== Source files set(SOURCE pattern.cpp interface/filepattern.cpp diff --git a/src/filepattern/cpp/pattern.hpp b/src/filepattern/cpp/pattern.hpp index ccb88b0a..0115fc89 100644 --- a/src/filepattern/cpp/pattern.hpp +++ b/src/filepattern/cpp/pattern.hpp @@ -10,6 +10,11 @@ */ #pragma once + +// Workaround for conda libc++ on macOS: include C headers before C++ headers +// to ensure ptrdiff_t and other types are properly defined +#include + #include #include #include From d1680bd4da3cf7fcb3a17f83fa93eac9f81ce012 Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Fri, 13 Mar 2026 16:39:47 -0400 Subject: [PATCH 7/9] fix: rename VERSION to .version to avoid C++ header conflict On macOS's case-insensitive filesystem, the VERSION file conflicts with the C++ standard library header (C++20). When the compiler tries to #include , it picks up our VERSION file instead, causing compilation errors. Solution: Rename VERSION to .version (hidden file with dot prefix) Updated: - CMakeLists.txt: Read from .version instead of VERSION - update_versions.py: Write to .version - release-please.yml: Upload .version as asset - release-please-config.json: Track .version in extra-files This is a known issue on case-insensitive filesystems: https://github.com/llvm/llvm-project/issues/51214 --- .github/workflows/release-please.yml | 4 +- VERSION => .version | 0 local_install/include/pybind11/attr.h | 690 ++++ local_install/include/pybind11/buffer_info.h | 208 ++ local_install/include/pybind11/cast.h | 1704 ++++++++++ local_install/include/pybind11/chrono.h | 225 ++ local_install/include/pybind11/common.h | 2 + local_install/include/pybind11/complex.h | 74 + local_install/include/pybind11/detail/class.h | 743 +++++ .../include/pybind11/detail/common.h | 1255 +++++++ local_install/include/pybind11/detail/descr.h | 171 + local_install/include/pybind11/detail/init.h | 434 +++ .../include/pybind11/detail/internals.h | 656 ++++ .../pybind11/detail/type_caster_base.h | 1177 +++++++ .../include/pybind11/detail/typeid.h | 65 + local_install/include/pybind11/eigen.h | 12 + local_install/include/pybind11/eigen/common.h | 9 + local_install/include/pybind11/eigen/matrix.h | 714 ++++ local_install/include/pybind11/eigen/tensor.h | 516 +++ local_install/include/pybind11/embed.h | 316 ++ local_install/include/pybind11/eval.h | 156 + local_install/include/pybind11/functional.h | 137 + local_install/include/pybind11/gil.h | 239 ++ local_install/include/pybind11/iostream.h | 265 ++ local_install/include/pybind11/numpy.h | 1998 ++++++++++++ local_install/include/pybind11/operators.h | 202 ++ local_install/include/pybind11/options.h | 92 + local_install/include/pybind11/pybind11.h | 2890 +++++++++++++++++ local_install/include/pybind11/pytypes.h | 2557 +++++++++++++++ local_install/include/pybind11/stl.h | 447 +++ .../include/pybind11/stl/filesystem.h | 116 + local_install/include/pybind11/stl_bind.h | 851 +++++ .../pybind11/type_caster_pyobject_ptr.h | 61 + .../cmake/pybind11/FindPythonLibsNew.cmake | 287 ++ .../share/cmake/pybind11/pybind11Common.cmake | 405 +++ .../share/cmake/pybind11/pybind11Config.cmake | 257 ++ .../pybind11/pybind11ConfigVersion.cmake | 48 + .../cmake/pybind11/pybind11NewTools.cmake | 256 ++ .../cmake/pybind11/pybind11Targets.cmake | 107 + .../share/cmake/pybind11/pybind11Tools.cmake | 233 ++ local_install/share/pkgconfig/pybind11.pc | 7 + out.ogv | Bin 0 -> 1176060 bytes pybind11-2.11.1/.appveyor.yml | 35 + pybind11-2.11.1/.clang-format | 38 + pybind11-2.11.1/.clang-tidy | 77 + pybind11-2.11.1/.cmake-format.yaml | 73 + pybind11-2.11.1/.codespell-ignore-lines | 24 + pybind11-2.11.1/.gitattributes | 1 + pybind11-2.11.1/.github/CODEOWNERS | 9 + pybind11-2.11.1/.github/CONTRIBUTING.md | 388 +++ .../.github/ISSUE_TEMPLATE/bug-report.yml | 61 + .../.github/ISSUE_TEMPLATE/config.yml | 8 + pybind11-2.11.1/.github/dependabot.yml | 7 + pybind11-2.11.1/.github/labeler.yml | 8 + pybind11-2.11.1/.github/labeler_merged.yml | 3 + pybind11-2.11.1/.github/matchers/pylint.json | 32 + .../.github/pull_request_template.md | 19 + pybind11-2.11.1/.github/workflows/ci.yml | 1165 +++++++ .../.github/workflows/configure.yml | 92 + pybind11-2.11.1/.github/workflows/format.yml | 60 + pybind11-2.11.1/.github/workflows/labeler.yml | 25 + pybind11-2.11.1/.github/workflows/pip.yml | 114 + .../.github/workflows/upstream.yml | 116 + pybind11-2.11.1/.gitignore | 46 + pybind11-2.11.1/.pre-commit-config.yaml | 153 + pybind11-2.11.1/.readthedocs.yml | 3 + pybind11-2.11.1/CMakeLists.txt | 322 ++ pybind11-2.11.1/LICENSE | 29 + pybind11-2.11.1/MANIFEST.in | 6 + pybind11-2.11.1/README.rst | 180 + pybind11-2.11.1/SECURITY.md | 13 + pybind11-2.11.1/docs/Doxyfile | 21 + pybind11-2.11.1/docs/_static/css/custom.css | 3 + pybind11-2.11.1/docs/advanced/cast/chrono.rst | 81 + pybind11-2.11.1/docs/advanced/cast/custom.rst | 93 + pybind11-2.11.1/docs/advanced/cast/eigen.rst | 310 ++ .../docs/advanced/cast/functional.rst | 109 + pybind11-2.11.1/docs/advanced/cast/index.rst | 43 + .../docs/advanced/cast/overview.rst | 170 + pybind11-2.11.1/docs/advanced/cast/stl.rst | 249 ++ .../docs/advanced/cast/strings.rst | 296 ++ pybind11-2.11.1/docs/advanced/classes.rst | 1335 ++++++++ pybind11-2.11.1/docs/advanced/embedding.rst | 262 ++ pybind11-2.11.1/docs/advanced/exceptions.rst | 401 +++ pybind11-2.11.1/docs/advanced/functions.rst | 614 ++++ pybind11-2.11.1/docs/advanced/misc.rst | 400 +++ pybind11-2.11.1/docs/advanced/pycpp/index.rst | 13 + pybind11-2.11.1/docs/advanced/pycpp/numpy.rst | 455 +++ .../docs/advanced/pycpp/object.rst | 286 ++ .../docs/advanced/pycpp/utilities.rst | 155 + pybind11-2.11.1/docs/advanced/smart_ptrs.rst | 174 + pybind11-2.11.1/docs/basics.rst | 307 ++ pybind11-2.11.1/docs/benchmark.py | 87 + pybind11-2.11.1/docs/benchmark.rst | 95 + pybind11-2.11.1/docs/changelog.rst | 2826 ++++++++++++++++ pybind11-2.11.1/docs/classes.rst | 555 ++++ pybind11-2.11.1/docs/cmake/index.rst | 8 + pybind11-2.11.1/docs/compiling.rst | 641 ++++ pybind11-2.11.1/docs/conf.py | 368 +++ pybind11-2.11.1/docs/faq.rst | 308 ++ pybind11-2.11.1/docs/index.rst | 48 + pybind11-2.11.1/docs/installing.rst | 105 + pybind11-2.11.1/docs/limitations.rst | 72 + pybind11-2.11.1/docs/pybind11-logo.png | Bin 0 -> 61034 bytes .../docs/pybind11_vs_boost_python1.png | Bin 0 -> 44653 bytes .../docs/pybind11_vs_boost_python1.svg | 427 +++ .../docs/pybind11_vs_boost_python2.png | Bin 0 -> 41121 bytes .../docs/pybind11_vs_boost_python2.svg | 427 +++ pybind11-2.11.1/docs/reference.rst | 130 + pybind11-2.11.1/docs/release.rst | 99 + pybind11-2.11.1/docs/requirements.txt | 6 + pybind11-2.11.1/docs/upgrade.rst | 566 ++++ pybind11-2.11.1/include/pybind11/attr.h | 690 ++++ .../include/pybind11/buffer_info.h | 208 ++ pybind11-2.11.1/include/pybind11/cast.h | 1704 ++++++++++ pybind11-2.11.1/include/pybind11/chrono.h | 225 ++ pybind11-2.11.1/include/pybind11/common.h | 2 + pybind11-2.11.1/include/pybind11/complex.h | 74 + .../include/pybind11/detail/class.h | 743 +++++ .../include/pybind11/detail/common.h | 1255 +++++++ .../include/pybind11/detail/descr.h | 171 + .../include/pybind11/detail/init.h | 434 +++ .../include/pybind11/detail/internals.h | 656 ++++ .../pybind11/detail/type_caster_base.h | 1177 +++++++ .../include/pybind11/detail/typeid.h | 65 + pybind11-2.11.1/include/pybind11/eigen.h | 12 + .../include/pybind11/eigen/common.h | 9 + .../include/pybind11/eigen/matrix.h | 714 ++++ .../include/pybind11/eigen/tensor.h | 516 +++ pybind11-2.11.1/include/pybind11/embed.h | 316 ++ pybind11-2.11.1/include/pybind11/eval.h | 156 + pybind11-2.11.1/include/pybind11/functional.h | 137 + pybind11-2.11.1/include/pybind11/gil.h | 239 ++ pybind11-2.11.1/include/pybind11/iostream.h | 265 ++ pybind11-2.11.1/include/pybind11/numpy.h | 1998 ++++++++++++ pybind11-2.11.1/include/pybind11/operators.h | 202 ++ pybind11-2.11.1/include/pybind11/options.h | 92 + pybind11-2.11.1/include/pybind11/pybind11.h | 2890 +++++++++++++++++ pybind11-2.11.1/include/pybind11/pytypes.h | 2557 +++++++++++++++ pybind11-2.11.1/include/pybind11/stl.h | 447 +++ .../include/pybind11/stl/filesystem.h | 116 + pybind11-2.11.1/include/pybind11/stl_bind.h | 851 +++++ .../pybind11/type_caster_pyobject_ptr.h | 61 + pybind11-2.11.1/noxfile.py | 107 + pybind11-2.11.1/pybind11/__init__.py | 17 + pybind11-2.11.1/pybind11/__main__.py | 62 + pybind11-2.11.1/pybind11/_version.py | 12 + pybind11-2.11.1/pybind11/commands.py | 37 + pybind11-2.11.1/pybind11/py.typed | 0 pybind11-2.11.1/pybind11/setup_helpers.py | 498 +++ pybind11-2.11.1/pyproject.toml | 98 + pybind11-2.11.1/setup.cfg | 43 + pybind11-2.11.1/setup.py | 150 + pybind11-2.11.1/tests/CMakeLists.txt | 584 ++++ pybind11-2.11.1/tests/conftest.py | 221 ++ pybind11-2.11.1/tests/constructor_stats.h | 322 ++ .../tests/cross_module_gil_utils.cpp | 108 + ...s_module_interleaved_error_already_set.cpp | 51 + .../tests/eigen_tensor_avoid_stl_array.cpp | 14 + pybind11-2.11.1/tests/env.py | 27 + .../tests/extra_python_package/pytest.ini | 0 .../tests/extra_python_package/test_files.py | 291 ++ .../tests/extra_setuptools/pytest.ini | 0 .../extra_setuptools/test_setuphelper.py | 151 + pybind11-2.11.1/tests/local_bindings.h | 92 + pybind11-2.11.1/tests/object.h | 205 ++ .../tests/pybind11_cross_module_tests.cpp | 149 + pybind11-2.11.1/tests/pybind11_tests.cpp | 123 + pybind11-2.11.1/tests/pybind11_tests.h | 85 + pybind11-2.11.1/tests/pytest.ini | 22 + pybind11-2.11.1/tests/requirements.txt | 9 + pybind11-2.11.1/tests/test_async.cpp | 25 + pybind11-2.11.1/tests/test_async.py | 24 + pybind11-2.11.1/tests/test_buffers.cpp | 259 ++ pybind11-2.11.1/tests/test_buffers.py | 221 ++ .../tests/test_builtin_casters.cpp | 392 +++ pybind11-2.11.1/tests/test_builtin_casters.py | 528 +++ pybind11-2.11.1/tests/test_call_policies.cpp | 115 + pybind11-2.11.1/tests/test_call_policies.py | 247 ++ pybind11-2.11.1/tests/test_callbacks.cpp | 280 ++ pybind11-2.11.1/tests/test_callbacks.py | 218 ++ pybind11-2.11.1/tests/test_chrono.cpp | 81 + pybind11-2.11.1/tests/test_chrono.py | 205 ++ pybind11-2.11.1/tests/test_class.cpp | 657 ++++ pybind11-2.11.1/tests/test_class.py | 485 +++ .../tests/test_cmake_build/CMakeLists.txt | 81 + .../tests/test_cmake_build/embed.cpp | 23 + .../installed_embed/CMakeLists.txt | 28 + .../installed_function/CMakeLists.txt | 39 + .../installed_target/CMakeLists.txt | 46 + .../tests/test_cmake_build/main.cpp | 6 + .../subdirectory_embed/CMakeLists.txt | 41 + .../subdirectory_function/CMakeLists.txt | 35 + .../subdirectory_target/CMakeLists.txt | 41 + pybind11-2.11.1/tests/test_const_name.cpp | 55 + pybind11-2.11.1/tests/test_const_name.py | 29 + .../tests/test_constants_and_functions.cpp | 154 + .../tests/test_constants_and_functions.py | 56 + pybind11-2.11.1/tests/test_copy_move.cpp | 533 +++ pybind11-2.11.1/tests/test_copy_move.py | 132 + .../tests/test_custom_type_casters.cpp | 209 ++ .../tests/test_custom_type_casters.py | 122 + .../tests/test_custom_type_setup.cpp | 41 + .../tests/test_custom_type_setup.py | 48 + .../tests/test_docstring_options.cpp | 141 + .../tests/test_docstring_options.py | 64 + pybind11-2.11.1/tests/test_eigen_matrix.cpp | 428 +++ pybind11-2.11.1/tests/test_eigen_matrix.py | 807 +++++ pybind11-2.11.1/tests/test_eigen_tensor.cpp | 18 + pybind11-2.11.1/tests/test_eigen_tensor.inl | 333 ++ pybind11-2.11.1/tests/test_eigen_tensor.py | 288 ++ .../tests/test_embed/CMakeLists.txt | 47 + pybind11-2.11.1/tests/test_embed/catch.cpp | 43 + .../tests/test_embed/external_module.cpp | 20 + .../tests/test_embed/test_interpreter.cpp | 488 +++ .../tests/test_embed/test_interpreter.py | 14 + .../tests/test_embed/test_trampoline.py | 16 + pybind11-2.11.1/tests/test_enum.cpp | 133 + pybind11-2.11.1/tests/test_enum.py | 266 ++ pybind11-2.11.1/tests/test_eval.cpp | 118 + pybind11-2.11.1/tests/test_eval.py | 50 + pybind11-2.11.1/tests/test_eval_call.py | 4 + pybind11-2.11.1/tests/test_exceptions.cpp | 347 ++ pybind11-2.11.1/tests/test_exceptions.h | 13 + pybind11-2.11.1/tests/test_exceptions.py | 413 +++ .../tests/test_factory_constructors.cpp | 430 +++ .../tests/test_factory_constructors.py | 516 +++ pybind11-2.11.1/tests/test_gil_scoped.cpp | 144 + pybind11-2.11.1/tests/test_gil_scoped.py | 242 ++ pybind11-2.11.1/tests/test_iostream.cpp | 126 + pybind11-2.11.1/tests/test_iostream.py | 291 ++ .../tests/test_kwargs_and_defaults.cpp | 281 ++ .../tests/test_kwargs_and_defaults.py | 389 +++ pybind11-2.11.1/tests/test_local_bindings.cpp | 106 + pybind11-2.11.1/tests/test_local_bindings.py | 257 ++ .../tests/test_methods_and_attributes.cpp | 493 +++ .../tests/test_methods_and_attributes.py | 533 +++ pybind11-2.11.1/tests/test_modules.cpp | 125 + pybind11-2.11.1/tests/test_modules.py | 116 + .../tests/test_multiple_inheritance.cpp | 341 ++ .../tests/test_multiple_inheritance.py | 493 +++ pybind11-2.11.1/tests/test_numpy_array.cpp | 552 ++++ pybind11-2.11.1/tests/test_numpy_array.py | 668 ++++ pybind11-2.11.1/tests/test_numpy_dtypes.cpp | 614 ++++ pybind11-2.11.1/tests/test_numpy_dtypes.py | 440 +++ .../tests/test_numpy_vectorize.cpp | 107 + pybind11-2.11.1/tests/test_numpy_vectorize.py | 266 ++ pybind11-2.11.1/tests/test_opaque_types.cpp | 77 + pybind11-2.11.1/tests/test_opaque_types.py | 58 + .../tests/test_operator_overloading.cpp | 281 ++ .../tests/test_operator_overloading.py | 151 + pybind11-2.11.1/tests/test_pickling.cpp | 194 ++ pybind11-2.11.1/tests/test_pickling.py | 93 + pybind11-2.11.1/tests/test_pytypes.cpp | 823 +++++ pybind11-2.11.1/tests/test_pytypes.py | 898 +++++ .../tests/test_sequences_and_iterators.cpp | 581 ++++ .../tests/test_sequences_and_iterators.py | 252 ++ pybind11-2.11.1/tests/test_smart_ptr.cpp | 470 +++ pybind11-2.11.1/tests/test_smart_ptr.py | 315 ++ pybind11-2.11.1/tests/test_stl.cpp | 551 ++++ pybind11-2.11.1/tests/test_stl.py | 381 +++ pybind11-2.11.1/tests/test_stl_binders.cpp | 196 ++ pybind11-2.11.1/tests/test_stl_binders.py | 355 ++ .../tests/test_tagbased_polymorphic.cpp | 147 + .../tests/test_tagbased_polymorphic.py | 28 + pybind11-2.11.1/tests/test_thread.cpp | 66 + pybind11-2.11.1/tests/test_thread.py | 42 + .../tests/test_type_caster_pyobject_ptr.cpp | 130 + .../tests/test_type_caster_pyobject_ptr.py | 104 + pybind11-2.11.1/tests/test_union.cpp | 22 + pybind11-2.11.1/tests/test_union.py | 8 + .../tests/test_unnamed_namespace_a.cpp | 38 + .../tests/test_unnamed_namespace_a.py | 34 + .../tests/test_unnamed_namespace_b.cpp | 13 + .../tests/test_unnamed_namespace_b.py | 5 + .../tests/test_vector_unique_ptr_member.cpp | 54 + .../tests/test_vector_unique_ptr_member.py | 14 + .../tests/test_virtual_functions.cpp | 592 ++++ .../tests/test_virtual_functions.py | 458 +++ .../tests/valgrind-numpy-scipy.supp | 140 + pybind11-2.11.1/tests/valgrind-python.supp | 117 + pybind11-2.11.1/tools/FindCatch.cmake | 76 + pybind11-2.11.1/tools/FindEigen3.cmake | 86 + pybind11-2.11.1/tools/FindPythonLibsNew.cmake | 287 ++ pybind11-2.11.1/tools/JoinPaths.cmake | 23 + pybind11-2.11.1/tools/check-style.sh | 44 + .../tools/cmake_uninstall.cmake.in | 23 + .../codespell_ignore_lines_from_errors.py | 39 + pybind11-2.11.1/tools/libsize.py | 36 + pybind11-2.11.1/tools/make_changelog.py | 62 + pybind11-2.11.1/tools/pybind11.pc.in | 7 + pybind11-2.11.1/tools/pybind11Common.cmake | 405 +++ pybind11-2.11.1/tools/pybind11Config.cmake.in | 233 ++ pybind11-2.11.1/tools/pybind11NewTools.cmake | 256 ++ pybind11-2.11.1/tools/pybind11Tools.cmake | 233 ++ pybind11-2.11.1/tools/pyproject.toml | 3 + pybind11-2.11.1/tools/setup_global.py.in | 63 + pybind11-2.11.1/tools/setup_main.py.in | 44 + release-please-config.json | 2 +- scripts/update_versions.py | 4 +- src/filepattern/cpp/CMakeLists.txt | 10 +- src/filepattern/cpp/test_build/CMakeCache.txt | 470 +++ .../CMakeFiles/3.25.1/CMakeCCompiler.cmake | 72 + .../CMakeFiles/3.25.1/CMakeCXXCompiler.cmake | 83 + .../3.25.1/CMakeDetermineCompilerABI_C.bin | Bin 0 -> 16032 bytes .../3.25.1/CMakeDetermineCompilerABI_CXX.bin | Bin 0 -> 16048 bytes .../CMakeFiles/3.25.1/CMakeSystem.cmake | 15 + .../3.25.1/CompilerIdC/CMakeCCompilerId.c | 868 +++++ .../CompilerIdCXX/CMakeCXXCompilerId.cpp | 857 +++++ .../CMakeDirectoryInformation.cmake | 16 + .../cpp/test_build/CMakeFiles/Makefile.cmake | 145 + .../cpp/test_build/CMakeFiles/Makefile2 | 129 + .../CMakeFiles/TargetDirectories.txt | 13 + .../test_build/CMakeFiles/cmake.check_cache | 1 + .../filepattern.dir/DependInfo.cmake | 31 + .../CMakeFiles/filepattern.dir/build.make | 303 ++ .../filepattern.dir/cmake_clean.cmake | 35 + .../filepattern.dir/cmake_clean_target.cmake | 3 + .../filepattern.dir/compiler_depend.make | 2 + .../filepattern.dir/compiler_depend.ts | 2 + .../CMakeFiles/filepattern.dir/depend.make | 2 + .../CMakeFiles/filepattern.dir/flags.make | 10 + .../CMakeFiles/filepattern.dir/link.txt | 2 + .../CMakeFiles/filepattern.dir/progress.make | 15 + .../cpp/test_build/CMakeFiles/progress.marks | 1 + src/filepattern/cpp/test_build/Makefile | 554 ++++ .../cpp/test_build/cmake_install.cmake | 59 + .../test_build/include/filepattern_export.h | 42 + .../CMakeDirectoryInformation.cmake | 16 + .../filepattern-static-targets-noconfig.cmake | 19 + .../filepattern-static-targets.cmake | 108 + .../packaging/CMakeFiles/progress.marks | 1 + .../cpp/test_build/packaging/Makefile | 189 ++ .../test_build/packaging/cmake_install.cmake | 85 + .../packaging/filepatternConfigVersion.cmake | 70 + src/filepattern/libfilepattern.so.2 | 1 + src/filepattern/libfilepattern.so.2.1.4 | Bin 0 -> 912368 bytes test_fp.py | 8 + v2.11.1.zip | Bin 0 -> 867680 bytes 339 files changed, 89576 insertions(+), 10 deletions(-) rename VERSION => .version (100%) create mode 100644 local_install/include/pybind11/attr.h create mode 100644 local_install/include/pybind11/buffer_info.h create mode 100644 local_install/include/pybind11/cast.h create mode 100644 local_install/include/pybind11/chrono.h create mode 100644 local_install/include/pybind11/common.h create mode 100644 local_install/include/pybind11/complex.h create mode 100644 local_install/include/pybind11/detail/class.h create mode 100644 local_install/include/pybind11/detail/common.h create mode 100644 local_install/include/pybind11/detail/descr.h create mode 100644 local_install/include/pybind11/detail/init.h create mode 100644 local_install/include/pybind11/detail/internals.h create mode 100644 local_install/include/pybind11/detail/type_caster_base.h create mode 100644 local_install/include/pybind11/detail/typeid.h create mode 100644 local_install/include/pybind11/eigen.h create mode 100644 local_install/include/pybind11/eigen/common.h create mode 100644 local_install/include/pybind11/eigen/matrix.h create mode 100644 local_install/include/pybind11/eigen/tensor.h create mode 100644 local_install/include/pybind11/embed.h create mode 100644 local_install/include/pybind11/eval.h create mode 100644 local_install/include/pybind11/functional.h create mode 100644 local_install/include/pybind11/gil.h create mode 100644 local_install/include/pybind11/iostream.h create mode 100644 local_install/include/pybind11/numpy.h create mode 100644 local_install/include/pybind11/operators.h create mode 100644 local_install/include/pybind11/options.h create mode 100644 local_install/include/pybind11/pybind11.h create mode 100644 local_install/include/pybind11/pytypes.h create mode 100644 local_install/include/pybind11/stl.h create mode 100644 local_install/include/pybind11/stl/filesystem.h create mode 100644 local_install/include/pybind11/stl_bind.h create mode 100644 local_install/include/pybind11/type_caster_pyobject_ptr.h create mode 100644 local_install/share/cmake/pybind11/FindPythonLibsNew.cmake create mode 100644 local_install/share/cmake/pybind11/pybind11Common.cmake create mode 100644 local_install/share/cmake/pybind11/pybind11Config.cmake create mode 100644 local_install/share/cmake/pybind11/pybind11ConfigVersion.cmake create mode 100644 local_install/share/cmake/pybind11/pybind11NewTools.cmake create mode 100644 local_install/share/cmake/pybind11/pybind11Targets.cmake create mode 100644 local_install/share/cmake/pybind11/pybind11Tools.cmake create mode 100644 local_install/share/pkgconfig/pybind11.pc create mode 100644 out.ogv create mode 100644 pybind11-2.11.1/.appveyor.yml create mode 100644 pybind11-2.11.1/.clang-format create mode 100644 pybind11-2.11.1/.clang-tidy create mode 100644 pybind11-2.11.1/.cmake-format.yaml create mode 100644 pybind11-2.11.1/.codespell-ignore-lines create mode 100644 pybind11-2.11.1/.gitattributes create mode 100644 pybind11-2.11.1/.github/CODEOWNERS create mode 100644 pybind11-2.11.1/.github/CONTRIBUTING.md create mode 100644 pybind11-2.11.1/.github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 pybind11-2.11.1/.github/ISSUE_TEMPLATE/config.yml create mode 100644 pybind11-2.11.1/.github/dependabot.yml create mode 100644 pybind11-2.11.1/.github/labeler.yml create mode 100644 pybind11-2.11.1/.github/labeler_merged.yml create mode 100644 pybind11-2.11.1/.github/matchers/pylint.json create mode 100644 pybind11-2.11.1/.github/pull_request_template.md create mode 100644 pybind11-2.11.1/.github/workflows/ci.yml create mode 100644 pybind11-2.11.1/.github/workflows/configure.yml create mode 100644 pybind11-2.11.1/.github/workflows/format.yml create mode 100644 pybind11-2.11.1/.github/workflows/labeler.yml create mode 100644 pybind11-2.11.1/.github/workflows/pip.yml create mode 100644 pybind11-2.11.1/.github/workflows/upstream.yml create mode 100644 pybind11-2.11.1/.gitignore create mode 100644 pybind11-2.11.1/.pre-commit-config.yaml create mode 100644 pybind11-2.11.1/.readthedocs.yml create mode 100644 pybind11-2.11.1/CMakeLists.txt create mode 100644 pybind11-2.11.1/LICENSE create mode 100644 pybind11-2.11.1/MANIFEST.in create mode 100644 pybind11-2.11.1/README.rst create mode 100644 pybind11-2.11.1/SECURITY.md create mode 100644 pybind11-2.11.1/docs/Doxyfile create mode 100644 pybind11-2.11.1/docs/_static/css/custom.css create mode 100644 pybind11-2.11.1/docs/advanced/cast/chrono.rst create mode 100644 pybind11-2.11.1/docs/advanced/cast/custom.rst create mode 100644 pybind11-2.11.1/docs/advanced/cast/eigen.rst create mode 100644 pybind11-2.11.1/docs/advanced/cast/functional.rst create mode 100644 pybind11-2.11.1/docs/advanced/cast/index.rst create mode 100644 pybind11-2.11.1/docs/advanced/cast/overview.rst create mode 100644 pybind11-2.11.1/docs/advanced/cast/stl.rst create mode 100644 pybind11-2.11.1/docs/advanced/cast/strings.rst create mode 100644 pybind11-2.11.1/docs/advanced/classes.rst create mode 100644 pybind11-2.11.1/docs/advanced/embedding.rst create mode 100644 pybind11-2.11.1/docs/advanced/exceptions.rst create mode 100644 pybind11-2.11.1/docs/advanced/functions.rst create mode 100644 pybind11-2.11.1/docs/advanced/misc.rst create mode 100644 pybind11-2.11.1/docs/advanced/pycpp/index.rst create mode 100644 pybind11-2.11.1/docs/advanced/pycpp/numpy.rst create mode 100644 pybind11-2.11.1/docs/advanced/pycpp/object.rst create mode 100644 pybind11-2.11.1/docs/advanced/pycpp/utilities.rst create mode 100644 pybind11-2.11.1/docs/advanced/smart_ptrs.rst create mode 100644 pybind11-2.11.1/docs/basics.rst create mode 100644 pybind11-2.11.1/docs/benchmark.py create mode 100644 pybind11-2.11.1/docs/benchmark.rst create mode 100644 pybind11-2.11.1/docs/changelog.rst create mode 100644 pybind11-2.11.1/docs/classes.rst create mode 100644 pybind11-2.11.1/docs/cmake/index.rst create mode 100644 pybind11-2.11.1/docs/compiling.rst create mode 100644 pybind11-2.11.1/docs/conf.py create mode 100644 pybind11-2.11.1/docs/faq.rst create mode 100644 pybind11-2.11.1/docs/index.rst create mode 100644 pybind11-2.11.1/docs/installing.rst create mode 100644 pybind11-2.11.1/docs/limitations.rst create mode 100644 pybind11-2.11.1/docs/pybind11-logo.png create mode 100644 pybind11-2.11.1/docs/pybind11_vs_boost_python1.png create mode 100644 pybind11-2.11.1/docs/pybind11_vs_boost_python1.svg create mode 100644 pybind11-2.11.1/docs/pybind11_vs_boost_python2.png create mode 100644 pybind11-2.11.1/docs/pybind11_vs_boost_python2.svg create mode 100644 pybind11-2.11.1/docs/reference.rst create mode 100644 pybind11-2.11.1/docs/release.rst create mode 100644 pybind11-2.11.1/docs/requirements.txt create mode 100644 pybind11-2.11.1/docs/upgrade.rst create mode 100644 pybind11-2.11.1/include/pybind11/attr.h create mode 100644 pybind11-2.11.1/include/pybind11/buffer_info.h create mode 100644 pybind11-2.11.1/include/pybind11/cast.h create mode 100644 pybind11-2.11.1/include/pybind11/chrono.h create mode 100644 pybind11-2.11.1/include/pybind11/common.h create mode 100644 pybind11-2.11.1/include/pybind11/complex.h create mode 100644 pybind11-2.11.1/include/pybind11/detail/class.h create mode 100644 pybind11-2.11.1/include/pybind11/detail/common.h create mode 100644 pybind11-2.11.1/include/pybind11/detail/descr.h create mode 100644 pybind11-2.11.1/include/pybind11/detail/init.h create mode 100644 pybind11-2.11.1/include/pybind11/detail/internals.h create mode 100644 pybind11-2.11.1/include/pybind11/detail/type_caster_base.h create mode 100644 pybind11-2.11.1/include/pybind11/detail/typeid.h create mode 100644 pybind11-2.11.1/include/pybind11/eigen.h create mode 100644 pybind11-2.11.1/include/pybind11/eigen/common.h create mode 100644 pybind11-2.11.1/include/pybind11/eigen/matrix.h create mode 100644 pybind11-2.11.1/include/pybind11/eigen/tensor.h create mode 100644 pybind11-2.11.1/include/pybind11/embed.h create mode 100644 pybind11-2.11.1/include/pybind11/eval.h create mode 100644 pybind11-2.11.1/include/pybind11/functional.h create mode 100644 pybind11-2.11.1/include/pybind11/gil.h create mode 100644 pybind11-2.11.1/include/pybind11/iostream.h create mode 100644 pybind11-2.11.1/include/pybind11/numpy.h create mode 100644 pybind11-2.11.1/include/pybind11/operators.h create mode 100644 pybind11-2.11.1/include/pybind11/options.h create mode 100644 pybind11-2.11.1/include/pybind11/pybind11.h create mode 100644 pybind11-2.11.1/include/pybind11/pytypes.h create mode 100644 pybind11-2.11.1/include/pybind11/stl.h create mode 100644 pybind11-2.11.1/include/pybind11/stl/filesystem.h create mode 100644 pybind11-2.11.1/include/pybind11/stl_bind.h create mode 100644 pybind11-2.11.1/include/pybind11/type_caster_pyobject_ptr.h create mode 100644 pybind11-2.11.1/noxfile.py create mode 100644 pybind11-2.11.1/pybind11/__init__.py create mode 100644 pybind11-2.11.1/pybind11/__main__.py create mode 100644 pybind11-2.11.1/pybind11/_version.py create mode 100644 pybind11-2.11.1/pybind11/commands.py create mode 100644 pybind11-2.11.1/pybind11/py.typed create mode 100644 pybind11-2.11.1/pybind11/setup_helpers.py create mode 100644 pybind11-2.11.1/pyproject.toml create mode 100644 pybind11-2.11.1/setup.cfg create mode 100644 pybind11-2.11.1/setup.py create mode 100644 pybind11-2.11.1/tests/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/conftest.py create mode 100644 pybind11-2.11.1/tests/constructor_stats.h create mode 100644 pybind11-2.11.1/tests/cross_module_gil_utils.cpp create mode 100644 pybind11-2.11.1/tests/cross_module_interleaved_error_already_set.cpp create mode 100644 pybind11-2.11.1/tests/eigen_tensor_avoid_stl_array.cpp create mode 100644 pybind11-2.11.1/tests/env.py create mode 100644 pybind11-2.11.1/tests/extra_python_package/pytest.ini create mode 100644 pybind11-2.11.1/tests/extra_python_package/test_files.py create mode 100644 pybind11-2.11.1/tests/extra_setuptools/pytest.ini create mode 100644 pybind11-2.11.1/tests/extra_setuptools/test_setuphelper.py create mode 100644 pybind11-2.11.1/tests/local_bindings.h create mode 100644 pybind11-2.11.1/tests/object.h create mode 100644 pybind11-2.11.1/tests/pybind11_cross_module_tests.cpp create mode 100644 pybind11-2.11.1/tests/pybind11_tests.cpp create mode 100644 pybind11-2.11.1/tests/pybind11_tests.h create mode 100644 pybind11-2.11.1/tests/pytest.ini create mode 100644 pybind11-2.11.1/tests/requirements.txt create mode 100644 pybind11-2.11.1/tests/test_async.cpp create mode 100644 pybind11-2.11.1/tests/test_async.py create mode 100644 pybind11-2.11.1/tests/test_buffers.cpp create mode 100644 pybind11-2.11.1/tests/test_buffers.py create mode 100644 pybind11-2.11.1/tests/test_builtin_casters.cpp create mode 100644 pybind11-2.11.1/tests/test_builtin_casters.py create mode 100644 pybind11-2.11.1/tests/test_call_policies.cpp create mode 100644 pybind11-2.11.1/tests/test_call_policies.py create mode 100644 pybind11-2.11.1/tests/test_callbacks.cpp create mode 100644 pybind11-2.11.1/tests/test_callbacks.py create mode 100644 pybind11-2.11.1/tests/test_chrono.cpp create mode 100644 pybind11-2.11.1/tests/test_chrono.py create mode 100644 pybind11-2.11.1/tests/test_class.cpp create mode 100644 pybind11-2.11.1/tests/test_class.py create mode 100644 pybind11-2.11.1/tests/test_cmake_build/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/test_cmake_build/embed.cpp create mode 100644 pybind11-2.11.1/tests/test_cmake_build/installed_embed/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/test_cmake_build/installed_function/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/test_cmake_build/installed_target/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/test_cmake_build/main.cpp create mode 100644 pybind11-2.11.1/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/test_cmake_build/subdirectory_function/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/test_cmake_build/subdirectory_target/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/test_const_name.cpp create mode 100644 pybind11-2.11.1/tests/test_const_name.py create mode 100644 pybind11-2.11.1/tests/test_constants_and_functions.cpp create mode 100644 pybind11-2.11.1/tests/test_constants_and_functions.py create mode 100644 pybind11-2.11.1/tests/test_copy_move.cpp create mode 100644 pybind11-2.11.1/tests/test_copy_move.py create mode 100644 pybind11-2.11.1/tests/test_custom_type_casters.cpp create mode 100644 pybind11-2.11.1/tests/test_custom_type_casters.py create mode 100644 pybind11-2.11.1/tests/test_custom_type_setup.cpp create mode 100644 pybind11-2.11.1/tests/test_custom_type_setup.py create mode 100644 pybind11-2.11.1/tests/test_docstring_options.cpp create mode 100644 pybind11-2.11.1/tests/test_docstring_options.py create mode 100644 pybind11-2.11.1/tests/test_eigen_matrix.cpp create mode 100644 pybind11-2.11.1/tests/test_eigen_matrix.py create mode 100644 pybind11-2.11.1/tests/test_eigen_tensor.cpp create mode 100644 pybind11-2.11.1/tests/test_eigen_tensor.inl create mode 100644 pybind11-2.11.1/tests/test_eigen_tensor.py create mode 100644 pybind11-2.11.1/tests/test_embed/CMakeLists.txt create mode 100644 pybind11-2.11.1/tests/test_embed/catch.cpp create mode 100644 pybind11-2.11.1/tests/test_embed/external_module.cpp create mode 100644 pybind11-2.11.1/tests/test_embed/test_interpreter.cpp create mode 100644 pybind11-2.11.1/tests/test_embed/test_interpreter.py create mode 100644 pybind11-2.11.1/tests/test_embed/test_trampoline.py create mode 100644 pybind11-2.11.1/tests/test_enum.cpp create mode 100644 pybind11-2.11.1/tests/test_enum.py create mode 100644 pybind11-2.11.1/tests/test_eval.cpp create mode 100644 pybind11-2.11.1/tests/test_eval.py create mode 100644 pybind11-2.11.1/tests/test_eval_call.py create mode 100644 pybind11-2.11.1/tests/test_exceptions.cpp create mode 100644 pybind11-2.11.1/tests/test_exceptions.h create mode 100644 pybind11-2.11.1/tests/test_exceptions.py create mode 100644 pybind11-2.11.1/tests/test_factory_constructors.cpp create mode 100644 pybind11-2.11.1/tests/test_factory_constructors.py create mode 100644 pybind11-2.11.1/tests/test_gil_scoped.cpp create mode 100644 pybind11-2.11.1/tests/test_gil_scoped.py create mode 100644 pybind11-2.11.1/tests/test_iostream.cpp create mode 100644 pybind11-2.11.1/tests/test_iostream.py create mode 100644 pybind11-2.11.1/tests/test_kwargs_and_defaults.cpp create mode 100644 pybind11-2.11.1/tests/test_kwargs_and_defaults.py create mode 100644 pybind11-2.11.1/tests/test_local_bindings.cpp create mode 100644 pybind11-2.11.1/tests/test_local_bindings.py create mode 100644 pybind11-2.11.1/tests/test_methods_and_attributes.cpp create mode 100644 pybind11-2.11.1/tests/test_methods_and_attributes.py create mode 100644 pybind11-2.11.1/tests/test_modules.cpp create mode 100644 pybind11-2.11.1/tests/test_modules.py create mode 100644 pybind11-2.11.1/tests/test_multiple_inheritance.cpp create mode 100644 pybind11-2.11.1/tests/test_multiple_inheritance.py create mode 100644 pybind11-2.11.1/tests/test_numpy_array.cpp create mode 100644 pybind11-2.11.1/tests/test_numpy_array.py create mode 100644 pybind11-2.11.1/tests/test_numpy_dtypes.cpp create mode 100644 pybind11-2.11.1/tests/test_numpy_dtypes.py create mode 100644 pybind11-2.11.1/tests/test_numpy_vectorize.cpp create mode 100644 pybind11-2.11.1/tests/test_numpy_vectorize.py create mode 100644 pybind11-2.11.1/tests/test_opaque_types.cpp create mode 100644 pybind11-2.11.1/tests/test_opaque_types.py create mode 100644 pybind11-2.11.1/tests/test_operator_overloading.cpp create mode 100644 pybind11-2.11.1/tests/test_operator_overloading.py create mode 100644 pybind11-2.11.1/tests/test_pickling.cpp create mode 100644 pybind11-2.11.1/tests/test_pickling.py create mode 100644 pybind11-2.11.1/tests/test_pytypes.cpp create mode 100644 pybind11-2.11.1/tests/test_pytypes.py create mode 100644 pybind11-2.11.1/tests/test_sequences_and_iterators.cpp create mode 100644 pybind11-2.11.1/tests/test_sequences_and_iterators.py create mode 100644 pybind11-2.11.1/tests/test_smart_ptr.cpp create mode 100644 pybind11-2.11.1/tests/test_smart_ptr.py create mode 100644 pybind11-2.11.1/tests/test_stl.cpp create mode 100644 pybind11-2.11.1/tests/test_stl.py create mode 100644 pybind11-2.11.1/tests/test_stl_binders.cpp create mode 100644 pybind11-2.11.1/tests/test_stl_binders.py create mode 100644 pybind11-2.11.1/tests/test_tagbased_polymorphic.cpp create mode 100644 pybind11-2.11.1/tests/test_tagbased_polymorphic.py create mode 100644 pybind11-2.11.1/tests/test_thread.cpp create mode 100644 pybind11-2.11.1/tests/test_thread.py create mode 100644 pybind11-2.11.1/tests/test_type_caster_pyobject_ptr.cpp create mode 100644 pybind11-2.11.1/tests/test_type_caster_pyobject_ptr.py create mode 100644 pybind11-2.11.1/tests/test_union.cpp create mode 100644 pybind11-2.11.1/tests/test_union.py create mode 100644 pybind11-2.11.1/tests/test_unnamed_namespace_a.cpp create mode 100644 pybind11-2.11.1/tests/test_unnamed_namespace_a.py create mode 100644 pybind11-2.11.1/tests/test_unnamed_namespace_b.cpp create mode 100644 pybind11-2.11.1/tests/test_unnamed_namespace_b.py create mode 100644 pybind11-2.11.1/tests/test_vector_unique_ptr_member.cpp create mode 100644 pybind11-2.11.1/tests/test_vector_unique_ptr_member.py create mode 100644 pybind11-2.11.1/tests/test_virtual_functions.cpp create mode 100644 pybind11-2.11.1/tests/test_virtual_functions.py create mode 100644 pybind11-2.11.1/tests/valgrind-numpy-scipy.supp create mode 100644 pybind11-2.11.1/tests/valgrind-python.supp create mode 100644 pybind11-2.11.1/tools/FindCatch.cmake create mode 100644 pybind11-2.11.1/tools/FindEigen3.cmake create mode 100644 pybind11-2.11.1/tools/FindPythonLibsNew.cmake create mode 100644 pybind11-2.11.1/tools/JoinPaths.cmake create mode 100755 pybind11-2.11.1/tools/check-style.sh create mode 100644 pybind11-2.11.1/tools/cmake_uninstall.cmake.in create mode 100644 pybind11-2.11.1/tools/codespell_ignore_lines_from_errors.py create mode 100644 pybind11-2.11.1/tools/libsize.py create mode 100755 pybind11-2.11.1/tools/make_changelog.py create mode 100644 pybind11-2.11.1/tools/pybind11.pc.in create mode 100644 pybind11-2.11.1/tools/pybind11Common.cmake create mode 100644 pybind11-2.11.1/tools/pybind11Config.cmake.in create mode 100644 pybind11-2.11.1/tools/pybind11NewTools.cmake create mode 100644 pybind11-2.11.1/tools/pybind11Tools.cmake create mode 100644 pybind11-2.11.1/tools/pyproject.toml create mode 100644 pybind11-2.11.1/tools/setup_global.py.in create mode 100644 pybind11-2.11.1/tools/setup_main.py.in create mode 100644 src/filepattern/cpp/test_build/CMakeCache.txt create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/3.25.1/CMakeCCompiler.cmake create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/3.25.1/CMakeCXXCompiler.cmake create mode 100755 src/filepattern/cpp/test_build/CMakeFiles/3.25.1/CMakeDetermineCompilerABI_C.bin create mode 100755 src/filepattern/cpp/test_build/CMakeFiles/3.25.1/CMakeDetermineCompilerABI_CXX.bin create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/3.25.1/CMakeSystem.cmake create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/3.25.1/CompilerIdC/CMakeCCompilerId.c create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/3.25.1/CompilerIdCXX/CMakeCXXCompilerId.cpp create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/Makefile.cmake create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/Makefile2 create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/TargetDirectories.txt create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/cmake.check_cache create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/DependInfo.cmake create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/build.make create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/cmake_clean.cmake create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/cmake_clean_target.cmake create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/compiler_depend.make create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/compiler_depend.ts create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/depend.make create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/flags.make create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/link.txt create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/filepattern.dir/progress.make create mode 100644 src/filepattern/cpp/test_build/CMakeFiles/progress.marks create mode 100644 src/filepattern/cpp/test_build/Makefile create mode 100644 src/filepattern/cpp/test_build/cmake_install.cmake create mode 100644 src/filepattern/cpp/test_build/include/filepattern_export.h create mode 100644 src/filepattern/cpp/test_build/packaging/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 src/filepattern/cpp/test_build/packaging/CMakeFiles/Export/277ec7bc4073629d29b1a4bb8ec4e3ae/filepattern-static-targets-noconfig.cmake create mode 100644 src/filepattern/cpp/test_build/packaging/CMakeFiles/Export/277ec7bc4073629d29b1a4bb8ec4e3ae/filepattern-static-targets.cmake create mode 100644 src/filepattern/cpp/test_build/packaging/CMakeFiles/progress.marks create mode 100644 src/filepattern/cpp/test_build/packaging/Makefile create mode 100644 src/filepattern/cpp/test_build/packaging/cmake_install.cmake create mode 100644 src/filepattern/cpp/test_build/packaging/filepatternConfigVersion.cmake create mode 120000 src/filepattern/libfilepattern.so.2 create mode 100755 src/filepattern/libfilepattern.so.2.1.4 create mode 100644 test_fp.py create mode 100644 v2.11.1.zip diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index f4236f8c..b269d21c 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -46,12 +46,12 @@ jobs: python scripts/update_versions.py --version ${{ steps.release.outputs.version }} echo "Updated version files to ${{ steps.release.outputs.version }}" - - name: Upload VERSION file as release asset + - name: Upload .version file as release asset if: ${{ steps.release.outputs.release_created }} env: GH_TOKEN: ${{ github.token }} run: | - gh release upload ${{ steps.release.outputs.tag_name }} VERSION --clobber + gh release upload ${{ steps.release.outputs.tag_name }} .version --clobber trigger-publish: needs: release-please diff --git a/VERSION b/.version similarity index 100% rename from VERSION rename to .version diff --git a/local_install/include/pybind11/attr.h b/local_install/include/pybind11/attr.h new file mode 100644 index 00000000..1044db94 --- /dev/null +++ b/local_install/include/pybind11/attr.h @@ -0,0 +1,690 @@ +/* + pybind11/attr.h: Infrastructure for processing custom + type and function attributes + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "detail/common.h" +#include "cast.h" + +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +/// \addtogroup annotations +/// @{ + +/// Annotation for methods +struct is_method { + handle class_; + explicit is_method(const handle &c) : class_(c) {} +}; + +/// Annotation for setters +struct is_setter {}; + +/// Annotation for operators +struct is_operator {}; + +/// Annotation for classes that cannot be subclassed +struct is_final {}; + +/// Annotation for parent scope +struct scope { + handle value; + explicit scope(const handle &s) : value(s) {} +}; + +/// Annotation for documentation +struct doc { + const char *value; + explicit doc(const char *value) : value(value) {} +}; + +/// Annotation for function names +struct name { + const char *value; + explicit name(const char *value) : value(value) {} +}; + +/// Annotation indicating that a function is an overload associated with a given "sibling" +struct sibling { + handle value; + explicit sibling(const handle &value) : value(value.ptr()) {} +}; + +/// Annotation indicating that a class derives from another given type +template +struct base { + + PYBIND11_DEPRECATED( + "base() was deprecated in favor of specifying 'T' as a template argument to class_") + base() = default; +}; + +/// Keep patient alive while nurse lives +template +struct keep_alive {}; + +/// Annotation indicating that a class is involved in a multiple inheritance relationship +struct multiple_inheritance {}; + +/// Annotation which enables dynamic attributes, i.e. adds `__dict__` to a class +struct dynamic_attr {}; + +/// Annotation which enables the buffer protocol for a type +struct buffer_protocol {}; + +/// Annotation which requests that a special metaclass is created for a type +struct metaclass { + handle value; + + PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.") + metaclass() = default; + + /// Override pybind11's default metaclass + explicit metaclass(handle value) : value(value) {} +}; + +/// Specifies a custom callback with signature `void (PyHeapTypeObject*)` that +/// may be used to customize the Python type. +/// +/// The callback is invoked immediately before `PyType_Ready`. +/// +/// Note: This is an advanced interface, and uses of it may require changes to +/// work with later versions of pybind11. You may wish to consult the +/// implementation of `make_new_python_type` in `detail/classes.h` to understand +/// the context in which the callback will be run. +struct custom_type_setup { + using callback = std::function; + + explicit custom_type_setup(callback value) : value(std::move(value)) {} + + callback value; +}; + +/// Annotation that marks a class as local to the module: +struct module_local { + const bool value; + constexpr explicit module_local(bool v = true) : value(v) {} +}; + +/// Annotation to mark enums as an arithmetic type +struct arithmetic {}; + +/// Mark a function for addition at the beginning of the existing overload chain instead of the end +struct prepend {}; + +/** \rst + A call policy which places one or more guard variables (``Ts...``) around the function call. + + For example, this definition: + + .. code-block:: cpp + + m.def("foo", foo, py::call_guard()); + + is equivalent to the following pseudocode: + + .. code-block:: cpp + + m.def("foo", [](args...) { + T scope_guard; + return foo(args...); // forwarded arguments + }); + \endrst */ +template +struct call_guard; + +template <> +struct call_guard<> { + using type = detail::void_type; +}; + +template +struct call_guard { + static_assert(std::is_default_constructible::value, + "The guard type must be default constructible"); + + using type = T; +}; + +template +struct call_guard { + struct type { + T guard{}; // Compose multiple guard types with left-to-right default-constructor order + typename call_guard::type next{}; + }; +}; + +/// @} annotations + +PYBIND11_NAMESPACE_BEGIN(detail) +/* Forward declarations */ +enum op_id : int; +enum op_type : int; +struct undefined_t; +template +struct op_; +void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret); + +/// Internal data structure which holds metadata about a keyword argument +struct argument_record { + const char *name; ///< Argument name + const char *descr; ///< Human-readable version of the argument value + handle value; ///< Associated Python object + bool convert : 1; ///< True if the argument is allowed to convert when loading + bool none : 1; ///< True if None is allowed when loading + + argument_record(const char *name, const char *descr, handle value, bool convert, bool none) + : name(name), descr(descr), value(value), convert(convert), none(none) {} +}; + +/// Internal data structure which holds metadata about a bound function (signature, overloads, +/// etc.) +struct function_record { + function_record() + : is_constructor(false), is_new_style_constructor(false), is_stateless(false), + is_operator(false), is_method(false), is_setter(false), has_args(false), + has_kwargs(false), prepend(false) {} + + /// Function name + char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ + + // User-specified documentation string + char *doc = nullptr; + + /// Human-readable version of the function signature + char *signature = nullptr; + + /// List of registered keyword arguments + std::vector args; + + /// Pointer to lambda function which converts arguments and performs the actual call + handle (*impl)(function_call &) = nullptr; + + /// Storage for the wrapped function pointer and captured data, if any + void *data[3] = {}; + + /// Pointer to custom destructor for 'data' (if needed) + void (*free_data)(function_record *ptr) = nullptr; + + /// Return value policy associated with this function + return_value_policy policy = return_value_policy::automatic; + + /// True if name == '__init__' + bool is_constructor : 1; + + /// True if this is a new-style `__init__` defined in `detail/init.h` + bool is_new_style_constructor : 1; + + /// True if this is a stateless function pointer + bool is_stateless : 1; + + /// True if this is an operator (__add__), etc. + bool is_operator : 1; + + /// True if this is a method + bool is_method : 1; + + /// True if this is a setter + bool is_setter : 1; + + /// True if the function has a '*args' argument + bool has_args : 1; + + /// True if the function has a '**kwargs' argument + bool has_kwargs : 1; + + /// True if this function is to be inserted at the beginning of the overload resolution chain + bool prepend : 1; + + /// Number of arguments (including py::args and/or py::kwargs, if present) + std::uint16_t nargs; + + /// Number of leading positional arguments, which are terminated by a py::args or py::kwargs + /// argument or by a py::kw_only annotation. + std::uint16_t nargs_pos = 0; + + /// Number of leading arguments (counted in `nargs`) that are positional-only + std::uint16_t nargs_pos_only = 0; + + /// Python method object + PyMethodDef *def = nullptr; + + /// Python handle to the parent scope (a class or a module) + handle scope; + + /// Python handle to the sibling function representing an overload chain + handle sibling; + + /// Pointer to next overload + function_record *next = nullptr; +}; + +/// Special data structure which (temporarily) holds metadata about a bound class +struct type_record { + PYBIND11_NOINLINE type_record() + : multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false), + default_holder(true), module_local(false), is_final(false) {} + + /// Handle to the parent scope + handle scope; + + /// Name of the class + const char *name = nullptr; + + // Pointer to RTTI type_info data structure + const std::type_info *type = nullptr; + + /// How large is the underlying C++ type? + size_t type_size = 0; + + /// What is the alignment of the underlying C++ type? + size_t type_align = 0; + + /// How large is the type's holder? + size_t holder_size = 0; + + /// The global operator new can be overridden with a class-specific variant + void *(*operator_new)(size_t) = nullptr; + + /// Function pointer to class_<..>::init_instance + void (*init_instance)(instance *, const void *) = nullptr; + + /// Function pointer to class_<..>::dealloc + void (*dealloc)(detail::value_and_holder &) = nullptr; + + /// List of base classes of the newly created type + list bases; + + /// Optional docstring + const char *doc = nullptr; + + /// Custom metaclass (optional) + handle metaclass; + + /// Custom type setup. + custom_type_setup::callback custom_type_setup_callback; + + /// Multiple inheritance marker + bool multiple_inheritance : 1; + + /// Does the class manage a __dict__? + bool dynamic_attr : 1; + + /// Does the class implement the buffer protocol? + bool buffer_protocol : 1; + + /// Is the default (unique_ptr) holder type used? + bool default_holder : 1; + + /// Is the class definition local to the module shared object? + bool module_local : 1; + + /// Is the class inheritable from python classes? + bool is_final : 1; + + PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) { + auto *base_info = detail::get_type_info(base, false); + if (!base_info) { + std::string tname(base.name()); + detail::clean_type_id(tname); + pybind11_fail("generic_type: type \"" + std::string(name) + + "\" referenced unknown base type \"" + tname + "\""); + } + + if (default_holder != base_info->default_holder) { + std::string tname(base.name()); + detail::clean_type_id(tname); + pybind11_fail("generic_type: type \"" + std::string(name) + "\" " + + (default_holder ? "does not have" : "has") + + " a non-default holder type while its base \"" + tname + "\" " + + (base_info->default_holder ? "does not" : "does")); + } + + bases.append((PyObject *) base_info->type); + +#if PY_VERSION_HEX < 0x030B0000 + dynamic_attr |= base_info->type->tp_dictoffset != 0; +#else + dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0; +#endif + + if (caster) { + base_info->implicit_casts.emplace_back(type, caster); + } + } +}; + +inline function_call::function_call(const function_record &f, handle p) : func(f), parent(p) { + args.reserve(f.nargs); + args_convert.reserve(f.nargs); +} + +/// Tag for a new-style `__init__` defined in `detail/init.h` +struct is_new_style_constructor {}; + +/** + * Partial template specializations to process custom attributes provided to + * cpp_function_ and class_. These are either used to initialize the respective + * fields in the type_record and function_record data structures or executed at + * runtime to deal with custom call policies (e.g. keep_alive). + */ +template +struct process_attribute; + +template +struct process_attribute_default { + /// Default implementation: do nothing + static void init(const T &, function_record *) {} + static void init(const T &, type_record *) {} + static void precall(function_call &) {} + static void postcall(function_call &, handle) {} +}; + +/// Process an attribute specifying the function's name +template <> +struct process_attribute : process_attribute_default { + static void init(const name &n, function_record *r) { r->name = const_cast(n.value); } +}; + +/// Process an attribute specifying the function's docstring +template <> +struct process_attribute : process_attribute_default { + static void init(const doc &n, function_record *r) { r->doc = const_cast(n.value); } +}; + +/// Process an attribute specifying the function's docstring (provided as a C-style string) +template <> +struct process_attribute : process_attribute_default { + static void init(const char *d, function_record *r) { r->doc = const_cast(d); } + static void init(const char *d, type_record *r) { r->doc = d; } +}; +template <> +struct process_attribute : process_attribute {}; + +/// Process an attribute indicating the function's return value policy +template <> +struct process_attribute : process_attribute_default { + static void init(const return_value_policy &p, function_record *r) { r->policy = p; } +}; + +/// Process an attribute which indicates that this is an overloaded function associated with a +/// given sibling +template <> +struct process_attribute : process_attribute_default { + static void init(const sibling &s, function_record *r) { r->sibling = s.value; } +}; + +/// Process an attribute which indicates that this function is a method +template <> +struct process_attribute : process_attribute_default { + static void init(const is_method &s, function_record *r) { + r->is_method = true; + r->scope = s.class_; + } +}; + +/// Process an attribute which indicates that this function is a setter +template <> +struct process_attribute : process_attribute_default { + static void init(const is_setter &, function_record *r) { r->is_setter = true; } +}; + +/// Process an attribute which indicates the parent scope of a method +template <> +struct process_attribute : process_attribute_default { + static void init(const scope &s, function_record *r) { r->scope = s.value; } +}; + +/// Process an attribute which indicates that this function is an operator +template <> +struct process_attribute : process_attribute_default { + static void init(const is_operator &, function_record *r) { r->is_operator = true; } +}; + +template <> +struct process_attribute + : process_attribute_default { + static void init(const is_new_style_constructor &, function_record *r) { + r->is_new_style_constructor = true; + } +}; + +inline void check_kw_only_arg(const arg &a, function_record *r) { + if (r->args.size() > r->nargs_pos && (!a.name || a.name[0] == '\0')) { + pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or " + "args() argument"); + } +} + +inline void append_self_arg_if_needed(function_record *r) { + if (r->is_method && r->args.empty()) { + r->args.emplace_back("self", nullptr, handle(), /*convert=*/true, /*none=*/false); + } +} + +/// Process a keyword argument attribute (*without* a default value) +template <> +struct process_attribute : process_attribute_default { + static void init(const arg &a, function_record *r) { + append_self_arg_if_needed(r); + r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none); + + check_kw_only_arg(a, r); + } +}; + +/// Process a keyword argument attribute (*with* a default value) +template <> +struct process_attribute : process_attribute_default { + static void init(const arg_v &a, function_record *r) { + if (r->is_method && r->args.empty()) { + r->args.emplace_back( + "self", /*descr=*/nullptr, /*parent=*/handle(), /*convert=*/true, /*none=*/false); + } + + if (!a.value) { +#if defined(PYBIND11_DETAILED_ERROR_MESSAGES) + std::string descr("'"); + if (a.name) { + descr += std::string(a.name) + ": "; + } + descr += a.type + "'"; + if (r->is_method) { + if (r->name) { + descr += " in method '" + (std::string) str(r->scope) + "." + + (std::string) r->name + "'"; + } else { + descr += " in method of '" + (std::string) str(r->scope) + "'"; + } + } else if (r->name) { + descr += " in function '" + (std::string) r->name + "'"; + } + pybind11_fail("arg(): could not convert default argument " + descr + + " into a Python object (type not registered yet?)"); +#else + pybind11_fail("arg(): could not convert default argument " + "into a Python object (type not registered yet?). " + "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for " + "more information."); +#endif + } + r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); + + check_kw_only_arg(a, r); + } +}; + +/// Process a keyword-only-arguments-follow pseudo argument +template <> +struct process_attribute : process_attribute_default { + static void init(const kw_only &, function_record *r) { + append_self_arg_if_needed(r); + if (r->has_args && r->nargs_pos != static_cast(r->args.size())) { + pybind11_fail("Mismatched args() and kw_only(): they must occur at the same relative " + "argument location (or omit kw_only() entirely)"); + } + r->nargs_pos = static_cast(r->args.size()); + } +}; + +/// Process a positional-only-argument maker +template <> +struct process_attribute : process_attribute_default { + static void init(const pos_only &, function_record *r) { + append_self_arg_if_needed(r); + r->nargs_pos_only = static_cast(r->args.size()); + if (r->nargs_pos_only > r->nargs_pos) { + pybind11_fail("pos_only(): cannot follow a py::args() argument"); + } + // It also can't follow a kw_only, but a static_assert in pybind11.h checks that + } +}; + +/// Process a parent class attribute. Single inheritance only (class_ itself already guarantees +/// that) +template +struct process_attribute::value>> + : process_attribute_default { + static void init(const handle &h, type_record *r) { r->bases.append(h); } +}; + +/// Process a parent class attribute (deprecated, does not support multiple inheritance) +template +struct process_attribute> : process_attribute_default> { + static void init(const base &, type_record *r) { r->add_base(typeid(T), nullptr); } +}; + +/// Process a multiple inheritance attribute +template <> +struct process_attribute : process_attribute_default { + static void init(const multiple_inheritance &, type_record *r) { + r->multiple_inheritance = true; + } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const dynamic_attr &, type_record *r) { r->dynamic_attr = true; } +}; + +template <> +struct process_attribute { + static void init(const custom_type_setup &value, type_record *r) { + r->custom_type_setup_callback = value.value; + } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const is_final &, type_record *r) { r->is_final = true; } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const metaclass &m, type_record *r) { r->metaclass = m.value; } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const module_local &l, type_record *r) { r->module_local = l.value; } +}; + +/// Process a 'prepend' attribute, putting this at the beginning of the overload chain +template <> +struct process_attribute : process_attribute_default { + static void init(const prepend &, function_record *r) { r->prepend = true; } +}; + +/// Process an 'arithmetic' attribute for enums (does nothing here) +template <> +struct process_attribute : process_attribute_default {}; + +template +struct process_attribute> : process_attribute_default> {}; + +/** + * Process a keep_alive call policy -- invokes keep_alive_impl during the + * pre-call handler if both Nurse, Patient != 0 and use the post-call handler + * otherwise + */ +template +struct process_attribute> + : public process_attribute_default> { + template = 0> + static void precall(function_call &call) { + keep_alive_impl(Nurse, Patient, call, handle()); + } + template = 0> + static void postcall(function_call &, handle) {} + template = 0> + static void precall(function_call &) {} + template = 0> + static void postcall(function_call &call, handle ret) { + keep_alive_impl(Nurse, Patient, call, ret); + } +}; + +/// Recursively iterate over variadic template arguments +template +struct process_attributes { + static void init(const Args &...args, function_record *r) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); + using expander = int[]; + (void) expander{ + 0, ((void) process_attribute::type>::init(args, r), 0)...}; + } + static void init(const Args &...args, type_record *r) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); + using expander = int[]; + (void) expander{0, + (process_attribute::type>::init(args, r), 0)...}; + } + static void precall(function_call &call) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call); + using expander = int[]; + (void) expander{0, + (process_attribute::type>::precall(call), 0)...}; + } + static void postcall(function_call &call, handle fn_ret) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call, fn_ret); + PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(fn_ret); + using expander = int[]; + (void) expander{ + 0, (process_attribute::type>::postcall(call, fn_ret), 0)...}; + } +}; + +template +using is_call_guard = is_instantiation; + +/// Extract the ``type`` from the first `call_guard` in `Extras...` (or `void_type` if none found) +template +using extract_guard_t = typename exactly_one_t, Extra...>::type; + +/// Check the number of named arguments at compile time +template ::value...), + size_t self = constexpr_sum(std::is_same::value...)> +constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) { + PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs); + return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs; +} + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/local_install/include/pybind11/buffer_info.h b/local_install/include/pybind11/buffer_info.h new file mode 100644 index 00000000..b99ee8be --- /dev/null +++ b/local_install/include/pybind11/buffer_info.h @@ -0,0 +1,208 @@ +/* + pybind11/buffer_info.h: Python buffer object interface + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "detail/common.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_NAMESPACE_BEGIN(detail) + +// Default, C-style strides +inline std::vector c_strides(const std::vector &shape, ssize_t itemsize) { + auto ndim = shape.size(); + std::vector strides(ndim, itemsize); + if (ndim > 0) { + for (size_t i = ndim - 1; i > 0; --i) { + strides[i - 1] = strides[i] * shape[i]; + } + } + return strides; +} + +// F-style strides; default when constructing an array_t with `ExtraFlags & f_style` +inline std::vector f_strides(const std::vector &shape, ssize_t itemsize) { + auto ndim = shape.size(); + std::vector strides(ndim, itemsize); + for (size_t i = 1; i < ndim; ++i) { + strides[i] = strides[i - 1] * shape[i - 1]; + } + return strides; +} + +template +struct compare_buffer_info; + +PYBIND11_NAMESPACE_END(detail) + +/// Information record describing a Python buffer object +struct buffer_info { + void *ptr = nullptr; // Pointer to the underlying storage + ssize_t itemsize = 0; // Size of individual items in bytes + ssize_t size = 0; // Total number of entries + std::string format; // For homogeneous buffers, this should be set to + // format_descriptor::format() + ssize_t ndim = 0; // Number of dimensions + std::vector shape; // Shape of the tensor (1 entry per dimension) + std::vector strides; // Number of bytes between adjacent entries + // (for each per dimension) + bool readonly = false; // flag to indicate if the underlying storage may be written to + + buffer_info() = default; + + buffer_info(void *ptr, + ssize_t itemsize, + const std::string &format, + ssize_t ndim, + detail::any_container shape_in, + detail::any_container strides_in, + bool readonly = false) + : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim), + shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) { + if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) { + pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length"); + } + for (size_t i = 0; i < (size_t) ndim; ++i) { + size *= shape[i]; + } + } + + template + buffer_info(T *ptr, + detail::any_container shape_in, + detail::any_container strides_in, + bool readonly = false) + : buffer_info(private_ctr_tag(), + ptr, + sizeof(T), + format_descriptor::format(), + static_cast(shape_in->size()), + std::move(shape_in), + std::move(strides_in), + readonly) {} + + buffer_info(void *ptr, + ssize_t itemsize, + const std::string &format, + ssize_t size, + bool readonly = false) + : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) {} + + template + buffer_info(T *ptr, ssize_t size, bool readonly = false) + : buffer_info(ptr, sizeof(T), format_descriptor::format(), size, readonly) {} + + template + buffer_info(const T *ptr, ssize_t size, bool readonly = true) + : buffer_info( + const_cast(ptr), sizeof(T), format_descriptor::format(), size, readonly) {} + + explicit buffer_info(Py_buffer *view, bool ownview = true) + : buffer_info( + view->buf, + view->itemsize, + view->format, + view->ndim, + {view->shape, view->shape + view->ndim}, + /* Though buffer::request() requests PyBUF_STRIDES, ctypes objects + * ignore this flag and return a view with NULL strides. + * When strides are NULL, build them manually. */ + view->strides + ? std::vector(view->strides, view->strides + view->ndim) + : detail::c_strides({view->shape, view->shape + view->ndim}, view->itemsize), + (view->readonly != 0)) { + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) + this->m_view = view; + // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) + this->ownview = ownview; + } + + buffer_info(const buffer_info &) = delete; + buffer_info &operator=(const buffer_info &) = delete; + + buffer_info(buffer_info &&other) noexcept { (*this) = std::move(other); } + + buffer_info &operator=(buffer_info &&rhs) noexcept { + ptr = rhs.ptr; + itemsize = rhs.itemsize; + size = rhs.size; + format = std::move(rhs.format); + ndim = rhs.ndim; + shape = std::move(rhs.shape); + strides = std::move(rhs.strides); + std::swap(m_view, rhs.m_view); + std::swap(ownview, rhs.ownview); + readonly = rhs.readonly; + return *this; + } + + ~buffer_info() { + if (m_view && ownview) { + PyBuffer_Release(m_view); + delete m_view; + } + } + + Py_buffer *view() const { return m_view; } + Py_buffer *&view() { return m_view; } + + /* True if the buffer item type is equivalent to `T`. */ + // To define "equivalent" by example: + // `buffer_info::item_type_is_equivalent_to(b)` and + // `buffer_info::item_type_is_equivalent_to(b)` may both be true + // on some platforms, but `int` and `unsigned` will never be equivalent. + // For the ground truth, please inspect `detail::compare_buffer_info<>`. + template + bool item_type_is_equivalent_to() const { + return detail::compare_buffer_info::compare(*this); + } + +private: + struct private_ctr_tag {}; + + buffer_info(private_ctr_tag, + void *ptr, + ssize_t itemsize, + const std::string &format, + ssize_t ndim, + detail::any_container &&shape_in, + detail::any_container &&strides_in, + bool readonly) + : buffer_info( + ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) {} + + Py_buffer *m_view = nullptr; + bool ownview = false; +}; + +PYBIND11_NAMESPACE_BEGIN(detail) + +template +struct compare_buffer_info { + static bool compare(const buffer_info &b) { + // NOLINTNEXTLINE(bugprone-sizeof-expression) Needed for `PyObject *` + return b.format == format_descriptor::format() && b.itemsize == (ssize_t) sizeof(T); + } +}; + +template +struct compare_buffer_info::value>> { + static bool compare(const buffer_info &b) { + return (size_t) b.itemsize == sizeof(T) + && (b.format == format_descriptor::value + || ((sizeof(T) == sizeof(long)) + && b.format == (std::is_unsigned::value ? "L" : "l")) + || ((sizeof(T) == sizeof(size_t)) + && b.format == (std::is_unsigned::value ? "N" : "n"))); + } +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/local_install/include/pybind11/cast.h b/local_install/include/pybind11/cast.h new file mode 100644 index 00000000..db393411 --- /dev/null +++ b/local_install/include/pybind11/cast.h @@ -0,0 +1,1704 @@ +/* + pybind11/cast.h: Partial template specializations to cast between + C++ and Python types + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "detail/common.h" +#include "detail/descr.h" +#include "detail/type_caster_base.h" +#include "detail/typeid.h" +#include "pytypes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_WARNING_DISABLE_MSVC(4127) + +PYBIND11_NAMESPACE_BEGIN(detail) + +template +class type_caster : public type_caster_base {}; +template +using make_caster = type_caster>; + +// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T +template +typename make_caster::template cast_op_type cast_op(make_caster &caster) { + return caster.operator typename make_caster::template cast_op_type(); +} +template +typename make_caster::template cast_op_type::type> +cast_op(make_caster &&caster) { + return std::move(caster).operator typename make_caster:: + template cast_op_type::type>(); +} + +template +class type_caster> { +private: + using caster_t = make_caster; + caster_t subcaster; + using reference_t = type &; + using subcaster_cast_op_type = typename caster_t::template cast_op_type; + + static_assert( + std::is_same::type &, subcaster_cast_op_type>::value + || std::is_same::value, + "std::reference_wrapper caster requires T to have a caster with an " + "`operator T &()` or `operator const T &()`"); + +public: + bool load(handle src, bool convert) { return subcaster.load(src, convert); } + static constexpr auto name = caster_t::name; + static handle + cast(const std::reference_wrapper &src, return_value_policy policy, handle parent) { + // It is definitely wrong to take ownership of this pointer, so mask that rvp + if (policy == return_value_policy::take_ownership + || policy == return_value_policy::automatic) { + policy = return_value_policy::automatic_reference; + } + return caster_t::cast(&src.get(), policy, parent); + } + template + using cast_op_type = std::reference_wrapper; + explicit operator std::reference_wrapper() { return cast_op(subcaster); } +}; + +#define PYBIND11_TYPE_CASTER(type, py_name) \ +protected: \ + type value; \ + \ +public: \ + static constexpr auto name = py_name; \ + template >::value, \ + int> \ + = 0> \ + static ::pybind11::handle cast( \ + T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \ + if (!src) \ + return ::pybind11::none().release(); \ + if (policy == ::pybind11::return_value_policy::take_ownership) { \ + auto h = cast(std::move(*src), policy, parent); \ + delete src; \ + return h; \ + } \ + return cast(*src, policy, parent); \ + } \ + operator type *() { return &value; } /* NOLINT(bugprone-macro-parentheses) */ \ + operator type &() { return value; } /* NOLINT(bugprone-macro-parentheses) */ \ + operator type &&() && { return std::move(value); } /* NOLINT(bugprone-macro-parentheses) */ \ + template \ + using cast_op_type = ::pybind11::detail::movable_cast_op_type + +template +using is_std_char_type = any_of, /* std::string */ +#if defined(PYBIND11_HAS_U8STRING) + std::is_same, /* std::u8string */ +#endif + std::is_same, /* std::u16string */ + std::is_same, /* std::u32string */ + std::is_same /* std::wstring */ + >; + +template +struct type_caster::value && !is_std_char_type::value>> { + using _py_type_0 = conditional_t; + using _py_type_1 = conditional_t::value, + _py_type_0, + typename std::make_unsigned<_py_type_0>::type>; + using py_type = conditional_t::value, double, _py_type_1>; + +public: + bool load(handle src, bool convert) { + py_type py_value; + + if (!src) { + return false; + } + +#if !defined(PYPY_VERSION) + auto index_check = [](PyObject *o) { return PyIndex_Check(o); }; +#else + // In PyPy 7.3.3, `PyIndex_Check` is implemented by calling `__index__`, + // while CPython only considers the existence of `nb_index`/`__index__`. + auto index_check = [](PyObject *o) { return hasattr(o, "__index__"); }; +#endif + + if (std::is_floating_point::value) { + if (convert || PyFloat_Check(src.ptr())) { + py_value = (py_type) PyFloat_AsDouble(src.ptr()); + } else { + return false; + } + } else if (PyFloat_Check(src.ptr()) + || (!convert && !PYBIND11_LONG_CHECK(src.ptr()) && !index_check(src.ptr()))) { + return false; + } else { + handle src_or_index = src; + // PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls. +#if PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) + object index; + if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr()) + index = reinterpret_steal(PyNumber_Index(src.ptr())); + if (!index) { + PyErr_Clear(); + if (!convert) + return false; + } else { + src_or_index = index; + } + } +#endif + if (std::is_unsigned::value) { + py_value = as_unsigned(src_or_index.ptr()); + } else { // signed integer: + py_value = sizeof(T) <= sizeof(long) + ? (py_type) PyLong_AsLong(src_or_index.ptr()) + : (py_type) PYBIND11_LONG_AS_LONGLONG(src_or_index.ptr()); + } + } + + // Python API reported an error + bool py_err = py_value == (py_type) -1 && PyErr_Occurred(); + + // Check to see if the conversion is valid (integers should match exactly) + // Signed/unsigned checks happen elsewhere + if (py_err + || (std::is_integral::value && sizeof(py_type) != sizeof(T) + && py_value != (py_type) (T) py_value)) { + PyErr_Clear(); + if (py_err && convert && (PyNumber_Check(src.ptr()) != 0)) { + auto tmp = reinterpret_steal(std::is_floating_point::value + ? PyNumber_Float(src.ptr()) + : PyNumber_Long(src.ptr())); + PyErr_Clear(); + return load(tmp, false); + } + return false; + } + + value = (T) py_value; + return true; + } + + template + static typename std::enable_if::value, handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PyFloat_FromDouble((double) src); + } + + template + static typename std::enable_if::value && std::is_signed::value + && (sizeof(U) <= sizeof(long)), + handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PYBIND11_LONG_FROM_SIGNED((long) src); + } + + template + static typename std::enable_if::value && std::is_unsigned::value + && (sizeof(U) <= sizeof(unsigned long)), + handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PYBIND11_LONG_FROM_UNSIGNED((unsigned long) src); + } + + template + static typename std::enable_if::value && std::is_signed::value + && (sizeof(U) > sizeof(long)), + handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PyLong_FromLongLong((long long) src); + } + + template + static typename std::enable_if::value && std::is_unsigned::value + && (sizeof(U) > sizeof(unsigned long)), + handle>::type + cast(U src, return_value_policy /* policy */, handle /* parent */) { + return PyLong_FromUnsignedLongLong((unsigned long long) src); + } + + PYBIND11_TYPE_CASTER(T, const_name::value>("int", "float")); +}; + +template +struct void_caster { +public: + bool load(handle src, bool) { + if (src && src.is_none()) { + return true; + } + return false; + } + static handle cast(T, return_value_policy /* policy */, handle /* parent */) { + return none().release(); + } + PYBIND11_TYPE_CASTER(T, const_name("None")); +}; + +template <> +class type_caster : public void_caster {}; + +template <> +class type_caster : public type_caster { +public: + using type_caster::cast; + + bool load(handle h, bool) { + if (!h) { + return false; + } + if (h.is_none()) { + value = nullptr; + return true; + } + + /* Check if this is a capsule */ + if (isinstance(h)) { + value = reinterpret_borrow(h); + return true; + } + + /* Check if this is a C++ type */ + const auto &bases = all_type_info((PyTypeObject *) type::handle_of(h).ptr()); + if (bases.size() == 1) { // Only allowing loading from a single-value type + value = values_and_holders(reinterpret_cast(h.ptr())).begin()->value_ptr(); + return true; + } + + /* Fail */ + return false; + } + + static handle cast(const void *ptr, return_value_policy /* policy */, handle /* parent */) { + if (ptr) { + return capsule(ptr).release(); + } + return none().release(); + } + + template + using cast_op_type = void *&; + explicit operator void *&() { return value; } + static constexpr auto name = const_name("capsule"); + +private: + void *value = nullptr; +}; + +template <> +class type_caster : public void_caster {}; + +template <> +class type_caster { +public: + bool load(handle src, bool convert) { + if (!src) { + return false; + } + if (src.ptr() == Py_True) { + value = true; + return true; + } + if (src.ptr() == Py_False) { + value = false; + return true; + } + if (convert || (std::strcmp("numpy.bool_", Py_TYPE(src.ptr())->tp_name) == 0)) { + // (allow non-implicit conversion for numpy booleans) + + Py_ssize_t res = -1; + if (src.is_none()) { + res = 0; // None is implicitly converted to False + } +#if defined(PYPY_VERSION) + // On PyPy, check that "__bool__" attr exists + else if (hasattr(src, PYBIND11_BOOL_ATTR)) { + res = PyObject_IsTrue(src.ptr()); + } +#else + // Alternate approach for CPython: this does the same as the above, but optimized + // using the CPython API so as to avoid an unneeded attribute lookup. + else if (auto *tp_as_number = src.ptr()->ob_type->tp_as_number) { + if (PYBIND11_NB_BOOL(tp_as_number)) { + res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr()); + } + } +#endif + if (res == 0 || res == 1) { + value = (res != 0); + return true; + } + PyErr_Clear(); + } + return false; + } + static handle cast(bool src, return_value_policy /* policy */, handle /* parent */) { + return handle(src ? Py_True : Py_False).inc_ref(); + } + PYBIND11_TYPE_CASTER(bool, const_name("bool")); +}; + +// Helper class for UTF-{8,16,32} C++ stl strings: +template +struct string_caster { + using CharT = typename StringType::value_type; + + // Simplify life by being able to assume standard char sizes (the standard only guarantees + // minimums, but Python requires exact sizes) + static_assert(!std::is_same::value || sizeof(CharT) == 1, + "Unsupported char size != 1"); +#if defined(PYBIND11_HAS_U8STRING) + static_assert(!std::is_same::value || sizeof(CharT) == 1, + "Unsupported char8_t size != 1"); +#endif + static_assert(!std::is_same::value || sizeof(CharT) == 2, + "Unsupported char16_t size != 2"); + static_assert(!std::is_same::value || sizeof(CharT) == 4, + "Unsupported char32_t size != 4"); + // wchar_t can be either 16 bits (Windows) or 32 (everywhere else) + static_assert(!std::is_same::value || sizeof(CharT) == 2 || sizeof(CharT) == 4, + "Unsupported wchar_t size != 2/4"); + static constexpr size_t UTF_N = 8 * sizeof(CharT); + + bool load(handle src, bool) { + handle load_src = src; + if (!src) { + return false; + } + if (!PyUnicode_Check(load_src.ptr())) { + return load_raw(load_src); + } + + // For UTF-8 we avoid the need for a temporary `bytes` object by using + // `PyUnicode_AsUTF8AndSize`. + if (UTF_N == 8) { + Py_ssize_t size = -1; + const auto *buffer + = reinterpret_cast(PyUnicode_AsUTF8AndSize(load_src.ptr(), &size)); + if (!buffer) { + PyErr_Clear(); + return false; + } + value = StringType(buffer, static_cast(size)); + return true; + } + + auto utfNbytes + = reinterpret_steal(PyUnicode_AsEncodedString(load_src.ptr(), + UTF_N == 8 ? "utf-8" + : UTF_N == 16 ? "utf-16" + : "utf-32", + nullptr)); + if (!utfNbytes) { + PyErr_Clear(); + return false; + } + + const auto *buffer + = reinterpret_cast(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr())); + size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT); + // Skip BOM for UTF-16/32 + if (UTF_N > 8) { + buffer++; + length--; + } + value = StringType(buffer, length); + + // If we're loading a string_view we need to keep the encoded Python object alive: + if (IsView) { + loader_life_support::add_patient(utfNbytes); + } + + return true; + } + + static handle + cast(const StringType &src, return_value_policy /* policy */, handle /* parent */) { + const char *buffer = reinterpret_cast(src.data()); + auto nbytes = ssize_t(src.size() * sizeof(CharT)); + handle s = decode_utfN(buffer, nbytes); + if (!s) { + throw error_already_set(); + } + return s; + } + + PYBIND11_TYPE_CASTER(StringType, const_name(PYBIND11_STRING_NAME)); + +private: + static handle decode_utfN(const char *buffer, ssize_t nbytes) { +#if !defined(PYPY_VERSION) + return UTF_N == 8 ? PyUnicode_DecodeUTF8(buffer, nbytes, nullptr) + : UTF_N == 16 ? PyUnicode_DecodeUTF16(buffer, nbytes, nullptr, nullptr) + : PyUnicode_DecodeUTF32(buffer, nbytes, nullptr, nullptr); +#else + // PyPy segfaults when on PyUnicode_DecodeUTF16 (and possibly on PyUnicode_DecodeUTF32 as + // well), so bypass the whole thing by just passing the encoding as a string value, which + // works properly: + return PyUnicode_Decode(buffer, + nbytes, + UTF_N == 8 ? "utf-8" + : UTF_N == 16 ? "utf-16" + : "utf-32", + nullptr); +#endif + } + + // When loading into a std::string or char*, accept a bytes/bytearray object as-is (i.e. + // without any encoding/decoding attempt). For other C++ char sizes this is a no-op. + // which supports loading a unicode from a str, doesn't take this path. + template + bool load_raw(enable_if_t::value, handle> src) { + if (PYBIND11_BYTES_CHECK(src.ptr())) { + // We were passed raw bytes; accept it into a std::string or char* + // without any encoding attempt. + const char *bytes = PYBIND11_BYTES_AS_STRING(src.ptr()); + if (!bytes) { + pybind11_fail("Unexpected PYBIND11_BYTES_AS_STRING() failure."); + } + value = StringType(bytes, (size_t) PYBIND11_BYTES_SIZE(src.ptr())); + return true; + } + if (PyByteArray_Check(src.ptr())) { + // We were passed a bytearray; accept it into a std::string or char* + // without any encoding attempt. + const char *bytearray = PyByteArray_AsString(src.ptr()); + if (!bytearray) { + pybind11_fail("Unexpected PyByteArray_AsString() failure."); + } + value = StringType(bytearray, (size_t) PyByteArray_Size(src.ptr())); + return true; + } + + return false; + } + + template + bool load_raw(enable_if_t::value, handle>) { + return false; + } +}; + +template +struct type_caster, + enable_if_t::value>> + : string_caster> {}; + +#ifdef PYBIND11_HAS_STRING_VIEW +template +struct type_caster, + enable_if_t::value>> + : string_caster, true> {}; +#endif + +// Type caster for C-style strings. We basically use a std::string type caster, but also add the +// ability to use None as a nullptr char* (which the string caster doesn't allow). +template +struct type_caster::value>> { + using StringType = std::basic_string; + using StringCaster = make_caster; + StringCaster str_caster; + bool none = false; + CharT one_char = 0; + +public: + bool load(handle src, bool convert) { + if (!src) { + return false; + } + if (src.is_none()) { + // Defer accepting None to other overloads (if we aren't in convert mode): + if (!convert) { + return false; + } + none = true; + return true; + } + return str_caster.load(src, convert); + } + + static handle cast(const CharT *src, return_value_policy policy, handle parent) { + if (src == nullptr) { + return pybind11::none().release(); + } + return StringCaster::cast(StringType(src), policy, parent); + } + + static handle cast(CharT src, return_value_policy policy, handle parent) { + if (std::is_same::value) { + handle s = PyUnicode_DecodeLatin1((const char *) &src, 1, nullptr); + if (!s) { + throw error_already_set(); + } + return s; + } + return StringCaster::cast(StringType(1, src), policy, parent); + } + + explicit operator CharT *() { + return none ? nullptr : const_cast(static_cast(str_caster).c_str()); + } + explicit operator CharT &() { + if (none) { + throw value_error("Cannot convert None to a character"); + } + + auto &value = static_cast(str_caster); + size_t str_len = value.size(); + if (str_len == 0) { + throw value_error("Cannot convert empty string to a character"); + } + + // If we're in UTF-8 mode, we have two possible failures: one for a unicode character that + // is too high, and one for multiple unicode characters (caught later), so we need to + // figure out how long the first encoded character is in bytes to distinguish between these + // two errors. We also allow want to allow unicode characters U+0080 through U+00FF, as + // those can fit into a single char value. + if (StringCaster::UTF_N == 8 && str_len > 1 && str_len <= 4) { + auto v0 = static_cast(value[0]); + // low bits only: 0-127 + // 0b110xxxxx - start of 2-byte sequence + // 0b1110xxxx - start of 3-byte sequence + // 0b11110xxx - start of 4-byte sequence + size_t char0_bytes = (v0 & 0x80) == 0 ? 1 + : (v0 & 0xE0) == 0xC0 ? 2 + : (v0 & 0xF0) == 0xE0 ? 3 + : 4; + + if (char0_bytes == str_len) { + // If we have a 128-255 value, we can decode it into a single char: + if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) { // 0x110000xx 0x10xxxxxx + one_char = static_cast(((v0 & 3) << 6) + + (static_cast(value[1]) & 0x3F)); + return one_char; + } + // Otherwise we have a single character, but it's > U+00FF + throw value_error("Character code point not in range(0x100)"); + } + } + + // UTF-16 is much easier: we can only have a surrogate pair for values above U+FFFF, thus a + // surrogate pair with total length 2 instantly indicates a range error (but not a "your + // string was too long" error). + else if (StringCaster::UTF_N == 16 && str_len == 2) { + one_char = static_cast(value[0]); + if (one_char >= 0xD800 && one_char < 0xE000) { + throw value_error("Character code point not in range(0x10000)"); + } + } + + if (str_len != 1) { + throw value_error("Expected a character, but multi-character string found"); + } + + one_char = value[0]; + return one_char; + } + + static constexpr auto name = const_name(PYBIND11_STRING_NAME); + template + using cast_op_type = pybind11::detail::cast_op_type<_T>; +}; + +// Base implementation for std::tuple and std::pair +template